• 《流畅的Python》Data Structures--第7章 colsure and decorator


    Function Decorators and Closures

    装饰器是用于增强函数的行为,理解它,就必须先理解闭包。

    Python3引入关键字nonlocal,如果要理解闭包,就必须了解它的所有方面,包括nonlocal的使用。

    闭包在装饰器中有用,另一个用处是回调方式的异步编程和函数方式编程的风格的基础。

    首先是decorator的基本知识。

    • 如何计算decorator syntax
    • 判断变量是否是local
    • closure的存在和工作原理
    • nonlocal能解决什么?

    通州 

    decorator

    本质是一个语法糖。它的参数是另外的函数。因此能把被装饰的函数替换成其他函数。

    另外,在加载模块时,decorator会立即执行。(包括作为脚本输出)。而被装饰的函数,只有在明确调用时才立刻执行。

    真实环境下的decorator,一般会定义一个内部函数,在这个内部函数处理传入的函数,最后返回的也是内部函数。

    因此,要理解closure和 变量作用域

    Variable Scope Rules 

    例子:

    >>> 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

    错误❌的翻译:局部变量b,在被分配之前,就被引用了。

    可以通过模块dis反编译模块,查看它的内部过程:

    >>> import dis
    >>> dis.dis(f2)
      2           0 LOAD_GLOBAL              0 (print)
                  2 LOAD_FAST                0 (a)
                  4 CALL_FUNCTION            1
                  6 POP_TOP
    
      3           8 LOAD_GLOBAL              0 (print)
                 10 LOAD_FAST                1 (b)
                 12 CALL_FUNCTION            1
                 14 POP_TOP
    
      4          16 LOAD_CONST               1 (9)
                 18 STORE_FAST               1 (b)
                 20 LOAD_CONST               0 (None)
                 22 RETURN_VALUE

    LOAD_FAST(var_num)实际上是将指向局部对象co_varnames[var_num]的引用推入栈顶。

    >>> f2.__code__.co_varnames
    ('a', 'b')

    print(b)这行代码,要打印的b是局部变量,即f2内部定义的变量,但并没有声明赋值,就引用所以报告❌。

    分析:

    这是因为,在把源码转化为字节码时,b = 9决定了b是一个局部变量。因此上面的反编译代码显示b是一个局部变量。

    所以在实际调用函数f2时,执行到第2行代码print(b),Python会尝试从函数的本地环境获取b,并发现b没有绑定值,最后报告错误。

    我个人叫他作编译中的冲突。

    去掉b = 9这行代码,那么源码转化为字节码时,根据作用域规则,局部作用域没有b,那么就往上一层级去找b, 因此编译程序会判定b为全局变量:

      4           0 LOAD_GLOBAL              0 (print)
                  2 LOAD_FAST                0 (a)
                  4 CALL_FUNCTION            1
                  6 POP_TOP
    
      5           8 LOAD_GLOBAL              0 (print)
                 10 LOAD_GLOBAL              1 (b)
                 12 CALL_FUNCTION            1
                 14 POP_TOP
                 16 LOAD_CONST               0 (None)
                 18 RETURN_VALUE

    执行函数后,会从外部作用域获得b。

    如果就要保留b = 9这行代码,那么需要在函数体第一行声明:

    global b

    这会明确告诉编译程序,b是一个全局变量,再看dis.dis(f2):

      5           0 LOAD_GLOBAL              0 (print)
                  2 LOAD_FAST                0 (a)
                  4 CALL_FUNCTION            1
                  6 POP_TOP
    
      6           8 LOAD_GLOBAL              0 (print)
                 10 LOAD_GLOBAL              1 (b)
                 12 CALL_FUNCTION            1
                 14 POP_TOP
    
      7          16 LOAD_CONST               1 (11)
                 18 STORE_GLOBAL             1 (b)
                 20 LOAD_CONST               0 (None)
                 22 RETURN_VALUE

    而后b= 9则是对全局变量b的值的修改。

    根本原因:

    这是Python设计的一种选择: 默认假定函数体内被赋值的变量都是局部变量。

    所以运行上面的代码才会报告❌。


    Closures

    历史上,闭包概念和lambda匿名函数同时出现。在此之前很少有在函数内部嵌套函数定义的。

    所以,很多人把闭包和匿名函数的概念混淆了。

    实际上,一个closure是一个扩展了作用领域的函数。函数是否匿名和闭包没有关系,关键是函数可以访问它定义体外的nonglobal变量(当然也可以访问global变量)。

    ⚠️Ruby的函数定义则不同,它的设计原则是,封闭作用域。即和外面完全分开。

    >>> def make_averager():
    ...     series = []
    ...     def averager(new_value):
    ...         series.append(new_value)
    ...         total = sum(series)
    ...         return total/len(series)
    ...     return averager
    ...
    >>> avg = make_averager()
    >>> avg
    <function make_averager.<locals>.averager at 0x103a5cc10>

    make_averager函数内声明了一个局部变量series,当make_averager()执行并返回值后,它的局部作用领域就关闭了。

    因此,avg实际上从makd_averager复制了一个series变量,由于series并不是averager函数体内声明的变量,可以称series为自由变量free variable

    使用co_freevars属性可以看到自由变量。

    >>> avg.__code__.co_varnames
    ('new_value', 'total')
    >>> avg.__code__.co_freevars
    ('series',)

    series的绑定在avg的闭包__closure__属性内。以cell对象的类型储存。

    >>> avg.__closure__
    (<cell at 0x10395fcd0: list object at 0x103a652c0>,)
    >>> avg(10)
    10.0
    >>> avg(11)
    10.5
    >>> avg.__closure__[0]
    <cell at 0x10395fcd0: list object at 0x103a652c0>
    >>> avg.__closure__[0].cell_contents
    [10, 11]

    因此,闭包就是指扩展了作用领域的函数。一般特指被嵌套的函数。

     

    nonlocal声明

    python3的新增语法糖。为被嵌套的函数提供变量的支持。 

    def make_averager():
        count = 0
        total = 0
        def averager(new_value):
            nonlocal count, total
            count += 1
            total += new_value
            return total / count
        return averager

    本章后面的章节:

    • 简单的装饰器。
    • 标准库的装饰器:内置函数: property, classmethod, staticmethod.
    • functools.lru_cache
    • 叠放装饰器
    • 参数化装饰器 
  • 相关阅读:
    centos7 composer 安装使用
    linux下安装DBeaver社区版的方式
    批处理文件中使用xcopy命令复制文件到指定位置
    Civil 3d中求路线交点
    idea 启动Jfinal项目加载不出样式
    windows文件通过rsync自动同步到Centos或者Truenas
    docker中MySQL时区不对【解决方案】
    Docker容器中MySQL异常,远程无法链接排查记录
    Mac切换Java版本
    polkit pkexec 本地提权漏洞(CVE20214034)
  • 原文地址:https://www.cnblogs.com/chentianwei/p/12028118.html
Copyright © 2020-2023  润新知