1.闭包与修饰器的关系
2.通过闭包实现修饰器
3.讲解闭包
4.nonlocal语句
1.闭包与修饰器的关系
我先不说闭包,因为我觉得通过修饰器来理解闭包会容易理解点。而修饰器是通过闭包实现的
2.通过闭包实现修饰器
修饰器是什么呢?修饰器的作用是:将函数A作为 修饰器 的参数传入修饰器,修饰器中专门会有一个函数B用来修饰传进来的函数,最后返回修饰过后的函数。
2.1 修饰器修饰函数:
演示代码:
def Decorator(func): #定义了一个修饰器
def wrap(): #这个wrap函数是作修饰加工传入来的func()函数用的
return "<b>"+func()+"</b>"
return wrap; #返回修饰后的函数;相当于wrap()与func()结合
@Decorator #在hello()上加 @Decorator 表示调用hello()函数时,会调用Decorator修饰器帮hello()进行修饰
def hello():
return "hello world"
if __name__ == '__main__':
print(hello()) #打印结果:<b>hello world</b>
可以多个修饰器叠加修饰:
def Decorator1(func): #定义了一个修饰器
def wrap(): #这个wrap函数是作修饰加工传入来的func()函数用的
return "<b>"+func()+"</b>"
return wrap; #返回修饰后的函数;相当于wrap()与func()结合
def Decorator2(func):
def wrap():
return "<i>"+func()+"</i>"
return wrap;
@Decorator2 #相当于 hello = Decorator2(Decorator1(hello))
@Decorator1 #相当于 hello = Decorator1(hello)
def hello():
return "hello world"
if __name__ == '__main__':
print(hello()) #此时执行hello()就不是执行13行的hello(),而是11行的。
#打印结果:<i><b>hello world</b></i>
2.2 修饰器修饰类
第一个示例代码是这样的:
def decorator(cls):
print(cls)
print(cls.model_para)
return
@decorator
class Model(object):
model_para = 'model para'
def __init__(self):
pass
# #打印:
# <class '__main__.Model'>
# model para
可以看到 Model这个类是作为 修饰器decorator的参数的。若是修饰器没人工指定参数,则为默认参数为修饰的类。
然后我们再看第二个示例代码:(修饰器带参数)
def decorator(num):
print(num) #打印 1
def dec2(cls):
cls.a = 2
return cls
return dec2
@decorator(1)
class Model(object):
def __init__(self):
self.a = 1
model = Model
print(model.a) #打印 2
我们可以看到,@decorator里跟了个参数“1”,即人工指定了修饰器decorator的参数为 “1”。修饰器decorator里面有个闭包dec2,dec2有个参数 cls,这里我们会很奇怪,因为我们找不到参数cls的定义。这个定义其实就是被修饰的类,即Model。当修饰器被指定了参数,那么Model就要成为闭包dec2的参数了。而如果修饰器decorator没被指定参数的话,闭包是不能表示类的(代码中Model类),会报错。
2.3 @wraps
然后我们额外介绍一个在 python的 functools包里的 wraps修饰器的用法:(注:wraps是官方提供的一个很好用的修饰器)
因为我在看yolov3的代码里面看到,所以就特地记录一下:
wraps方便对某个函数进行加工和参数修改。如下面代码:funA的参数p1和p2原本为1和2的。然后我们通过decorate_funA对funA加入一些功能,和修改参数。
from functools import wraps
#原函数,即被修改的函数
def funA(p1=1,p2=2):
print(p1,p2)
#decorate_funA是往funA里添加的功能函数
@wraps(funA)
def decorate_funA(*args,**kwargs):
p_for_funA = {}
p_for_funA['p1'] = kwargs.get('p1') if kwargs.get('p1')>10 else 0 #对输入的参数进行一些处理
p_for_funA['p2'] = kwargs.get('p2')
#print(p_for_funA) #{'p1': 0, 'p2': 4}
return funA(*args,**p_for_funA)
decorate_funA(**{'p1':3,'p2':4})
#打印: 0 4
而在yolov3中,就是用wraps修饰器来修改Con2D函数,使其变成想要的 DarknetCon2D() 。我们大概看一下yolov3的部分,不感兴趣的可以直接跳过:
下面的代码,通过对DarknetConv2D的参数继续修改和一些处理,然后把处理后的参数传给Conv2D,其实主要目的就是修改Con2D的一些参数。
@wraps(Conv2D)
def DarknetConv2D(*args, **kwargs):
"""Wrapper to set Darknet parameters for Convolution2D."""
darknet_conv_kwargs = {'kernel_regularizer': l2(5e-4)}
darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same'
darknet_conv_kwargs.update(kwargs)
return Conv2D(*args, **darknet_conv_kwargs)
3.讲解闭包
什么是闭包?闭包即一个封闭的包裹。当你调用一个函数A,然后函数A回一个函数B给你,这个过程就是闭包。
闭包有外层函数和内部函数。上述的函数A就是外层函数,函数B就是内部函数。外层函数也叫父函数。
代码演示:
'''下面的代码块是一个闭包'''
def A(name): #外层函数
def B(age): #内部函数,用于返回出去
print("name:",name,"...","age:",age)
return B #把name传入A(),name填充B()中的name,例如name="Jim",
# 则A(name) 为 A("Jim")
#def B(age):print("name:","Jim","...","age:",age)
#那么B剩下没填充的参数就只有age了。然后返回填充了name后的B()
'''执行'''
if __name__ == '__main__':
func = A("Jim") #对A()中B()的name变量进行赋值,返回赋值后的B()
func(22) #对B()中的age变量进行赋值
打印结果:name: Jim ... age: 22
闭包可以根据不同的环境来返回不同的函数,以上述代码为例,不同的环境指不同的name,然后生成各种name对应的B()函数并返回。
4.nonlocal语句
在python函数中,可以直接引用外部变量,但不能改变外部变量,因此如果直接在闭包中改变父函数的变量,将会出现错误。
如:
def A():
count = 0;
def B():
count+=1;
print("count=",count)
return B;
func = A()
func();
#发生错误:local variable 'count' referenced before assignment
原因:原来在 python 的函数中和全局变量同名的变量,如果你有修改该变量的值就会变成局部变量,在修改之前对该变量的引用自然会出现没定义这样的错误。
如果一定要修改,则记上 nonlocal 就可以了,下面代码演示:
def A():
count = 0;
def B():
nonlocal count
count+=1;
print("count=",count)
return B;
func = A()
func();
func();
func();
'''打印结果:
count= 1
count= 2
count= 3
'''