闭包与修饰器

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
'''

 

 

 

 

 

 

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值