跳转到内容

Python/装饰器

维基教科书,自由的教学读本

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

一个函数decorators用于函数定义,它位于在函数定义之前的一行。例如:

@myDecorator
def aFunction():
   print "inside aFunction"

当编译器处理这段代码时,aFunction()被编译然后将结果函数对象传递给myDecorator代码,后者创建一个类函数对象并取代原来的aFunction()。

Decorator所返回的对象唯一的约束是它可以作为函数使用。意味着它必须是可调用的。因此,作为decorators使用的任何一个类必须实现__call__。

简介[编辑]

属性装饰器(property decorator)的最简单例子:

>>> 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

泛型装饰器(generic decorator)的最简单例子:

>>> 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

装饰器是一个著名的设计模式,经常被用于有切面(aspect)需求的场景,如插入日志、性能测试、事务处理等。装饰器可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。

Python于是提供了一些语法糖来降低为使用装饰器而带来的源代码输入量。

import time
 
def timeit(func):
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
    return wrapper
 
@timeit
def foo():
    print 'in foo()'
 
foo()

内置的装饰器有三个,分别是staticmethod、classmethod和property,分别把类中定义的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多,除非你想要完全的面向对象编程。属性也不是不可或缺的。

class Rabbit(object):     
    def __init__(self, name):
        self._name = name
     
    @staticmethod
    def newRabbit(name):
        return Rabbit(name)
     
    @classmethod
    def newRabbit2(cls):
        return Rabbit('')
     
    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name

functools模块[编辑]

def wrapper(func):
    def inner_function():
        pass
    return inner_function

def wrapped():
    pass

print(wrapper(wrapped).__name__)
#inner_function

如何避免这种情况的产生?方法是使用 functools .wraps 装饰器,它的作用就是将 被修饰的函数(wrapped) 的一些属性值赋值给 修饰器函数(wrapper) ,最终让属性的显示更符合我们的直觉。

wraps(wrapped[, assigned][, updated]): 将装饰过的函数的特殊属性保留。

from functools import wraps

def wrapper(func):
    @wraps(func) 
    def inner_function():
        pass
    return inner_function

@wrapper
def wrapped():
    pass

print(wrapped.__name__)
# wrapped

wraps 其实是一个偏函数对象(partial),源码如下

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

可以看到wraps其实就是调用了一个函数update_wrapper,知道原理后,我们改写上面的代码,在不使用 wraps的情况下,也可以让 wrapped.__name__ 打印出 wrapped,代码如下:

from functools import update_wrapper

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')

def wrapper(func):
    def inner_function():
        pass

    update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS)
    return inner_function

@wrapper
def wrapped():
    pass

print(wrapped.__name__)

带参数的装饰器[编辑]

不传参的装饰器,只能对被装饰函数,执行固定逻辑。如果要用到两个内容大体一致,只是某些地方不同的逻辑。不传参的话,就要写两个装饰器。装饰器如何实现传参呢,会比较复杂,需要多一层嵌套,最外层的装饰器的函数接受这些参数。例如: 函数闭包实现的示例:

def decoratorFunctionWithArguments(arg1, arg2, arg3):
    def wrap(f):
        print("Inside wrap()")
        def wrapped_f(*args):
            print("Inside wrapped_f()")
            print("Decorator arguments:", arg1, arg2, arg3)
            f(*args)
            print("After f(*args)")
        return wrapped_f
    return wrap
 
@decoratorFunctionWithArguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
    print('sayHello arguments:', a1, a2, a3, a4)

带参数的情况与无参情况截然不同:__call__()对象不能再作为decorated函数使用了。带参数情况的decoration方法调用构造函数,然后就马上调用__call__(),后者只能包含一个参数(函数对象)且返回替代原有函数的decorated函数对象。注意decoration期间__call__()仅被调用一次,此后从__call__()返回的decorated函数就可以在实际调用中使用了。

带参数的类装饰器示例:

class decoratorWithArguments(object):
    def __init__(self, arg1, arg2, arg3):#不再接收被装饰函数,而是接收装饰器传入参数。
        """
        If there are decorator arguments, the function
        to be decorated is not passed to the constructor!
        """
        print "Inside __init__()"
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

    def __call__(self, f):#接收被装饰函数,实现装饰逻辑。
        """
        If there are decorator arguments, __call__() is only called
        once, as part of the decoration process! You can only give
        it a single argument, which is the function object.
        """
        print "Inside __call__()"
        def wrapped_f(*args):
            print "Inside wrapped_f()"
            print "Decorator arguments:", self.arg1, self.arg2, self.arg3
            f(*args)
            print "After f(*args)"
        return wrapped_f

@decoratorWithArguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
    print 'sayHello arguments:', a1, a2, a3, a4