理解 Python 中的装饰器

一直觉得装饰器很神奇。如 Flask 中用到的路由装饰器:

@app.route('/')
def hello_world():
    return 'Hello World!

看了《简单 12 步理解 Python 装饰器》,有些心得,记录一下新知识。

1、内嵌函数

Python 允许创建内嵌函数。即可以在函数内部声明函数,并且所有的作用域和生命周期规则仍然适用。

>>> def outer():
...     x = 1
...     def inner():
...         print x # 1
...     inner() # 2
...
>>> outer()
1

2、函数也是对象

function 是 object 的子类,可以作为参数传递

>>> def add(x, y):
... return x + y
>>> def sub(x, y):
... return x - y
>>> def apply(func, x, y): # 1
... return func(x, y) # 2
>>> apply(add, 2, 1) # 3
3
>>> apply(sub, 2, 1)
1

3、闭包

打破了作用域规则。即使外层函数被销毁,内层函数也可以记住外层作用域。

>>> def outer(x):
...     def inner():
...         print x # 1
...     return inner
>>> print1 = outer(1)
>>> print2 = outer(2)
>>> print1()
1
>>> print2()
2

4、装饰器

利用内嵌函数机制,对函数进行包装,添加额外功能

>>> class Coordinate(object):
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...     def __repr__(self):
...         return "Coord: " + str(self.__dict__)
>>> def add(a, b):
...     return Coordinate(a.x + b.x, a.y + b.y)
>>> def sub(a, b):
...     return Coordinate(a.x - b.x, a.y - b.y)
>>> one = Coordinate(100, 200)
>>> two = Coordinate(300, 200)
>>> three = Coordinate(-100, -100)
>>> def wrapper(func):
...     def checker(a, b): # 1
...         if a.x < 0 or a.y < 0:
...             a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
...         if b.x < 0 or b.y < 0:
...             b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
...         ret = func(a, b)
...         if ret.x < 0 or ret.y < 0:
...             ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
...         return ret
...     return checker
>>> add = wrapper(add)
>>> sub = wrapper(sub)
>>> sub(one, two)
Coord: {'y': 0, 'x': 0}
>>> add(one, three)
Coord: {'y': 200, 'x': 100}

5、装饰器语法糖

Python 2.4 通过在函数定义前添加一个装饰器名和 @ 符号,来实现对函数的包装。

>>> @ wrapper 
... def add(a, b): 
...     return Coordinate(a.x + b.x, a.y + b.y)

6、用可变参数实现更通用的装饰器

>>> def logger(func):
...     def inner(*args, **kwargs): #1
...         print "Arguments were: %s, %s" % (args, kwargs)
...         return func(*args, **kwargs) #2
...     return inner
>>> @logger
... def foo1(x, y=1):
...     return x * y
>>> @logger
... def foo2():
...     return 2
>>> foo1(5, 4)
Arguments were: (5, 4), {}
20
>>> foo1(1)
Arguments were: (1,), {}
1
>>> foo2()
Arguments were: (), {}
2