• 函数装饰器和闭包(一)


    装饰器基础知识

    装饰器是可调用的对象,其参数是另一个函数(被装饰的函数),装饰器可能会处理被装饰的函数,然后将它返回,或者将其替换成另一个函数或可调用对象

    def deco(func):
        def inner():
            print("running innner()")
    
        return inner  # <1>
    
    
    @deco
    def target():  # <2>
        print("running target()")
    
    
    target()  # <3>
    

      

    运行结果:

    running innner()
    

      

    1. deco返回inner函数对象
    2. 使用deco装饰target函数
    3. 调用被装饰的target函数其实执行的是inner函数

    Python装饰器何时执行装饰器

    装饰器的一个关键特性是,它在被装饰的函数定义之后立即执行,通常在导入模块或文件时

    registry = []  # <1>
    
    
    def register(func):  # <2>
        print('running register(%s)' % func)
        registry.append(func)
        return func
    
    
    @register  # <3>
    def f1():
        print('running f1()')
    
    
    @register  # <4>
    def f2():
        print('running f2()')
    
    
    def f3():  # <5>
        print('running f3()')
    
    
    def main():  # <6>
        print('running main()')
        print('registry ->', registry)
        f1()
        f2()
        f3()
    
    
    if __name__ == '__main__':
        main()
    

      

    1. 定义一个名为registry的列表
    2. register函数接收一个函数对象,然后打印这个函数对象,再将函数对象添加到registry列表中,最后返回函数对象
    3. f1函数被register函数装饰
    4. f2函数被register函数装饰
    5. f3没有被register函数装饰
    6. 打印registry列表并依次执行f1、f2、f3三个函数

    如果不看装饰器,应该先打印running main(),再打印registry列表,之后依次执行三个函数,三个函数会打印其函数中的内容,但真实情况是如何呢?我们看一下运行结果:

    running register(<function f1 at 0x000000773FBF0730>)
    running register(<function f2 at 0x000000773FBF38C8>)
    running main()
    registry -> [<function f1 at 0x000000773FBF0730>, <function f2 at 0x000000773FBF38C8>]
    running f1()
    running f2()
    running f3()
    

      

    很遗憾,运行结果和我们一开始的设想不一样,我们看到程序是优先执行register函数,将f1和f2两个函数当做参数传入register函数中,然后register函数打印其传入的函数,之后才打印running main(),再来打印registry列表,这时候registry列表已经不是一个空列表了,在register函数中会把传入的函数参数加入到registry列表中,因为f1和f2被register函数装饰,所以列表有两个函数对象,之后才是依次打印3个函数中的内容

     因此我们可以知道,函数装饰器在导入模块时就立即执行,而被装饰的函数只有在明确调用时才运行。事实上,装饰器返回的函数,与通过参数传入所返回的结果相同,大部分装饰器会在内部定义一个函数,然后将其返回

    装饰器是一项很有用的技术,很多Python Web框架使用装饰器装饰一个函数,将函数添加到某中央注册处,当客户端发起一个HTTP请求时,通过URL查找相对应的函数,执行函数返回结果

    变量作用域规则

    我们先来看三个例子:

    >>> 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: name 'b' is not defined
    

    毋庸置疑,b变量没有被定义过,所以f1在打印b的时候报出为定义b的错误

    >>> b = 6
    >>> def f2(a):
    ...     print(a)
    ...     print(b)
    ...
    >>> f2(3)
    3
    6 

    我们在函数体之外定义了变量b,这里正常打印了b的值

    >>> def f3(a):
    ...     print(a)
    ...     print(b)
    ...     b = 9
    ...
    >>> f3(3)
    3
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 3, in f3
    UnboundLocalError: local variable 'b' referenced before assignment  

    这里输出了3,证明print(a)执行了,但在执行print(b)的时候报错了。是什么原因造成第三个例子无法正常打印b呢?原因是,Python在编译函数的定义体时,它判断b是局部变量,因为在函数中给b赋值了。在执行函数体的时候,Python会尝试从本地环境中获取变量b,但这个时候发现b还没有绑定值

    如果想让解释器把b当成全局变量,需要使用global声明:

    >>> def f4(a):
    ...     global b
    ...     print(a)
    ...     print(b)
    ...     b = 9
    ...
    >>> f4(3)
    3
    6
    

      

    dis模块会打印Python函数字节码,我们比较一下之前例子的字节码

    第一个例子:

    >>> from dis import dis
    >>> dis(f1)
      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_GLOBAL              1 (b)
                 12 CALL_FUNCTION            1
                 14 POP_TOP
                 16 LOAD_CONST               0 (None)
                 18 RETURN_VALUE
    

      

    • 第三行,获取全局变量名称print
    • 第四行,从本地环境中获取变量a
    • 第九行,从全局变量中获取b

    在第一个例子中,不管在函数体或是函数外都没定义b变量,所以获取b变量肯定会报错

    而第二个例子中,我们在函数外围定义了变量b,所以第二个函数字节码与第一个类似,但可以正常执行

    再来看第三个例子:

    >>> b = 6
    >>> def f3(a):
    ...     print(a)
    ...     print(b)
    ...     b = 9
    ...
    >>> from dis import dis
    >>> dis(f3)
      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
    

      

    第三个例子也是执行失败的例子,我们看第十五行,这里获取变量b并不是LOAD_GLOBAL 从全局环境中获取,而是LOAD_FAST从本地环境获取,这个时候b还未赋值,所以当解释器发现b没有绑定值,就会报出错误

  • 相关阅读:
    Android基础之项目结构分析
    串口调试,提示the given port name does not start with COM/com异常解决办法,,发现是打印机在搞怪
    C# 通过URL获取图片并显示在PictureBox上的方法
    学习资料集合
    C#语音朗读文本 — TTS的实现
    SQL SERVER 2008安装错误(is not a valid login or you do have permission)
    函数调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。
    SerialPort使用
    Javascript函数的几种写法
    JS验证图片格式和大小并预览
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/9308382.html
Copyright © 2020-2023  润新知