在23中设计模式中,给出关于装饰器模式的定义是:就是对已经存在的某些类进行装饰,以此来扩展或者说加强函数的一些功能;然而在python中,装饰器使用一种语法糖来实现。不知道你是否知道nonlocal关键字以及闭包,可是若是你想本身实现函数装饰器,那就必须了解闭包的方方面面,所以也就须要知道 nonlocal
要理解装饰器,就要知道下面问题:html
装饰器是可调用的对象,其参数是另外一个函数(被装饰的函数)。 装饰器可能会处理被装饰的函数,而后把它返回,或者将其替换成另外一个函数或可调用对象。
活很少说来看下面例子:python
>>> def deco(func): ... def inner(): ... print('running inner()') ... return inner ➊ ... >>> @deco ... def target(): ➋ ... print('running target()') ... >>> target() ➌ running inner() >>> target ➍ <function deco.<locals>.inner at 0x10063b598>
❶ deco 返回 inner 函数对象。
❷ 使用 deco 装饰 target。
❸ 调用被装饰的 target 其实会运行 inner。
❹ 审查对象,发现 target 如今是 inner 的引用。
若是看完这个例子你可能会有疑问,明明上面说的是,装饰器是加强函数的功能,可是为何这里却改变了函数的打印结果,这是由于在deco(func)
函数传入的形参是func
,可是后来咱们在func
这个函数中却没有用到func
而是用inner
,所以target
的行为改变了。
注:若是你对这里的操纵台的操做 target
输出结果有疑问,或者说不知道为何不是 target()
的话,能够参考博主的另外一篇博文 一等函数,毕竟函数在Python中也是一种对象!程序员
装饰器的一大特性是,能把被装饰的函数替换成其余函数。第二个特性是,装饰器在加载模块时当即执行,下面咱们来看一下。设计模式
装饰器的一个关键特性是,它们在被装饰的函数定义以后当即运行。这一般是在导入时(即 Python 加载模块时)闭包
registry = [] ➊\ def register(func): ➋ print('running register(%s)' % func) ➌ registry.append(func) ➍ return func ➎ @register ➏ def f1(): print('running f1()') @register def f2(): print('running f2()') def f3(): ➐ print('running f3()') def main(): ➑ print('running main()') print('registry ->', registry) f1() f2() f3() if __name__=='__main__': main() ➒
❶ registry 保存被 @register 装饰的函数引用。
❷ register 的参数是一个函数。
❸ 为了演示,显示被装饰的函数。
❹ 把 func 存入 registry。
❺ 返回 func:必须返回函数;这里返回的函数与经过参数传入的同样。
❻ f1 和 f2 被 @register 装饰。
❼ f3 没有装饰。
❽ main 显示 registry,而后调用 f1()、f2() 和 f3()。
❾ 只有把 registration.py 看成脚本运行时才调用 main()app
把 registration.py 看成脚本运行获得的输出以下:函数
$ python3 registration.py running register(<function f1 at 0x100631bf8>) running register(<function f2 at 0x100631c80>) running main() registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>] running f1() running f2() running f3()
经过执行脚本能够看出,在执行main()
函数以前,装饰器都已经被调用过二次了,装饰器的参数分别是被装饰的两个函数对象f1
f2
debug
若是导入 registration.py 模块(不做为脚本运行),输出以下:设计
>>> import registration running register(<function f1 at 0x10063b1e0>) running register(<function f2 at 0x10063b268>)
此时查看 registry 的值,获得的输出以下:code
>>> registration.registry [<function f1 at 0x10063b1e0>, <function f2 at 0x10063b268>]
函数装饰器在导入模块时当即执行,而被装饰的函数只在明确调用时运行。这突出了 Python 程序员所说的导入时和运行时之间的区别。
若是你了解全局变量与局部变量的区别,那就很容易明白下面的问题
>>> def f1(a): ... print(a) ... print(b) ... >>> f1(3) 3 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in f1 NameError: global name 'b' is not defined
错误提示给出了,变量b未定义。那若是这样呢,咱们事先给变量b定义好
>>> b = 6 >>> f1(3) 3 6
就不会出错
在来看下面这段代码,若是你对变量做用域不够清楚的话就会出错。
>>> b = 6 >>> def f2(a): ... print(a) ... print(b) ... b = 9 ... >>> f2(3) 3 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in f2 UnboundLocalError: local variable 'b' referenced before assignment
我刚开始看的时候,也是一脸懵逼,可是看到流畅的Python关于这个解释才明白。在函数体编译的时候,因为b=9
赋值语句是在函数f2
内部,因此python判断b是一个局部变量,可是在执行函数的print(b)
的时候,b变量尚未绑定任何值,因此出错。
想要解决这一问题,就要告诉python,b变量是一个全局变量,在打印的时候才不会出错。要使用 global 声明:
>>> b = 6 >>> def f3(a): ... global b ... print(a) ... print(b) ... b = 9 ... >>> f3(3) 3 6 >>> b 9 >>> f3(3) 3 9 >>> b = 30 >>> b 30
闭包指延伸了做用域的函数,其中包含函数定义体中引用、可是不在定义体中定义的非全局变量。函数是否是匿名的没有关系,关键是它能访问定义体以外定义的非全局变量。
class Averager(): def __init__(self): self.series = [] def __call__(self, new_value): self.series.append(new_value) total = sum(self.series) return total/len(self.series)
注:这里的call
特殊方法使得Averager
函数对象成为了一个能够调用的对象。若是还有疑问能够参考博主的另外一篇博文 一等函数
假若有个名为 avg 的函数,它的做用是计算不断增长的系列值的均值;例如,整个历史中某个商品的平均收盘价。天天都会增长新价格,所以平均值要考虑至目前为止全部的价格。
操纵台输入输出:Averager 的实例是可调用对象:
>>> avg = Averager() >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(12) 11.0
这样就实现了计算平均值。固然这样写没有问题,可是还有另外一种写法。
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager
调用make_averager
时,返回一个 averager
函数对象。每次调用averager
时,它会把参数添加到系列值中,而后计算当前平均值。
注:这里make_averager
扮演者一个高阶函数的角色,所谓高阶函数就是把其余函数对象做为参数传入自身的函数对象。
>>> avg = make_averager() >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(12) 11.0
注意,这两个示例有共通之处:调用 Averager() 或make_averager() 获得一个可调用对象 avg,它会更新历史值,而后计算当前均值。在第一个示例 ,avg 是 Averager 的实例;在第二个示例中是内部函数 averager。无论怎样,咱们都只需调用 avg(n),把 n放入系列值中,而后从新计算均值。
Averager 类的实例 avg 在哪里存储历史值很明显:self.series 实例属性。可是第二个示例中的 avg 函数在哪里寻找 series 呢?
注意,series 是 make_averager 函数的局部变量,由于那个函数的定义体中初始化了 series:series = []。但是,调用 avg(10)时,make_averager 函数已经返回了,而它的本地做用域也一去不复返了。
在 averager 函数中,series 是自由变量(free variable)。这是一个技术术语,指未在本地做用域中绑定的变量
在图中:averager 的闭包延伸到那个函数的做用域以外,包含自由变量 series 的绑定。
闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义做用域不可用了,可是仍能使用那些绑定。
注意,只有嵌套在其余函数中的函数才可能须要处理不在全局做用域中的外部变量。
在上面的例子你仔细看可能会以为别扭,不舒服,由于本来是想计算平均值,而又要用一个series
记录下列表,而后在列表中记录下全部的值,而且计算总和在求平均值,这样作也能够,只是不符合习惯,而且不高,要知道申请列表空间可比整型要浪费多了,正确的思路应该是作累加,并记录下数的个数,最后求平均值。
def make_averager(): count = 0 total = 0 def averager(new_value): count += 1 total += new_value return total / count return averager
可是会出现这样的结果
>>> avg = make_averager() >>> avg(10) Traceback (most recent call last): ... UnboundLocalError: local variable 'count' referenced before assignment >>>
这可能跟上面的局部变量问题有些类似,甚至错误类型都是相同的,UnboundLocalError: local variable 'count' referenced before assignment
,当 count 是数字或任何不可变类型时,count += 1 语句的做用其实与 count = count + 1 同样。所以,咱们在 averager 的定义体中为 count 赋值了,这会把 count 变成局部变量。total 变量也受这个问题影响。
上面的series
中没遇到这个问题,由于咱们没有给 series 赋值,咱们只是调用 series.append,并把它传给 sum 和 len。也就是说,咱们利用了列表是可变的对象这一事实。
因此对不可变类型来讲,进行+=
或者*=
操做的时候,实际上是又建立了一个相同的类型并赋值,而不是像列表那样进行追加,这一点也能够经过id()
查看。而在上面的例子中,count
和total
都做为不可变类型而在averager
函数内部的局部变量,而不是自由变量。为了解决这一问题,Python3引入nonlocal
声明。
它的做用是把变量标记为自由变量,即便在函数中为变量赋予新值了,也会变成自由变量。若是为 nonlocal 声明的变量赋予新值,闭包中保存的绑定会更新。所以上面问题的解决方案就成了
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count, total count += 1 total += new_value return total / count return averager
在早些时候 (Python Version < 2.4,2004年之前),为一个函数添加额外功能的写法是这样的。
def debug(func): def wrapper(): print "[DEBUG]: enter {}()".format(func.__name__) return func() return wrapper def say_hello(): print "hello!" say_hello = debug(say_hello) # 添加功能并保持原函数名不变
上面的debug函数其实已是一个装饰器了,它对原函数作了包装并返回了另一个函数,额外添加了一些功能。由于这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。
def debug(func): def wrapper(): print "[DEBUG]: enter {}()".format(func.__name__) return func() return wrapper @debug def say_hello(): print "hello!"
这是最简单的装饰器,可是有一个问题,若是被装饰的函数须要传入参数,那么这个装饰器就坏了。由于返回的函数并不能接受参数,你能够指定装饰器函数wrapper接受和原函数同样的参数,好比:
def debug(func): def wrapper(something): # 指定一毛同样的参数 print "[DEBUG]: enter {}()".format(func.__name__) return func(something) return wrapper # 返回包装过函数 @debug def say(something): print "hello {}!".format(something)
这样你就解决了一个问题,但又多了N个问题。由于函数有千千万,你只管你本身的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就能够用于任意目标函数了。
def debug(func): def wrapper(*args, **kwargs): # 指定宇宙无敌参数 print "[DEBUG]: enter {}()".format(func.__name__) print 'Prepare and say...', return func(*args, **kwargs) return wrapper # 返回 @debug def say(something): print "hello {}!".format(something)