学习python中的Decorator

What is a Decorator?

参考Decorator Pattern wiki,(这个维基词条多讲得是Java/C#/C++相关,但是与python中的实现的功能类似.)

A decorator is the name used for a software design pattern. Decorators dynamically alter the functionality of a function, method, or class without having to directly use subclasses or change the source code of the function being decorated.

修饰模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。

通过使用修饰模式,可以在运行时扩充一个类的功能。原理是:增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。修饰类必须和原来的类有相同的接口。

Python Decorator:

A decorator is any callable Python object that is used to modify a function, method or class definition. A decorator is passed the original object being defined and returns a modified object, which is then bound to the name in the definition. Python decorators were inspired in part by Java annotations, and have a similar syntax; the decorator syntax is pure syntactic sugar, using @ as the keyword:

在python中,Decorator与函数式编程(Functional Programming)元编程(Metaprogramming)息息相关,可以用Decorator来修改一个函数,类以及类的定义.

动机

接下来举几个简单例子说明python中为何要使用Decorator.

  1. 更加简明,易读

    如下面两个例子,分别要实现一个classmethod方法和测试函数试行时间的功能,但是函数的定义和最后要实现的功能处出现了分离,对函数的操作变换代码出现在了函数的后面位置,不管是代码复杂性和逻辑都有问题.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from time import time
    # example 1: a classmethod function
    def foo(self):
    do_something
    foo = classmethod(foo)

    # example 2: timer for a function
    def foo():
    do_something
    def timer(func):
    def decor_fun():
    start_time = time()
    func()
    print("Cost time: ", time()-start_time)
    return decor_fun
    foo = timer(foo)

    通过Decorator更加直观,明了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from time import time
    # example 1
    @classmethod
    def foo(cls):
    do_something

    # example 2
    def timer(func):
    def decor_fun():
    start_time = time()
    func()
    print("Cost time: ", time()-start_time)
    return decor_fun

    @timer
    def foo():
    do_something
  2. 实现特定的功能

    参考这个知乎问题:Python 中的 classmethod 和 staticmethod 有什么具体用途?,还可以结合函数式编程,某个函数功能需要被其他多个函数重复实现时,可以考虑Decorator,如: example 2测试函数运行时间, 输出log日志等等.

如何使用

通过example 2以及python Decorator Wiki不难看出@符号其实是以要定义的函数foo作为参数传入@后面的函数bar中,返回一个新的同时实现两者功能的函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@bar
def foo():
return "foo"

# 等价于以下的 bar(foo)

def foo():
print("foo")

def bar(func):
def func_decor():
print("bar/"+func())
return func_decor

带参数呢?

如果接触过函数式编程的话,不难解决这个问题,更普遍的方法可以使用*args**kwargs关键字,针对带有默认参数和不带有默认参数的函数都可以解决.

1
2
3
4
5
6
7
8
9
10
11
12
13
from time import time
def timer(func, *args, **kwargs):
def decor_func(*args, **kwargs):
start_time = time()
ans = func(*args, **kwargs)
print("cost time: ", time()-start_time)
return ans
return decor_func

@timer
def add2(x, y=66):
return x + y

另外,Decorator也是可以输入参数的,还是上一个例子,实现调用n次函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
def ntimes(n):
def innner(func)
def wrapper(*args, **kwargs):
for _ in range(n):
print("calling function %s"%func.__name__)
ans = func(*args, **kwargs)
return ans
return wrapper
return inner
n = 10
@ntimes(10)
def add2(x, y=66):
return x + y

本例中,从函数式编程角度看,深度更深了一层,从@本质理解,其后符号ntimes也是函数,故也可接受参数,一般Decorator不会比这个更深,所以了解即可. 详情可以了解python中的闭包.

多个Decorator

道理都是类似的,语法为

1
2
3
4
@dec2
@dec1
def func(arg1, arg2, ...):
pass

Class Decorator

In Python prior to version 2.6, decorators apply to functions and methods, but not to classes. Decorating a (dummy) __new__ method can modify a class, however.[25]Class decorators are supported[26] starting with Python 2.6.

Python 2.6之后才支持class decorators,之前都是通过metaclass来实现相应功能,__new__方法修改类.

按照之前的解释,Class Decorator与Function Decorator应该是基本相同的,只是装饰的对象不同而已.

Decorator 作用整个类

可以看到此时的decorator接受一个类作为函数参数,返回一个修饰之后的类,跟之前一样的道理.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def decorator(cls):
class Wrapper(object):
def __init__(self, *args):
self.wrapped = cls(*args)

def __getattr__(self, name):
print('Getting the {} of {}'.format(name, self.wrapped))
return getattr(self.wrapped, name)

return Wrapper

@decorator
class C(object):
def __init__(self, x, y):
self.x = x
self.y = y

Decorator本身是类

如下面例子所示,

__init__方法接受要修饰的函数为参数.

__call__方法在调用被修饰函数时调用.

此外还有__set__, __get__, __getattribute__等方法均可修改.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class decorator(object):
def __init__(self, func):
self.func = func

def __call__(self, *args):
print('Called {func} with args: {args}'.format(func=self.func.func_name,
args=args))
return self.func(*args)

@decorator
def func(x,y):
return x + y

func(1, 3)

从这个角度看,Decorator可以实现修改调用模型的这一功能. 可以参考修改调用模型.

改进

仔细研读细节就会发现,使用decorator可以完成期望的功能,取得成效,但是也会带来代码细节与原本出现偏差的问题,毕竟对了一层调用,改进方法可以使用 functools wrapsDecorator.

1
2
3
4
5
6
7
from functools import wraps

def some_decorator(func):
@wraps(f)
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper

也可以使用decorator.

1
2
3
4
5
6
7
from decorator import decorator

@decorator
def some_decorator(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper