跳转到内容

Python 编程/装饰器

来自维基教科书,开放世界中的开放书籍


在软件中,重复代码被认为是一种不好的做法,主要原因是它需要更多的工作量来维护。如果同一个算法对不同的数据执行两次,你可以将算法放入一个函数中并将数据传递给它,以避免重复代码。但是,有时你会发现代码本身发生了变化,但两个或多个地方仍然有大量的重复样板代码。一个典型的例子可能是记录

def multiply(a, b):
    result = a * b
    log("multiply has been called")
    return result

def add(a, b):
    result = a + b
    log("add has been called")
    return result

在这种情况下,如何消除重复并不明显。我们可以遵循我们之前将公共代码移到函数中的模式,但是用不同的数据调用函数不足以产生我们想要的不同行为(加法或乘法)。相反,我们必须将一个函数传递给公共函数。这涉及一个对函数进行操作的函数,称为高阶函数

Python 中的装饰器是高阶函数的语法糖。

属性装饰器的最小示例

>>> class Foo(object):
...     @property
...     def bar(self):
...         return 'baz'
...
>>> F = Foo()
>>> print(F.bar)
baz

上面的示例实际上只是像这样的代码的语法糖

>>> class Foo(object):
...     def bar(self):
...         return 'baz'
...     bar = property(bar)
...
>>> F = Foo()
>>> print(F.bar)
baz

通用装饰器的最小示例

>>> def decorator(f):
...     def called(*args, **kargs):
...         print('A function is called somewhere')
...         return f(*args, **kargs)
...     return called
...
>>> class Foo(object):
...     @decorator
...     def bar(self):
...         return 'baz'
...
>>> F = Foo()
>>> print(F.bar())
A function is called somewhere
baz

装饰器的良好用途是允许你重构你的代码,以便可以将常见功能移动到装饰器中。例如,假设你想跟踪对某些函数的所有调用,并打印出每次调用时所有函数参数的值。现在你可以在装饰器中实现它,如下所示

#define the Trace class that will be 
#invoked using decorators
class Trace(object):
    def __init__(self, f):
        self.f =f

    def __call__(self, *args, **kwargs):
        print("entering function " + self.f.__name__)
        i=0
        for arg in args:
            print("arg {0}: {1}".format(i, arg))
            i =i+1
            
        return self.f(*args, **kwargs)

然后,你可以通过以下方式将装饰器用于你定义的任何函数

@Trace
def sum(a, b):
    print "inside sum"
    return a + b

运行此代码,你会看到类似的输出

>>> sum(3,2)
entering function sum
arg 0: 3
arg 1: 2
inside sum

或者,你可以使用函数而不是将装饰器创建为类。

def Trace(f):
    def my_f(*args, **kwargs):
        print("entering " +  f.__name__)
        result= f(*args, **kwargs)
        print("exiting " +  f.__name__)
        return result
    my_f.__name = f.__name__
    my_f.__doc__ = f.__doc__
    return my_f

#An example of the trace decorator
@Trace
def sum(a, b):
    print("inside sum")
    return a + b

#if you run this you should see
>>> sum(3,2)
entering sum
inside sum
exiting sum
5

请记住,返回函数或一个合适的装饰替换函数是一个好的做法,这样就可以对装饰器进行链式调用。

华夏公益教科书