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

我觉得想要理解装饰器,这几个点是应该会的。

最后

爱生活,爱小丽

    原文作者:爱小丽
    原文地址: https://www.jianshu.com/p/1369d610f8bb
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞