• 谈谈装饰器的实现原理


    关于我
    一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。
    Github:https://github.com/hylinux1024
    微信公众号:终身开发者(angrycode)

    谈谈装饰器(Decorator)的实现原理

    熟悉Java编程的程序猿对装饰器模式一定不陌生,它是能够动态的给一个类添加新的行为的一种设计模式。相对于通过继承的方式使用装饰器会更加灵活。

    图片来源wiki百科

    Python里面装饰器(Decorator)也是一个非常重要的概念。跟装饰器模式类似,它能够动态为一个函数、方法或者类添加新的行为,而不需要通过子类继承或直接修改函数的代码来获取新的行为能力,使用Decorator的方式会更加Pythonic

    要理解装饰器我们就要从函数说起。

    0x00 函数

    Python中函数是作为一级对象存在的(一切都是对象),它拥有自己的属性,函数名可以赋值给一个变量,也可以作为另一个函数的参数进行传递。

    1、定义函数
    def fib(n):
        """打印小于 n 的 fibonacci 数列"""
        a, b = 0, 1
        while a < n:
            print(a, end=' ')
            a, b = b, a + b
        print()
    
    def call_fib(func):
        """函数名作为函数的参数进行传递"""
        func(1000)
    
    if __name__ == '__main__':
        print(fib)  # <function fib at 0x103e66378>
        print(isinstance(fib, object))  # 函数是一级对象:True
        print(fib.__name__)  # 函数名称:fib
        print(fib.__code__.co_varnames)  # __code__属性是函数中的'代码对象',co_varnames是函数中的本地变量,以元组的方式展现:('n', 'a', 'b')
        print(fib.__doc__)  # 函数中的注释 
        print(fib.__globals__)  # 全局变量
        
        f = fib  # 函数赋值给一个变量f
        f(1000)  # 通过变量f对函数fib调用
        
        call_fib(fib) # 函数名作为参数
    
    2、嵌套函数

    在定义函数时,在函数内部定义一个函数。

    def outer_func():
        # 在函数内部定义一个函数
        def inner_func():
            print('inner func')
    
        inner_func()
        print('outer func')
    

    嵌套函数对我们理解装饰器非常重要,也是闭包实现的基础,这里先引出了本地变量和全局变量的概念,后文会详细说明闭包的概念。

    3、全局变量(globals)和本地变量(locals)

    根据作用域的不同,变量可以分成全局变量和本地变量,这其实是相对的概念。例如在下面的模块中gvar是一个全局变量,而在函数outer_func()定义的是本地变量。

    gvar = 'global var' # 全局变量
    
    
    def outer_func():
        gvar = 'outer var' # outer_func 本地变量
    
        # 在函数内部定义一个函数
        def inner_func():
            gvar = 'inner var' # inner_func 本地变量
            print('inner: {}'.format(gvar))
    
        inner_func()
        print('outer: {}'.format(gvar))
    
    outer_func()
    print('gvar in global : {}'.format(gvar))
    # 输出结果
    # inner: inner var
    # outer: outer var
    # gvar in global : global var
    
    

    在函数外定义了全局变量gvar,同时在outer_func()函数中也定义了gvar,而这个是本地变量
    从示例代码中可以看到,outer_func()并没有改变全局变量的值。

    在函数中定义的变量都存储在本地符号表(local symbol table)里,同样inner_func()中的gvar也存在它自己的本地符号表中,而全局变量gvar是则存储在全局符号表(global symbol table)。
    变量的查找路是:首先从本地符号表中查找,然后是外部函数(在嵌套函数中)的本地符号表中查找,再到全局符号表,最后是内置符号表

    graph TD
    A[本地符号表]-->B[外部函数的本地符号表]
    B[函数外的本地符号表]-->C[全局符号表]
    C[全局符号表]-->D[内置符号表]
    

    如果把上面代码中的inner_func()中的gvar = 'inner var'注释掉,那么输出的结果将是

    # inner: outer gvar # inner_func中引用的gvar变量是outer_func中定义的
    # outer: outer gvar
    # gvar in global : global var
    

    变量查找逻辑可以简单理解为:就近查找
    如果在以上路径中都没有找到,Python解析器将抛出NameError: name 'gvar' is not defined

    若在函数中要使用全局变量,那么就需要用到global关键字。
    对上文的代码修改如下

    gvar = 'global var'
    
    
    def outer_func():
        global gvar  # 声明gvar是全局变量
        gvar = 'outer gvar'
    
        # 在函数内部定义一个函数
        def inner_func():
            gvar = 'inner gvar'  # 这个依然是本地变量
            print('inner: {}'.format(gvar))
    
        inner_func()
        print('outer: {}'.format(gvar))
    
    outer_func()
    print('gvar in global : {}'.format(gvar))
    
    # 输出结果
    # inner: inner gvar
    # outer: outer gvar
    # gvar in global : outer gvar
    

    除了global还有一个nonlocal的关键字,它的作用是在函数中使用外部函数的变量定义(注意:不能是全局变量)

    gvar = 'global var' # 全局变量
    
    
    def outer_func():
        gvar = 'outer gvar' # 本地变量
        # 在函数内部定义一个函数
        def inner_func():
            nonlocal gvar  # nonlocal的含义是让gvar使用外部函数的变量,
            # 如果外部函数没有定义该变量,那么运行时将抛出SyntaxError: no binding for nonlocal 'gvar' found
            gvar = 'inner gvar'  # 这个依然是本地变量
            print('inner: {}'.format(gvar))
    
        inner_func()
        print('outer: {}'.format(gvar))
    
    # 输出结果
    # inner: inner gvar
    # outer: inner gvar
    # gvar in global : global var
    

    inner_func()中使用nonlocal关键字声明的gvar必须在外部函数(即outer_func()函数)定义,否则将抛出SyntaxError: no binding for nonlocal 'gvar' found

    0x01 什么是闭包

    首先结合前文的嵌套函数定义的例子,修改一下代码,返回内部函数的对象。

    # 普通的嵌套函数
    def outer_func():
        # 在函数内部定义一个函数
        def inner_func():
            print('inner func')
    
        inner_func()
        print('outer func')
    
    
    # 闭包
    def closure_func():
        local_var = 100
    
        def inner_func():
            print('inner func call : {}'.format(local_var))
    
        return inner_func # 这里将形成一个闭包
    
    f = closure_func()
    print(f)
    print(f.__closure__)
    print(outer_func)
    print(outer_func.__closure__)
    
    
    # 输出结果
    # <function closure_func.<locals>.inner_func at 0x104f8a8c8> 
    # (<cell at 0x104f6fa68: int object at 0x104d23910>,)
    # <function outer_func at 0x1070ea268>
    # None # 普通函数的__closure__属性为空
    

    可以看出变量f就是闭包,它是一个函数对象,这个函数可以持有本地变量local_var,而这个本地变量可以脱离定义它的作用域而存在

    现在来维基百科关于闭包的定义

    在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。--引用自维基百科

    0x02 实现装饰器

    有了前面的铺垫,理解装饰器就非常简单啦。
    一个函数返回另外一个函数,通常会使用@wrapper的语法形式,而装饰器其实就是一种语法糖(syntactic sugar)。
    我们还是看代码

    # 定义一个logger函数,在函数执行前打印log信息
    def logger(func):
        def log_func(*args):
            print('Running "{}" with arguments {}'.format(func.__name__, args))
            return func(*args)
    
        return log_func # 形成闭包
    
    # 定义加法函数
    def add(x, y):
        return x + y
    
    # 以下两种方式的使用是等价的,当然使用@logger更加Pythonic
    @logger
    def add(x, y):
        return x + y
    
    
    # add = logger(add)
    
    print(add(1,4))
    
    # 输出结果
    # Running "add" with arguments (1, 4)
    # 5
    

    这样的通过自定义装饰器,我们就可以动态地给函数添加新的功能。
    除了自定义的装饰器,还有常见的如classmethod()staticmethod()内置的装饰器。

    0x03 总结

    本文重点说明函数和嵌套函数的定义,还说明了全局变量和本地变量的作用域,在Python中变量索引的路径是就近查找,同时引出闭包是一个持有自由变量的函数对象的概念,而通过闭包可以实现装饰器,在使用使用装饰器时,可以使用@wrapper形式的语法糖。

    0x04 引用

    1. https://docs.python.org/3/reference/compound_stmts.html#function-definitions
    2. https://wiki.python.org/moin/PythonDecorators
    3. https://docs.python.org/3/tutorial/controlflow.html#defining-functions
    4. https://zh.wikipedia.org/wiki/闭包_(计算机科学)
  • 相关阅读:
    使用Leangoo玩转故事地图
    用Leangoo做敏捷需求管理
    LEANGOO成员
    LEANGOO卡片
    给WebAPI的REST接口添加测试页面(三)
    使用Win2D在UWP程序中2D绘图(二)
    Visual Studio 2015的“转到定义”和“查看定义”出错的Bug
    使用Win2D在UWP程序中2D绘图(一)
    Windows 10 UWP程序标题栏设置
    .NET 4.6的RyuJIT尾递归优化的Bug
  • 原文地址:https://www.cnblogs.com/angrycode/p/11393139.html
Copyright © 2020-2023  润新知