关于我
一个有思想的程序猿, 终身学习实践者, 目前在一个创业团队任 team lead, 技术栈涉及 Android,Python,Java 和 Go, 这个也是我们团队的主要技术栈.
GitHub:https://github.com/hylinux1024
微信公众号: 终身开发者(angrycode)
谈谈装饰器 (Decorator) 的实现原理
熟悉 Java 编程的程序猿对装饰器模式一定不陌生, 它是能够动态的给一个类添加新的行为的一种设计模式. 相对于通过继承的方式使用装饰器会更加灵活.
在 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 引用
- https://docs.python.org/3/reference/compound_stmts.html#function-definitions
- https://wiki.python.org/moin/PythonDecorators
- https://docs.python.org/3/tutorial/controlflow.html#defining-functions
- https://zh.wikipedia.org/wiki/闭包_(计算机科学)
来源: https://www.cnblogs.com/angrycode/p/11393139.html