Python - 学装饰器之前,有几个点要理解

作者: c37d344afd22 | 来源:发表于2017-07-15 00:29 被阅读261次

    要不这样吧,如果编程语言里有个地方你弄不明白,而正好又有个人用了这个功能,那就开枪把他打死,这比学习新特性要容易些,然后过不了多久,那些活下来的程序员就会开始用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
    

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


    最后

    爱生活,爱小丽

    相关文章

      网友评论

        本文标题:Python - 学装饰器之前,有几个点要理解

        本文链接:https://www.haomeiwen.com/subject/cicahxtx.html