实例详解Python装饰器与闭包
作者:再见紫罗兰 发布时间:2021-05-27 10:12:21
闭包是Python装饰器的基础。要理解闭包,先要了解Python中的变量作用域规则。
变量作用域规则
首先,在函数中是能访问全局变量的:
>>> a = 'global var'
>>> def foo():
print(a)
>>> foo()
global var
然后,在一个嵌套函数中,内层函数能够访问在外层函数中定义的局部变量:
>>> def foo():
a = 'free var'
def bar():
print(a)
return bar
>>> foo()()
free var
闭包
上面的嵌套函数就是闭包。 闭包 是指延伸了作用域的函数,在其中能够访问未在函数定义体中定义的非全局变量。未在函数定义体中定义的非全局变量一般都是在嵌套函数中出现的。
上述示例中的变量a就是一个并未在函数bar中定义的非全局变量。对于bar来说,它有个专业名字,叫做 自由变量 。
自由变量的名称可以在字节码对象中查看:
>>> bar = foo()
>>> bar.__code__.co_freevars
('a',)
自由变量的值绑定在函数的__closure__属性中:
>>> bar.__closure__
(<cell at 0x000001CB2912DF48: str object at 0x000001CB291D3D70>,)
其中保存了对应自由变量的cell对象的序列,cell对象的cell_contents属性保存了变量的值:
>>> bar.__closure__[0].cell_contents
'free var'
这与JavaScript中闭包的行为是类似的,JavaScript中嵌套函数会将外层函数的活动对象添加到它的作用域链中。但与JavaScript不同的是,当Python函数中的全局变量或者自由变量是不可变对象(数字、字符串、元组等)时,是只能读取,无法更新的:
>>> a = 1
>>> def foo():
print(a)
a += 1
>>> foo()
UnboundLocalError: local variable 'a' referenced before assignment
>>> def foo():
a = 1
def bar():
print(a)
a += 1
return bar
>>> foo()()
UnboundLocalError: local variable 'a' referenced before assignment
两种情况下,都会报错。这并不是缺陷,而是Python的设计选择。Python不要求声明变量,但是会假定在函数定义体中赋值的变量是局部变量,以避免在不知情的情况下修改全局变量。
a += 1 与 a = a + 1 相同,编译函数的定义体时,会将a当做局部变量,不会当做自由变量保存。然后尝试获取a的值时,发现a并没有绑定值,于是报错。
解决这个问题的办法,一是将变量置于一些可变对象,如列表、字典中:
def foo():
ns = {}
ns['a'] = 1
def bar():
ns['a'] += 1
print (ns['a'])
return bar
另外的方法就是使用 global 或者 nonlocal 将变量声明为全局变量或者自由变量:
>>> def foo():
a = 1
def bar():
nonlocal a
a += 1
print(a)
return bar
>>> foo()()
2
当自由变量本身是可变对象时,是可以直接进行操作的:
def make_avg():
ls = []
def avg(x):
ls.append(x)
print(sum(ls)/len(ls))
return avg
装饰器
装饰器是可调用对象,参数一般是另一个函数。装饰器可以以某种方式增强被装饰函数的行为,然后返回被装饰的函数或者将其替换成一个新的函数。
一个最简单的不做任何额外行为的装饰器:
def decorate(func):
return func
decorate 函数就是一个最简单的装饰器,使用方法:
def target():
pass
target = decorate(target)
Python为装饰器的使用提供了语法糖,可以简便的写为:
@decorate
def target():
pass
导入时运行
装饰器一个很重要的特性是它是导入时(加载模块时)运行的:
def decorate(func):
print('running decorator when import')
return func
@decorate
def foo():
print('running foo')
pass
if __name__ == '__main__':
print('start foo')
foo()
结果:
running decorator when import start foo running foo
可以看到,装饰器是导入时运行的,而被装饰的函数是明确调用时运行的。
装饰器可以返回被装饰的函数本身,和运行时导入的特性结合起来,可以实现简单的注册器功能:
view_registry = []
def register(func):
view_registry.append(func)
return func
@register
def view1():
pass
@register
def view2():
pass
def main():
print(view_registry)
if __name__ == '__main__':
main()
返回新函数
上述装饰器的例子都返回了被装饰的原函数,但装饰器的典型行为还是返回一个新函数:把被装饰的函数替换成新函数,新函数接受与原函数相同的参数,并且返回原函数本该返回的值。写法类似于:
def deco(func):
def new_func(*args, **kwargs):
return func(*args, **kwargs)
return new_func
这种情况下装饰器就使用到了闭包。JavaScript中的防抖与节流函数就是这种典型的装饰器行为。新函数一般会使用外部装饰器函数中的变量当做自由变量,对函数作出某种增强行为。
举个例子,我们知道,当Python函数的参数是个可变对象时,会产生意料之外的行为:
def foo(x, y=[]):
y.append(x)
print(y)
foo(1)
foo(2)
foo(3)
输出:
[1] [1, 2] [1, 2, 3]
这是因为,函数的参数默认值保存在__defaults__属性中,指向了同一个列表:
>>> foo.__defaults__
([1, 2, 3],)
我们就可以用一个装饰器在函数执行前取出默认值做深复制,然后覆盖函数原先的参数默认值:
import copy
def fresh_defaults(func):
defaults = func.__defaults__
def deco(*args, **kwargs):
func.__defaults__ = copy.deepcopy(defaults)
return func(*args, **kwargs)
return deco
@fresh_defaults
def foo(x, y=[]):
y.append(x)
print(y)
foo(1)
foo(2)
foo(3)
输出:
[1] [2] [3]
接收参数的装饰器
装饰器除了可以接受函数作为参数外,还可以接受其他参数。使用方法是:创建一个装饰器工厂,接受参数,返回一个装饰器,再把它应用到被装饰的函数上,语法如下:
def deco_factory(*args, **kwargs):
def deco(func):
print(args)
return func
return deco
@deco_factory('factory')
def foo():
pass
在Web框架中,通常要将URL模式映射到生成响应的view函数,并将view函数注册到某些中央注册处。之前我们曾经实现过一个简单的注册装饰器,只是注册了view函数,却没有URL映射,是远远不够的。
在Flask中,注册view函数需要一个装饰器:
@app.route('/hello')
def hello():
return 'Hello, World'
原理就是使用了装饰器工厂,可以简单的模拟一下实现:
class App:
def __init__(self):
self.view_functions = {}
def route(self, rule):
def deco(view_func):
self.view_functions[rule] = view_func
return view_func
return deco
app = App()
@app.route('/')
def index():
pass
@app.route('/hello')
def hello():
pass
for rule, view in app.view_functions.items():
print(rule, ':', view.__name__)
输出:
/ : index /hello : hello
还可以使用装饰器工厂来确定view函数可以允许哪些HTTP请求方法:
def action(methods):
def deco(view):
view.allow_methods = [method.lower() for method in methods]
return view
return deco
@action(['GET', 'POST'])
def view(request):
if request.method.lower() in view.allow_methods:
...
重叠的装饰器
装饰器也是可以重叠使用的:
@d1
@d2
def foo():
pass
等同于:
foo = d1(d2(foo))
类装饰器
装饰器的参数也可以是一个类,也就是说,装饰器可以装饰类:
import types
def deco(cls):
for key, method in cls.__dict__.items():
if isinstance(method, types.FunctionType):
print(key, ':', method.__name__)
return cls
@deco
class Test:
def __init__(self):
pass
def foo(self):
pass
总结
以上所述是小编给大家介绍的实例详解Python装饰器与闭包,网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
来源:https://www.linuxidc.com/Linux/2019-07/159657.htm
猜你喜欢
- Python 多进程默认不能共享全局变量主进程与子进程是并发执行的,进程之间默认是不能共享全局变量的(子进程不能改变主进程中全局变量的值)。
- 简单介绍Mac下使用HomeBrew安装Python 3.*版本并设置为默认值1、首先查看Mac自带的python,可以看到是2.7.10的
- 1、更新包管理 apt-get install update.2、安装 pip3 :apt-get install python3-pip3
- 导读:SQL Server数据迁移的知识之前已经为大家介绍了很多,比如SQL Server数据库迁移方法,接下来就为大家详细介绍SQL Se
- 本文实例讲述了Python图形绘制操作之正弦曲线实现方法。分享给大家供大家参考,具体如下:要画正弦曲线先设定一下x的取值范围,从0到2π。要
- 这些列举的网站,站内还有大量的效果,希望大家多去找找,对于研究的朋友来说,更是很棒的源码参考。第5款的东东,很强很震撼...1、$fx()简
- python求定积分计算from sympy import *x = symbols('x')print(integrate
- 数据库计算机 databasecomputer 实现数据库的存储、管理和控制的一种专用计算机系统。它能十分快速而有效地完成各种数据库操作,并
- 上期回顾:亚马逊购物用户体验分析 (一)“查找内部”功能书是在亚马逊最常被购买的产品之一,所以毋庸置疑亚马逊的开发小组已经建立了一个关于“查
- 定义神经网络继承nn.Module类;初始化函数__init__:网络层设计;forward函数:模型运行逻辑。class NeuralNe
- 本文为大家分享了Python文本特征抽取与向量化的具体代码,供大家参考,具体内容如下假设我们刚看完诺兰的大片《星际穿越》,设想如何让机器来自
- window.onload=function(){ pd(11);} function pd(number) { if(number>
- Python可以使用open函数来实现文件的打开,关闭,读写操作;Python3中的open函数定义为:open(file, mode=
- 本文实例讲述了PHP字典树(Trie树)定义与实现方法。分享给大家供大家参考,具体如下:Trie树的概念(百度的解释):字典树又称单词查找树
- 注:代码用 jupyter notebook跑的,分割线线上为代码,分割线下为运行结果1.导入库生成缺失值通过pandas生成一个6行4列的
- 所谓线性最小二乘法,可以理解为是解方程的延续,区别在于,当未知量远小于方程数的时候,将得到一个无解的问题。最小二乘法的实质,是保证误差最小的
- 一.背景一道ctf题,通过破解2048游戏获得flag游戏的规则很简单,需要控制所有方块向同一个方向运动,两个相同数字方块撞在一起之后合并成
- 在python中利用numpy创建一个array, 然后我们想获取array的最大值,最小值。可以使用一下方法:一、创建数组这样就可以获得一
- 一、前言Python语言近年来人气爆棚。它广泛应用于数据科学,人工智能,以及网络安全问题中,由于代码可读性较强,学习效率较高,吸引了许多非科
- 在 Python 中也可以像 gcc/gdb 那样调试程序,只要在运行 Python 程序时引入 pdb 模块(假设要调试的程序名为 d.p