Python decorator 装饰器

装饰器模式(Decorator Pattern)是设计模式中的一种,主要目的是提供了这样一种操作,可以在不需要改变函数实现代码的情况下,用来修改或者完善它的功能。

Python 里的 decorator 也是基于这样的设计,比如:

@log
def test_func(a, b):
    pass

原来的 test_func 函数,经过 log 装饰器后,变成了 log(test_func),所有调用 test_func 的地方,现在都变成了调用 log(test_func),从而实现了在保持原来 test_func 的实现、以及使用的地方不变的情况下,添加了新的功能。用示意图,大概是这样的:

                   +----------+
                   | x x x x x|  <-- log
+----------+       +----------+
|//////////|  ==>  |//////////|
|//////////|       |//////////|
+----------+       +----------+
                   | y y y y y|  <-- log
                   +----------+

 test_func    ==>  test_func (with decorator)
   ^                    ^
   |                    |
   |                    |
  Demo -----------------/

举例

举例一下代码、以及对它的调用:

def test_func(a, b):
    print('%d + %d = %d' % (a, b, a + b))

test_func(1, 2)

会得到这样的输出:

1 + 2 = 3

简单的装饰器

可以这样定义并使用 decorator(以补丁方式突出增加的代码):

+def log(func):
+    def wrapper(*args, **kwargs):
+        print('before call')
+        func(*args, **kwargs)
+        print('after call')
+    return wrapper
+
+@log
 def test_func(a, b):
     print('%d + %d = %d' % (a, b, a + b))

 test_func(1, 2)

这样,通过一个语法 @log 装饰了 test_func,可以得到这样的结果:

before call
1 + 2 = 3
after call

可以看到,装饰器可以在调用真正函数的前面和后面,加入代码逻辑实现新的功能。

test_func 和 wrapper 的关系

在代码里打印这两个函数的 id:

 def log(func):
     def wrapper(*args, **kwargs):
+        print('id(func) = %r' % (id(func)))
+        print('id(wrapper) = %r' % (id(wrapper)))
         print('before call')
         func(*args, **kwargs)
         print('after call')
     return wrapper

 @log
 def test_func(a, b):
+    print('id(test_func) = %r' % (id(test_func)))
     print('%d + %d = %d' % (a, b, a + b))

 test_func(1, 2)
+print('id(test_func) = %r' % (id(test_func)))

可以得到这样的输出(id 取决于实际运行时):

id(func) = 140717307679808
id(wrapper) = 140717307679968
before call
id(test_func) = 140717307679968
1 + 2 = 3
after call
id(test_func) = 140717307679968

可以看到出现了两个不同的 id,其中 func(即原来的 test_func) 是一个 id,而 wrapper装饰过的 test_func 是另一个 id。所以这时的 test_func 实际上是 log(test_func),或者说是 log(test_func).wrapper

带参数的装饰器

装饰器 decorator 是允许带参数的,比如:

def log(x, y):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print('x=%r, y=%r' % (x, y))
            print('before call')
            func(*args, **kwargs)
            print('after call')
        return wrapper
    return decorator

@log('XXX', 'YYYY')
def test_func(a, b):
    print('%d + %d = %d' % (a, b, a + b))

test_func(1, 2)

这里的 test_func 经过装饰器后,实际上是 log(..).decorator(test_func).wrapper,输出是这样的:

x='XXX', y='YYYY'
before call
1 + 2 = 3
after call

基于类实现的装饰器

装饰器可以用类来实现,比如:

class log:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print('x=%r, y=%r' % (self.x, self.y))
            print('before call')
            func(*args, **kwargs)
            print('after call')
        return wrapper

@log('XXX', 'YYYY')
def test_func(a, b):
    print('%d + %d = %d' % (a, b, a + b))

这里的 test_func 经过装饰器后,实际上是 log(..)(test_func).wrapper。输出同上。

Read More: