Python - 学装饰器之前,有几个点要理解
要不这样吧,如果编程语言里有个地方你弄不明白,而正好又有个人用了这个功能,那就开枪把他打死,这比学习新特性要容易些,然后过不了多久,那些活下来的程序员就会开始用0.9.6版本的Python,而且他们只需要使用这个版本中易于理解的小部分就好了(眨眼)。 - Tim Peters
首先推荐一波和朋友一起弄的壁纸下载,爬取了各大网站的壁纸,总有你喜欢的类型。http://wp.d2collection.com/
众所周知,Python里装饰器是一个很重要并且很牛X的功能,他可以在不改变原函数的功能和结构的基础上增加新功能。
但是想要理解装饰器还是有很多知识点的:
- 导入时、运行时
- 闭包与变量的作用域
- nonlocal
一般我们的装饰器都是在另外的一个文件里写的,类似xxx_deco。
当我们在别的文件中引入进来并且在自己的函数上定义时,装饰器就立即运行了。(当然不是说代码刚写就运行了+_+)
用《流畅的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()
运行结果如下:
running register(<function f1 at 0x10373d730>)
running register(<function f2 at 0x10373d7b8>)
running main()
registry -> [<function f1 at 0x10373d730>, <function f2 at 0x10373d7b8>]
running f1()
running f2()
running f3()
从结果我们可以证实刚才的话,在装饰后,就已经运行了。
下面我们来讨论变量的问题,众所周知,一个函数内部的变量在函数执行结束后就被销毁了。那么我们看一个小例子
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
return averager
这是一个利用闭包实现的求平均数的方法,导入运行结果如下
In[2]: from average import make_averager
In[3]: avg = make_averager()
In[4]: avg(10)
Out[4]: 10.0
In[5]: avg(11)
Out[5]: 10.5
In[6]: avg(12)
Out[6]: 11.0
这里爱思考的盆友会看出来,每次值都被记录了下来,我当时看到这里的时候在想,这不是坑爹么!谁说局部变量执行完就销毁的(╯‵□′)╯︵┻━┻
跑回去看了一下闭包的解释::闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
闭包延伸了变量的作用域,还起了个名字叫自由变量。所以上面的series就是一个自由变量。自由变量是指在本地作用域中绑定的变量,所以这个series没有被释放掉并且一直可以用。
那么这个时候再来看,如果我想统计有多少个数字呢?代码增加如下
def make_averager():
series = []
count = 0
def averager(new_value):
count += 1
series.append(new_value)
total = sum(series)
return total / len(series)
return averager
运行如下:
In[2]: from average import make_averager
In[3]: avg = make_averager()
In[4]: avg(10)
Traceback (most recent call last):
File "/Users/WangLu/.pyenv/versions/3.5.2/lib/python3.5/site-packages/IPython/core/interactiveshell.py", line 2862, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-4-2b3d43cb065d>", line 1, in <module>
avg(10)
File "/Users/WangLu/Study/FluentPython/average.py", line 6, in averager
count += 1
UnboundLocalError: local variable 'count' referenced before assignment
报错了,在定义变量之前应用了变量。这跟刚才说的不一致。
在Python中,数字、元组等不可变类型是只读的,想要重新赋值就要重新创建变量,在刚才的例子中,如果重新创建变量的话那就不是自由变量了,没有自由变量的闭包还是闭包么?所以在Python3中引入了nonlocal声明,被nonlocal声明的变量为自由变量,闭包中的数据就会更新。
所以,代码改写如下:
def make_averager():
series = []
count = 0
def averager(new_value):
nonlocal count
count += 1
series.append(new_value)
total = sum(series)
return total / len(series)
return averager
如果是Python2的话,就需要把变量设置为可变的如list等
至此,我们就可以来实现装饰器了,在每次调用的时候输出 哈哈 + 方法名 + 结果
def haha_deco(func):
def haha(*args):
result = func(*args)
print('哈哈 -> {},结果为:{}'.format(func.__name__, result))
return result
return haha
调用结果:
In[2]: from haha_deco import haha_deco
In[3]: @haha_deco
...: def demo(n):
...: return 1 if n < 2 else n * demo(n-1)
...:
In[4]: demo(5)
哈哈 -> demo,结果为:1
哈哈 -> demo,结果为:2
哈哈 -> demo,结果为:6
哈哈 -> demo,结果为:24
哈哈 -> demo,结果为:120
Out[4]: 120
我觉得想要理解装饰器,这几个点是应该会的。