美文网首页python学习
闭包和装饰器

闭包和装饰器

作者: 潇潇雨歇_安然 | 来源:发表于2018-05-29 10:01 被阅读14次

    一、闭包

    • 什么是闭包?在了解这个概念之前,我们先来看一个小例子。
    def create_line(k,b):
        '''定义一个表示直线的函数'''
        def line(x):
            ret = k * x + b
            return ret
        return line
    
    line_11_22 = create_line(11,22) #返回的是一个函数line
    print(line_11_22(1)) #line_11_22(1)相当于执行line(1)
    print(line_11_22(2))
    print(line_11_22(3))
    

    上面的例子中,我们看到有一个外部函数,内部定义了一个函数,外部函数返回的是内部函数,而内部函数同时使用到了外部函数的变量k和b。

    像这样,在一个外部函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。在上面的例子中,我们发现,当我们在执行输出的时候,传入的是一个参数,但是却可以计算值来,从这点可以看出,闭包可以保存之前出入的数据。

    • 特点:闭包可以保存数据和功能。

    • 通过闭包,我们可以修改外部函数中的变量,在Python3中我们修改外部变量的值是通过使用关键字nonlocal来实现的,如下:

    def set_func(count=0):
        def counter():
            nonlocal count # nonlocal 表示引用的是外部函数的变量。
            count += 10
            return count
        return counter
    
    ct = set_func(10)
    print(ct()) # 20
    

    二、装饰器

    装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以下面跟大家介绍下装饰器,在介绍装饰器之前,先来一个小例子。

    def task1():
        print('无参数')
    
    task1()
    

    如上面的例子,如果我们要修改task1函数的功能必须每次都修改task1函数内部的代码,这样做很麻烦,我们其实可以用装饰器来解决这个问题。那么什么是装饰器呢?

    • 装饰器:在不修改原有函数定义的前提下,给函数扩展相应的功能,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。我们使用:@装饰器函数名 放在一个函数定义前面来装饰一个函数。

    那么装饰器可以针对哪些函数呢,下面我们一一列举:

    1. 无参数无返回值的函数

    def decorater(func):
        def wrapper():
            func()
        return wrapper
    
    @decorater
    def task1():
        print('无参数')
    
    task1()
    

    分析:
    当我们在task1函数定义前加了@decorater之后,装饰器函数底层做了一个操作,即:task1 = decorater(task1),此时func指向了task1,task1指向了wrapper,当我们在调用task1()的时候,相当于执行了wrapper(),在wrapper函数里面执行了task1函数。

    2. 无参数有返回值的函数

    def decorater(func):
        def wrapper():
            return func()
        return wrapper
    
    @decorater
    def task2():
        return '有返回值'
    
    t = task2()
    print(t)
    

    3. 有参数无返回值的函数

    def decorater(func):
        def wrapper(num):
            func(num)
        return wrapper
    
    @decorater
    def task3(num):
        print('有参数,无返回值 %d' % num)
    
    task3(10)
    

    4. 有参数有返回值的函数

    def decorater(func):
        def wrapper(num):
            return func(num)
        return wrapper
    
    @decorater
    def task4(num):
        return num + 10
    
    t2 = task4(10)
    print(t2)
    

    5. 万能装饰器

    上面的4种方法都没什么问题,但是写起来太麻烦,下面我们实现一种万能的装饰器,可以有、无参数,也可以有、无返回值。

    def decorater(func):
        def wrapper(*args,**kwargs):
            return func(*args,**kwargs)
        return wrapper
    
    @decorater
    def task4(*args,**kwargs):
        print('args:',args)
        print('kwargs:',kwargs)
        return 10
    
    t1 = task4()
    print(t1)
    t2 = task4(10,20)
    print(t2)
    t3 = task4(15,25,age=30)
    print(t3)
    
    运行结果:
    args: ()
    kwargs: {}
    10
    args: (10, 20)
    kwargs: {}
    10
    args: (15, 25)
    kwargs: {'age': 30}
    10
    

    可以看到,无论我们装饰的函数有没有参数和返回值,都可以装饰成功。

    6. 多个装饰器装饰一个函数

    #短信验证的装饰器
    4 def message_fun(func):
    5    print('正在进行短信验证装饰')
    6   def call_mesage():
    7       print('正在进行短信安全验证')
    8     func()
    9   return call_mesage
    
    #手势验证的装饰器
    12 def gesture_fun(func):
    13   print('正在进行手势验证装饰')
    14    def call_gesture():
    15       print('正在进行手势安全验证')
    16        func()
    17   return call_gesture
    
    19 @message_fun # t = message_fun(t)
    20 @gesture_fun # t = gesture_fun(t)
    21 def t():
    22    print('装饰结束了哟')
    
    24 t()
    
    运行结果:
    正在进行手势验证装饰
    正在进行短信验证装饰
    正在进行短信安全验证
    正在进行手势安全验证
    装饰结束了哟
    

    从运行结果我们可以看到,装饰的时候是从内到外,执行的时候是从外到内,这是什么意思呢?就是谁距离被装饰的函数最近,谁最先装饰这个函数,谁距离被装饰的函数越远,谁最先执行。
    我们来分析一下这个过程(-》表示指向):

    分析装饰过程:
    4行:定义函数message_fun
    12行:定义函数gesture_fun
    19行:此时t还没有定义,所以等待执行
    20行:此时t也还没定义,所以也是等待执行
    21行:定义函数t
    20行:
        1. 执行函数gesture_fun(t),跳到第13行,执行 此时 gesture_fun.func  -》 t
        2. print('正在进行手势验证装饰')
        3. 第14行,定义函数call_gesture ,t = gesture_fun(t), 此时 t -》call_gesture
    19行:
        1.执行函数message_fun(t),跳到第5行,执行 此时 message_fun.func -》call_gesture
        2.print('正在进行短信验证装饰')
        3.第6行,定义函数call_mesage,t = message_fun(t),此时 t -》 call_mesage
    24行: 
        1. 此时t - 》 call_mesage,执行第7行
        2. print('正在进行短信安全验证'),在执行第8行,执行func(),跳到第15行,
        3. print('正在进行手势安全验证'),执行func(),跳到第21行,
        4. print('装饰结束了哟')       
    

    7. 类装饰器

    装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 call() 方法,那么这个对象就是callable的。

    class Person(object):
        '''类装饰器'''
        def __init__(self,func):
            self.__func = func
    
        def __call__(self, *args, **kwargs):
            self.__func()
    
    @Person  #等价于 task_test = Person(task_test)
    def task_test():
        print('hehe')
    
    task_test() #执行这个对象
    

    8. 装饰器带参数,在原有的基础上设置外部变量

    import functools
    
    86 def timefun_arg(pre='hello'):
    87    def timefun(func):
    88        @functools.wraps(func)
    89        def wrapped_func():
    90           print('%s called at %s :%s' % (func.__name__,ctime(), pre))
    91           return func()
    92        return wrapped_func
    93    return timefun
    
    95 @timefun_arg('Hi')  #底层实现:foo = timefun_arg('Hi')(foo)
    96 def foo():
    97    print('哈哈')
    
    99  foo()
    

    我们分析下上面的过程:

    分析过程:
    86行:定义函数timefun_arg
    95行:先执行 timefun_arg('Hi'),此时pre='Hi',然后timefun -》timefun_arg('Hi'),此时foo还未定义,所以等待。。
    96行:定义foo函数
    95行:此时执行 timefun(foo),然后func -》foo,返回wrapped_func,此时foo -》wrapped_func
    99行:foo() 相当于执行wrapped_func(),然后执行 print('%s called at %s :%s' % (func.__name__,ctime(), pre))
    91行:返回func(), 此时func —》foo,所以跳到第97行,打印:print('哈哈')
    

    以上的decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'foo'变了'wrapped_func',因为返回的那个wrapped_func()函数名字就是'wrapped_func',所以,需要把原始函数的__name__等属性复制到wrapped_func()函数中,否则,有些依赖函数签名的代码执行就会出错。不需要编写wrapped_func.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的。上面的一个例子我们已经加了这段代码。在打印的时候发现,他的名字已经变成了跟函数名字一样的。

    最后我们再来实现一个需求:
    '''
    思考一下能否写出一个@log的decorator,使它既支持:
    @log # f = log(f)
    def f():
    pass

    又支持:
    @log('execute') #f = log('execute')(f)
    def f():
    pass
    '''
    代码如下:

    113 def log(arg='haha'):
    114    def decorator(func):
    115        @functools.wraps(func)
    116        def wrapper():
    117            print('name: %s,arg: %s' % (func.__name__,arg))
    118            return func()
    119        return wrapper
    120    if callable(arg): #说明使用的是 @log方式
    121       return decorator(arg)
    122    else:
    123        return decorator
    124
    125 @log
    126 def f1():
    127    print('大家')
    
    129 @log('哈哈')
    130 def f2():
    131    print('笑了')
    
    133 f1()
    134 f2()
    

    我们也来分析一下过程:

    分析过程:
    113行:定义函数log
    125行:底层实现:f1 = log(f1),arg—>f1,
    114行,定义一个函数decorator,
    120行,满足条件,直接返回decorator(arg),此时f1-》decorator(arg),执行114行,此时func-》arg,返回wrapper,
        最终f1-》wrapper
    133行,执行f1() 相当于执行wrapper(),print('name: %s,arg: %s' % (func.__name__,arg)),
        返回func(),此时func-》arg-》f1,相当于执行f1(),打印 print('大家')
    129行,底层实现:f2 = log('哈哈')(f2),先执行log('哈哈'),arg->'哈哈',114行,定义函数decorator,
        120行,不满足,123行,满足,返回,此时f2 = decorator(f2),此时func-》f2,返回wrapper,最终f2 -》wrapper
    134行,f2(),相当于执行wrapper(),打印,print('name: %s,arg: %s' % (func.__name__,arg)),
        返回func(),此时func-》f2,所以执行f2(),130行,打印print('笑了')
        
    

    相关文章

      网友评论

        本文标题:闭包和装饰器

        本文链接:https://www.haomeiwen.com/subject/qhbnjftx.html