【问题标题】:Class decorators vs function decorators [duplicate]类装饰器与函数装饰器[重复]
【发布时间】:2011-10-04 07:08:04
【问题描述】:

在python中有两种声明装饰器的方法:

基于类

class mydecorator(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *k, **kw):
        # before f actions
        self.f(*k, **kw)
        # after f actions

基于函数

def mydecorator(f):
    def decorator(*k, **kw):
        # before f actions
        f(*k, **kw)
        # after f actions

    return decorator          

这些声明之间有什么区别吗? 在哪些情况下应该使用它们?

【问题讨论】:

  • 有了类,就可以使用描述符协议了!
  • @phant0m:不利的一面是,您必须使用描述符协议的类(否则您的装饰器将无法完全在方法上工作)。
  • @Rosh:嗯,是的,你有一个非常强的地方:)

标签: python decorator


【解决方案1】:

当您创建一个返回另一个可调用对象的可调用对象时,函数方法更简单、更便宜。有两个主要区别:

  1. 函数方法自动与方法一起使用,而如果您使用类方法,则必须阅读描述符并定义 __get__ 方法。
  2. 类方法使保持状态更容易。您可以使用闭包,尤其是在 Python 3 中,但通常首选类方法来保持状态。

此外,函数方法允许您在修改或存储后返回原始函数。

然而,装饰器可以返回非可调用对象或可调用对象更多的东西。通过类,您可以:

  1. 向修饰的可调用对象添加方法和属性,或对它们执行操作(呃-哦)。
  2. 创建在放置在类中时以特殊方式起作用的描述符(例如classmethodproperty
  3. 使用继承来实现相似但不同的装饰器。

如果您有任何疑问,请问问自己:您是否希望您的装饰器返回一个功能与功能完全一样的功能?使用返回函数的函数。您是否希望您的装饰器返回一个自定义对象,该对象执行更多或与函数不同的功能?创建一个类并将其用作装饰器。

【讨论】:

  • +1 令人震惊的是没有其他人注意到方法问题!
  • +1 非常完整的答案。迟到了,但这应该是公认的答案。
  • “函数方法自动与方法一起工作”这是一个显着的区别。谢谢!
【解决方案2】:

如果你想在装饰器中保持状态,你应该使用一个类。

例如,这不起作用

def mydecorator(f):
    x = 0 
    def decorator():
        x += 1 # x is a nonlocal name and cant be modified
        return f(x)
    return decorator 

有很多解决方法,但最简单的方法是使用类

class mydecorator(object):
    def __init__(self, f):
        self.f = f
        self.x = 0

    def __call__(self, *k, **kw):
        self.x += 1
        return f(self.x)

【讨论】:

  • 我认为最简单的方法是说nonlocal x; x += 1。或者这在低于 3 的 Python 版本中不起作用? -- 是的,看起来nonlocal 是在 Python 3 中引入的。
  • 更多关于方法装饰器限制的信息可以在stackoverflow.com/questions/6676015/… answer中找到。
  • 一个被多个模块使用的有状态装饰器对它的任何使用都取决于使用它的其他模块在程序的其他地方被导入的顺序,包括在完全不相关的模块所以我的第一条经验法则是,如果你想在装饰器中保持状态“停下来想想这是否是最好的方法”。有时当然会,在这种情况下是的,你应该使用类装饰器。
【解决方案3】:

让我们测试一下吧!

test_class = """
class mydecorator_class(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *k, **kw):
        # before f actions
        print 'hi class'
        self.f(*k, **kw)
        print 'goodbye class'
        # after f actions

@mydecorator_class
def cls():
    print 'class'

cls()
"""

test_deco = """
def mydecorator_func(f):
    def decorator(*k, **kw):
        # before f actions
        print 'hi function'
        f(*k, **kw)
        print 'goodbye function'
        # after f actions
    return decorator

@mydecorator_func
def fun():
    print 'func'

fun()
"""

if __name__ == "__main__":
    import timeit
    r = timeit.Timer(test_class).timeit(1000)
    r2 = timeit.Timer(test_deco).timeit(1000)
    print r, r2

我得到这样的结果:0.0499339103699 0.0824959278107

这是否意味着装饰类快 2 倍?

【讨论】:

  • 很有趣,但我想它是特定于实现的。使用其他 python 实现(Jython、PyPy)甚至不同版本的 CPython,您可能会得到完全不同的结果。此外,这可能是运行“mydecorator_func”和类定义代码的时间差异。这只是应用程序的启动时间,通常无关紧要。在这两种情况下,装饰函数调用速度可能相同。
【解决方案4】:

不,有(不止)两种方法可以创建可调用对象。一种是给def一个函数,显然是可以调用的。另一个是在一个类中定义一个__call__ 方法,这将使它的实例可以调用。而类本身就是可调用对象。

装饰器只不过是一个可调用对象,它旨在接受一个函数作为其唯一参数并返回可调用的东西。以下语法:

@decorate
def some_function(...):
    ...

只是一种更好的写作方式:

def some_function(...):
    ...

some_function = decorate(some_function)

你给出的基于类的例子不是一个接受一个函数并返回一个函数的函数,这是一个沼泽标准的香草装饰器,它是一个用实例可调用的函数初始化的类。对我来说,如果您实际上没有将它用作一个类,这有点奇怪(它有其他方法吗?它的状态会改变吗?您是否创建了几个具有该类封装的共同行为的实例?)。但是正常使用你的装饰函数不会有区别(除非它是一个特别具有侵入性的装饰器),所以你可以做任何你觉得更自然的事情。

【讨论】:

    【解决方案5】:

    事实上,没有“两种方式”。只有一种方法(定义可调用对象)或与 python 中一样多的方法来创建可调用对象(它可能是其他对象的方法,lambda 表达式的结果,“部分”对象,任何可调用)。

    函数定义是创建可调用对象的最简单方法,并且作为最简单的方法,在大多数情况下可能是最好的。使用类为您提供了更多可能性来清晰地编写更复杂的情况(即使在最简单的情况下它看起来也很优雅),但它的作用并不那么明显。

    【讨论】:

      猜你喜欢
      • 2019-09-01
      • 2011-06-06
      • 2012-02-09
      • 2010-10-27
      • 2013-02-06
      • 2011-09-30
      • 2020-03-26
      • 2015-06-11
      • 2011-06-25
      相关资源
      最近更新 更多