python内嵌函数和闭包,闭包python 菜鸟教程
简介函数装饰器(不是设计模式中的装饰器模式)用于在源代码中“标记”函数,以某种方式增强它们的行为。这是一个强大的函数,但是如果你想掌握它,你必须理解闭包。
除了在decorators中有用之外,闭包还是回调异步编程和函数式编程的风格。
晶格的基础。
首先看一下装修工的基础知识。
装饰器基础知识装饰器是一个可调用的对象,它的参数是另一个函数(装饰函数)。安装
装饰者可以处理被装饰的函数,然后返回它,或者用另一个函数替换它。
或者一个函数可以调用一个对象。
@装饰
定义目标():
Print(running target())上面的代码与下面的写法具有相同的效果:
定义目标()
print(running target())
Target=decorate(target)上面两段代码执行后得到的目标不一定是原来的目标函数,而是decorate(target)返回的函数。
装饰者通常用另一个函数替换一个函数:
在[1]: def deco(func):
.def inner():
.print(running inner())
.return inner #deco返回内部函数对象
.
In [2]: @deco #用deco装饰目标
.定义目标():
.print(running target())
.
In [3]: target() #实际上将在内部运行
运行内部()
在[4]中:target #target是内部的引用
Out [4]: function _ _ main _ _。deco.locales.inner()综上所述,装饰器的一大特点就是可以用其他函数替换被装饰的函数。第二
其特点是当模块被加载时,装饰器立即执行。
Python执行decorators Decorators的一个关键特性是,它们会在被修饰函数定义之后立即运行。这通常是在导入时(即Python加载模块时)
#注册. py
# -*-编码:utf-8 -*
Registry=[] #保存由@register修饰的函数引用
Def寄存器(func): #参数func是一个函数
Print( running register(% s) % func)#打印修饰的函数
registry.append(func)#存放在注册表中。
Return func #必须返回一个函数,这里返回的是修饰后的函数。
#f1和f2被装饰
@注册
定义f1():
打印(运行f1())
@注册
定义f2():
打印(运行f2())
#f3不装修。
def f3():
打印(运行f3())
def main():
打印( running main())
打印(注册表-,注册表)
f1()
f2()
f3()
if __name__==__main__ :
Main()运行脚本,输出如下:
运行寄存器(0x0000015c807e730处的函数f1)#寄存器在主运行之前运行。
运行寄存器(0x0000015C8077E7B8的功能f2)
运行main()
注册表-[0x 0000015 c 8077 e 730处的函数f1,0x0000015C8077E7B8处的函数F2]
运行f1()
运行f2()
运行f3()如果导入registration.py模块,输出如下:
进口登记
运行寄存器(0x0000022BB52DE840处的函数f1)
运行register(0x 0000022 bb 52 de 8 c 8的函数F2)证明函数decorator在模块导入时立即执行,而修饰函数只有在显式调用时才运行。
使用decorator改进从集合导入命名元组的策略模式。
Customer=namedtuple(Customer , name fidelity )
类别行项目:
def __init__(自身,产品,数量,价格):
自我。_product=产品
自我。_quantity=数量
自我。_price=价格
定义总计(自身):
回归自我。_price * self。_数量
类:# context,即客户端代码
def __init__(自身,客户,购物车,促销=无):
自我。_customer=客户
自我。_cart=list(cart)
自我。_promotion=促销
定义总计(自身):
如果不是hasattr(self, __total ):
自我。_ _ total=sum(item . total()for item in self。_购物车)
回归自我。_ _总计
延期到期(自身):
折扣
如果自我。_promotion是无:
折扣=0
否则:
折扣=自我。_ promotion (self) #只需调用self.promotion()函数
return self.total() -折扣
def __repr__(self):
fmt=订单总额:{:2f}到期日:{:2f}
return fmt.format(self.total()、self.due())
Promos=[] #列表最初为空。
定义促销(promo_func):
Promos.append(promo_func) #添加到促销列表
Return promo_func#原封不动地返回
由@promotion装饰的@promotion#功能将被添加到促销列表中。
def fidelity_promo(订单):
为积分达到或超过1000分的客户提供5%的折扣
如果是订单,则返回order.total() * 0.05。_customer.fidelity=1000 else 0
@推广
def bulk_item_promo(订单):
单品20件以上,打九折
折扣=0
对于订单中的项目。_购物车:
如果项目。_quantity=20:
折扣=item.total() * .1
退货折扣
@推广
def large_order_promo(订单):
订单中有10个或10个以上不同项目时,提供7%的折扣
distinct_items={item。_订单中项目的产品。_cart}
如果len(distinct_items)=10:
退货单总数()* 0.07
返回0
def best_promo(订单):
选择最优惠的折扣
max(promos中的promo (order) for promo)与Python给出的使用一级函数实现设计模式的方案相比有几个优点:
不需要为推广函数使用特殊的名称@ promotionDecorator突出了修饰函数的作用,使得暂时禁用一个推广策略变得很容易:只需注释掉Decorator即可。促销折扣策略可以在其他模块定义,用@ promotionDecorator即可。但是,大多数装饰者会修改被装饰的功能。通常,它们定义了一个内部函数。
数字,然后返回它,替换被修饰的函数。几乎所有使用内部函数的代码都必须
只有关闭才能正常工作。为了理解闭包,我们必须先回过头来理解Python。
中的变量范围。
变量作用域规则读取局部变量和全局变量的函数:
在[1]中:定义f1(a):
.打印(a)
.打印(b)
.
在[2]中:f1(3)
三
-
NameError Traceback(最近一次调用)
模块中的ipython-input-2-db0f80b394ed
- 1个f1(3)
f1(a)中的ipython-input-1-c1318c6d0711
1个定义f1(a):
2打印(一份)
- 3张照片(b)
四
错误:名字 b 不是如果你先给全局变量b赋值,然后调用f,就不会出错:
在[3]中:b=6
在[5]: f1(3)看看下面例子中的f2函数。然而,前两行代码与前面的f1相同
给B赋值,然后打印它的值。然而,在分配任务之前,第二次打印失败了。
[8]: b=6
在[9]中:定义f2(a):
.打印(a)
.打印(b)
.b=9
.
在[10]: f2(3)
三
-
UnboundLocalError回溯(最后一次调用)
模块中的ipython-input-10-ddde86392cb4
- 1个f2(3)
f2(a)中的ipython-input-9-2304a03d7bfd
1 def f2(a):
2打印(一份)
- 3张照片(b)
4 b=9
五
Boundlocalerror:局部变量 b 从上面的错误信息来看,它是在print(b)处报告的。你惊讶吗?我还以为会打印6,因为有一个全局变量b。
但事实是,Python在编译函数的定义体时,判断B是局部变量,因为在
它在函数中被赋值。生成的字节码证实了这个判断,Python将尝试从中学习
环境收购B.当稍后调用f2(3)时,f2的定义将获取并打印本地更改。
a的值,但是在试图获取局部变量B的值时,发现B没有绑定值。
这不是一个缺陷,而是一种设计选择:Python不要求声明变量,但是假设在函数中
定义体中分配的变量是局部变量。
如果您希望解释器在函数中赋值时将B视为全局变量,请使用全局声明:
在[11]中:b=6
在[12]中:定义f3(a):
.全局b #使用全局声明。
.打印(a)
.打印(b)
.b=9
.
在[13]中:f3(3)
三
六
在[14]: b
Out[14]: 9
在[16]: f3(3)
三
九
在[17]中:b=30
在[18]: b
Out[18]: 30 Closure Closure引用一个具有扩展作用域的函数,它在函数定义体中包含引用,但是
没有在定义体中定义的非全局变量。
如果有一个函数叫avg,它的作用就是计算递增的一系列值的平均值。
价值;例如,历史上一种商品的平均收盘价。每天都会增加新的价格,
所以平均值要考虑到目前为止的所有价格。
起初,avg是这样使用的:
平均值(10)
10.0
平均值(11)
10.5
平均值(12)
1.0从哪里来,它的历史价值保存在哪里?
我们通过函数公式来实现它:
# -*-编码:utf-8 -*
def make_averager():
Series=[]#历史值
定义平均器(新值):
series.append(新值)
总计=总和(系列)
退货总计/贷款(系列)
回报平均器
if __name__==__main__ :
avg=make_averager()
打印(平均值(10))
打印(平均值(11))
打印输出(avg(12))为:
10.0
10.5
1.0注意series是make_averager函数的局部变量,series: series=[]在函数的定义权重中初始化。但是调用avg(10)的时候,make_averager函数已经返回了,它的局部作用域一去不复返了。
在averager函数中,级数是自由变量(free variable)。这是一个
一个技术术语,指不在局部范围内的变量。
averager的闭包超出了那个函数的范围,包含了自由变量序列的绑定。
打印(平均__代码_ _。co_varnames) #(新值,合计)
打印(平均__代码_ _。co_freevars) #(系列,)
打印(平均_ _ closure _ _)#(0x 00000261 e22d 8498处的单元格:0x00000261E2371308处的列表对象,)
打印(平均__closure__ [0]。cell _ contents) # [10,11,12],即值AVG中的每个元素。系列保存的_ _ closure _ _对应于avg中的一个名称。__代码_ _。co_freevars。这些元素是具有cell_contents属性的单元格对象,该属性保存实际值。
闭包是一个函数,它保留了定义函数时存在的自由变量的绑定。
当以这种方式调用函数时,尽管定义范围不可用,但仍然可以使用那些绑定。
只有嵌套在其他函数中的函数可能需要处理不在全局范围内的外部变量。
在非局部声明之前实现make_averager函数的方法效率很低。我们将所有值存储在历史序列中,然后在每次调用averager时使用sum来求和。更好的方法是只存储当前的总值和元素个数,然后用这两个数计算平均值。
def make_averager():
计数=0
总计=0
定义平均器(新值):
计数=1
总计=新值
返回总数/计数
当我键入完这些代码时,编译器被提示报告一个错误。我试着运行它,看看是什么错误:
BoundLocalError:局部变量“count”当count为数字或任何不可变类型时,count=1语句
其实和count=count 1是一样的。因此,我们处于平均水平
Count是在定义中赋值的,这会把count变成一个局部变量。变更总计
数量也是如此。
我们在前面的例子中没有遇到这个问题,因为我们没有给series赋值,只是调用了append方法。也就是说,利用了列表是可变的这一事实。
也就是说,rebinding(赋值)会隐式地创建一个局部变量,它不是一个自由变量,所以它不会保存在闭包里。
Python引入了非局部声明。它的作用是将变量标记为自由变量。即使一个新的值被赋予一个函数中的变量,它也会变成一个自由变量。
def make_averager():
计数=0
总计=0
定义平均器(新值):
非局部count,total #的用法和之前的全局类似。
计数=1
总计=新值
返回总数/计数
Return实现一个简单的装饰器,一个简单的装饰器,输出函数的运行时间:
# -*-编码:utf-8 -*
导入时间
定义时钟(函数):
Def clocked(*args): #接受任何定位参数
t0=time.perf_counter()
result=func(*args)#clocked的闭包包含自由变量func。
elapsed=time.perf_counter() - t0
name=func。__姓名_ _
Arg _ str=,。join (repr (arg) for arg in args) #参数
print([% 0.8 fs]% s(% s)-% r %(elapsed,name,arg_str,result))
回送结果
Return clocked#返回内部函数,替换修饰函数。
@时钟
定义暂停(秒):
时间.睡眠(秒)
@时钟
定义阶乘(n):
如果n ^ 2否则返回1n *阶乘(n - 1)
if __name__==__main__ :
打印( * * 40,呼叫暂停(. 123))
贪睡(. 123)
print(* * 40,调用阶乘(6))
打印( 6!=,factorial(6))运行输出为:
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *呼叫暂停(. 123)
[0.12056875s]暂停(0.123) -无
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *调用阶乘(6)
[0.00000079s]阶乘(1) - 1
[0.00002686s]阶乘(2) - 2
[0.00003832s]阶乘(3) - 6
[0.00004899s]阶乘(4) - 24
[0.00005926s]阶乘(5) - 120
[0.00007111s]阶乘(6) - 720
6!=720以下代码:
@时钟
定义阶乘(n):
1如果n2else n * factorial (n-1)等于:
定义阶乘(n):
如果n ^ 2否则返回1n *阶乘(n-1)
Factorial=clock(Factorial)Factorial会作为func参数传递给clock,然后,clock函数会返回clocked函数,python会在后面给Factorial赋值clocked。您可以查看factorial的__name__属性:
打印(阶乘。_ _ name _ _)#计时
现在factorial保存了对时钟函数的引用。
每次调用factorial(n)时,都会执行clocked(n)。粗略计时
做了以下事情。
记录初始时间t0。调用原始阶乘函数并保存结果。计算经过的时间。格式化数据,然后打印出来。返回步骤2中保存的结果。这是装饰者的典型行为:用一个新的函数替换被装饰的函数,两者都接受相同的函数。
参数,并且(通常)返回被修饰的函数应该返回的值,同时执行一些
附加操作。
上面实现的时钟装饰器有几个缺点:它不支持关键字参数,而
并介绍了修饰函数的__name__和__doc__属性。
在下面的例子中,functools.wraps decorator用于将相关属性从func复制到clocked。此外,这个新版本可以正确处理关键字参数。
导入功能工具
定义时钟(函数):
@functools.wraps(func)
Def clocked(*args,**kwargs): #接受任何定位参数和关键字参数。
t0=time.time()
result=func (* args,* * kwargs) # clocked包含自由变量func。
elapsed=time.time() - t0
name=func。__姓名_ _
arg_lst=[]
如果参数:
arg_lst.append(,)。join(repr(arg) for arg in args))
如果夸尔斯:
pairs=[%s=%r % (k,w) for k,w in sorted(kwargs.items())]
arg_lst.append(,)。连接(成对))
arg_str=,。join(arg_lst)
print([% 0.8 fs]% s(% s)-% r %(elapsed,name,arg_str,result))
回送结果
Return clocked#返回内部函数,替换修饰函数functools.wraps只是标准库中可用的装饰器之一。
标准库中的装饰器标准库中最值得注意的两个装饰器是lru_cache和新的Single Dispatch(Python 3.4中的新特性)。两个装饰器都是在functools模块中定义的。
使用functools.lru_cache作为备忘录。functools.lru_cache是一个非常实用的装饰器,实现了备忘录。
(备忘录)功能。保存耗时函数的结果。
起来,避免传入相同参数时重复计算。LRU的三个字母是“最少”
“最近使用”的缩写表示缓存不会无限增长,并且在一段时间内不会被使用。
条目将被丢弃。
生成第n个斐波那契数的慢速递归函数适合使用lru_cache:
from clock_deco导入时钟
@时钟
定义斐波那契(n):
如果n 2:
返回
返回斐波纳契(n-2)斐波纳契(n-1)
if __name__==__main__ :
Print(fibonacci(6))输出:
[0.00000119s]斐波那契(0) - 0
[0.00000079s]斐波那契(1) - 1
[0.00008020s]斐波那契(2) - 1
[0.00000040s]斐波那契(1) - 1
[0.00000040s]斐波那契(0) - 0
[0.00000040s]斐波那契(1) - 1
[0.00002252s]斐波那契(2) - 1
[0.00004425s]斐波那契(3) - 2
[0.00014736s]斐波那契(4) - 3
[0.00000040s]斐波那契(1) - 1
[0.00000040s]斐波那契(0) - 0
[0.00000079s]斐波那契(1) - 1
[0.00002094s]斐波那契(2) - 1
[0.00004227s]斐波那契(3) - 2
[0.00000040s]斐波那契(0) - 0
[0.00000040s]斐波那契(1) - 1
[0.00002094s]斐波那契(2) - 1
[0.00000040s]斐波那契(1) - 1
[0.00000040s]斐波那契(0) - 0
[0.00000079s]斐波那契(1) - 1
[0.00002291s]斐波那契(2) - 1
[0.00004267s]斐波那契(3) - 2
[0.00008889s]斐波那契(4) - 3
[0.00015289s]斐波那契(5) - 5
[0.00032118s]斐波那契(6) - 8
8时间的浪费很明显:斐波那契(1)调用8次,斐波那契(2)调用5次.但是,如果您添加两行代码并使用lru_cache,性能将会显著提高:
from clock_deco导入时钟
导入功能工具
@functools.lru_cache() #像常规函数一样调用,因为它可以接收参数。
@时钟
定义斐波那契(n):
如果n 2:
返回
返回斐波纳契(n-2)斐波纳契(n-1)
if __name__==__main__ :
Print(fibonacci(6))输出:
[0.00000040s]斐波那契(0) - 0
[0.00000040s]斐波那契(1) - 1
[0.00008573s]斐波那契(2) - 1
[0.00000079s]斐波那契(3) - 2
[0.00010509s]斐波那契(4) - 3
[0.00000079s]斐波那契(5) - 5
[0.00012168s]斐波那契(6) - 8
每个值8只调用一次,执行时间优化了一半以上。
Lru_cache可以用两个可选参数进行配置。它的签名是:
func tools . LRU _ cache(maxsize=128,typed=False)
Maxsize知道缓存了多少调用结果。如果将类型化参数设置为True,则不同参数类型得到的结果是分开保存的,即区分通常认为相等的浮点数和整数参数(如1和1.0)。Single dispatch通用函数Python 3.4新的functools . single dispatch decorator可以把整个
该方案被拆分成多个模块,甚至可以为您无法修改的类提供特殊功能。制造
用@ singledDispatch修饰的普通函数会变成泛型函数:根据第一个参数的类型,以不同方式执行相同操作的一组函数。
Singledispatch创建一个自定义的htmlize.register装饰器,它将多个函数绑定在一起,形成一个通用函数:
从functools导入单个调度
从集合导入abc
导入号码
导入html
@ single dispatch # @ single dispatch标签处理对象类型的基本函数。
def htmlize(obj):
content=html.escape(repr(obj))
返回 pre {} /pre 。格式(内容)
@htmlize.register(str) #每个专用函数都用@ base _ function.register (type)修饰
Def _(text): #专用函数的名称无关紧要
content=html.escape(text)。替换( \n , br \n )
返回“p {0} /p”。格式(内容)
@ html ize . Register(numbers . integral)#为每个需要特殊处理的类型注册一个函数。数字。Integral是int的虚超类。
def _(n):
返回“pre {0} (0x{0:x}) /pre”。格式(n)
@htmlize.register(tuple) #可以堆叠多个寄存器装饰器,让同一个函数支持不同的类型。
@htmlize.register(abc。可变序列)
def _(seq):
inner= /li \n li 。join(为seq中的项目htmlize(item ))
Return ul \n li inner /li \n /ul 只要有可能,注册的专用函数应该处理抽象基类(例如
数字。积分和abc。MutableSequence),不处理具体的
实现(比如int和list)。通过这种方式,代码支持更广泛的兼容性类型。
装饰器是函数,所以它们可以组合使用(也就是说,它们可以应用于已经被装饰的函数)
重叠装饰器将两个装饰器@d1和@d2依次应用于f函数,等价于f=d1(d2(f))。
@d1
@d2
def f():
Print(f )相当于:
def f():
打印( f )
F=d1(d2(f)) #注意顺序参数装饰器是如何让装饰器接受其他参数的?答案是:创建一个装饰者。
工厂函数,向它传递参数,返回一个装饰器,然后应用它进行装饰。
在功能上。
我们以registration.py模块的删节版为例:
注册表=[]
def寄存器(func):
print(运行寄存器(%s) % func)
注册表.追加(func)
返回功能
@注册
定义f1():
打印(运行f1())
打印( running main())
打印(注册表-,注册表)
F1()一个参数化注册装饰器。为了启用或禁用register执行的函数注册功能,我们为其提供了
可选的活动参数。当设置为False时,不注册修饰函数:
# -*-编码:utf-8 -*
Registry=set() #set对象,这样可以更快地添加和删除函数。
Def寄存器(active=True): #接收一个可选的关键字参数
Def decorate(func): #decorate这个内部函数才是真正的decorator,参数是函数。
print( running register(active=% s)-decoration(% s)%(active,func))
Active: # register func仅当Active参数的值(从闭包获得)为真时。
registry.add(func)#存放在注册表中
否则:
Registry.discard(func) #实现反向注册(注销)功能。
Return func #decorate是一个装饰器,必须返回一个函数。
Return decorate # register是一个装饰工厂函数,它返回decorator。
# @注册工厂函数必须作为传入了所需参数的函数来调用。
@register(active=False)
定义f1():
打印(运行f1())
#即使不传递参数,也作为函数调用
@register()
定义f2():
打印(运行f2())
def f3():
Print(running f3())从概念上讲,没有安装这个新的注册函数
Decorator,而是decorator factory函数。调用它将返回真正的装饰者,也就是应用程序。
目标函数上的装饰。
如果您导入上述模块,您将得到以下结果:
进口登记
运行寄存器(active=False)-装饰(0x000001CEA7FBE8C8处的函数f1)
运行寄存器(active=True)-装饰(0x000001CEA7FBE950处的函数f2)
注册
{ function F2 at0x 0000026 c7b 43 e 950 }如果不使用@语法,应该像普通函数一样使用register如果您想关闭
添加到注册表,那么装饰f函数的语法是register()(f);不
如果要添加(或删除)它,语法是register(active=False)(f)。
下面演示了如何向注册表添加函数以及如何从中删除函数:
从注册导入*
运行寄存器(active=False)-装饰(0x0000028C925CE8C8处的函数f1)
运行寄存器(active=True)-装饰(0x0000028C925CE950处的函数f2)
Registry #导入模块时,注册表中只有f2。
{0x 0000028 c 925 ce 950处的函数F2 }
register()(f3) #register()表达式返回decorate,然后将其应用于f3。
运行寄存器(active=True)-装饰(0x0000028C925CE840处的功能f3)
0x0000028C925CE840处的功能f3
注册表#在注册表中注册f3。
{0x 0000028 c 925 ce 840处的函数f3,0x0000028C925CE950处的函数F2 }
寄存器(active=False)(f2)#删除f2
运行寄存器(active=False)-装饰(0x0000028C925CE950处的函数f2)
0x0000028C925CE950处的功能f2
Registry#确认f2已被删除。
{0x 0000028 c 925 ce 840处的功能F3 }
原创作品来自愤怒的可乐,的博主,转载请联系作者取得转载授权,否则将追究法律责任。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。