• 闭包和装饰器相关


     闭包参考:http://blog.csdn.net/marty_fu/article/details/7679297

    装饰器参考1:https://segmentfault.com/a/1190000007321935  *****

    装饰器参考2:http://blog.csdn.net/dreamcoding/article/details/8611578

    1. 闭包中是不能修改外部作用域的局部变量的

    >>>
    >>> def foo(num):
    ...     def bar():
    ...         num = 3
    ...         print(num)
    ...     print(num)
    ...     bar()
    ...
    >>> foo(2)
    2
    3
    >>>
    

    2. 闭包中经典的错误代码

    >>>
    >>> def foo():
    ...     a = 1
    ...     def bar():
    ...         a = a + 1
    ...         return a
    ...     return bar
    ...
    >>> f = foo()
    >>> f
    <function foo.<locals>.bar at 0x000000000076B048>
    >>> f()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 4, in bar
    UnboundLocalError: local variable 'a' referenced before assignment
    >>>
    

    解释

    python规则指定所有在赋值语句左面的变量都是局部变量,

    则在闭包bar()中,变量a在赋值符号"="的左面,被python认为是bar()中的局部变量。

    再接下来执行print c()时,程序运行至a = a + 1时,因为先前已经把a归为bar()中的局部变量,

    所以python会在bar()中去找在赋值语句右面的a的值,结果找不到,就会报错。

    解决办法:使用nonlocal来指定a不是闭包中的局部变量

    >>>
    >>> def foo():
    ...     a = 1
    ...     def bar():
    ...         nonlocal a
    ...         a = a + 1
    ...         return a
    ...     return bar
    ...
    >>> f = foo()
    >>> f
    <function foo.<locals>.bar at 0x000000000076B0D0>
    >>>
    >>> f()
    2
    >>>
    

    或者把 a 设定为一个容器

    >>>
    >>> def foo():
    ...     a = [1]
    ...     def bar():
    ...         a[0] = a[0] + 1
    ...         return a[0]
    ...     return bar
    ...
    >>> f = foo()
    >>> f
    <function foo.<locals>.bar at 0x000000000076B158>
    >>>
    >>> f()
    2
    >>>
    

     

    3.为什么要使用闭包?

    参考:https://zhuanlan.zhihu.com/p/26934085

    闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来。

    这一点与面向对象编程是非常类似的,在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

    所有函数都有一个 __closure__属性,如果这个函数是一个闭包的话,那么它返回的是一个由 cell 对象 组成的元组对象。

    cell 对象的cell_contents 属性就是闭包中的自由变量。

    def adder(x):
        def wrapper(y):
            return x + y
        return wrapper
    
    adder5 = adder(5)
    print(adder5(10))   # 15
    
    print(adder.__closure__)       # None
    print(adder5.__closure__)      # (<cell at 0x00000000006F6168: int object at 0x0000000059060250>,)
    # for attr in dir(adder5.__closure__[0]):
    #     print(attr,getattr(adder5.__closure__[0],attr))
    print(adder5.__closure__[0].cell_contents)   # 5
    

    这解释了为什么局部变量脱离函数之后,还可以在函数之外被访问的原因的,

    因为它存储在了闭包的 cell_contents 中了。 

    4. 闭包的应用

    # 1. 当闭包执行完后,仍然能够保持住当前的运行环境
    
    >>>
    >>> origin = [0, 0]    # 坐标系统原点
    >>> legal_x = [0, 50]  # x轴方向的合法坐标
    >>> legal_y = [0, 50]  # y轴方向的合法坐标
    >>> def create(pos=origin):
    ...     def player(direction,step):
    ...         # 这里应该首先判断参数direction,step的合法性,比如direction不能斜着
    走,step不能为负等
    ...         # 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭
    包,就不详细写了。
    ...         new_x = pos[0] + direction[0]*step
    ...         new_y = pos[1] + direction[1]*step
    ...         pos[0] = new_x
    ...         pos[1] = new_y
    ...         #注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过
    ...         return pos
    ...     return player
    ...
    >>>
    >>> player = create()         # 创建棋子player,起点为原点
    >>> print(player([1,0],10))   # 向x轴正方向移动10步
    [10, 0]
    >>> print(player([0,1],20))   # 向y轴正方向移动20步
    [10, 20]
    >>> print(player([-1,0],10))  # 向x轴负方向移动10步
    [0, 20]
    >>>
    

      

    # 2. 闭包可以根据外部作用域的局部变量来得到不同的结果,类似配置功能。
    
    >>>
    >>> def make_filter(keep):
    ...     def the_filter(file_name):
    ...         file = open(file_name)
    ...         lines = file.readlines()
    ...         file.close()
    ...         filter_doc = [i for i in lines if keep in i]
    ...         return filter_doc
    ...     return the_filter
    ...
    >>>
    >>> filter = make_filter("pass")
    >>> filter_result = filter("result.txt")
    ...
    

    5. 装饰器

    """装饰器"""
    def wrapper(func):
    	print('装饰器工作了')
    	def inner(*args,**kwargs):
    		return func(*args,**kwargs)
    	return inner
    '''
    1.立即执行wrapper函数,并将下面装饰的函数当做参数传递进去
    2.将wrapper函数的返回值,赋给被装饰的函数,即:
    	index = wrapper(index)
    	index = inner函数
    	所以,下面执行的index(),实际上就是执行的inner函数
    '''
    @wrapper
    def index():
    	print('index')
    
    index()
    

      

    # 1. 装饰器之装饰无参函数
    
    >>>
    >>> def bar(func):
    ...     def wrapper():
    ...             return "Good " + func()
    ...     return wrapper
    ...
    >>> def foo(func):
    ...     def wrapper():
    ...             return "evening " + func()
    ...     return wrapper
    ...
    >>> @bar
    ... @foo
    ... def hello():
    ...     return 'standby'
    ...
    >>> hello()
    'Good evening standby'
    >>>
    

      

    # 2. 装饰器之装饰有参函数
    
    >>>
    >>> def bar(func):
    ...     def wrapper(name):
    ...             return "Good " + func(name)
    ...     return wrapper
    ...
    >>> def foo(func):
    ...     def wrapper(name):
    ...             return "evening " + func(name)
    ...     return wrapper
    ...
    >>> @bar
    ... @foo
    ... def hello(name):
    ...     return name
    ...
    >>> hello('standby')
    'Good evening standby'
    >>>
    

      

    # 3. 装饰器之装饰参数数量不确定的函数
    
    >>>
    >>> def bar(func):
    ...     def wrapper(*args, **kwargs):
    ...             return "Good " + func(*args, **kwargs)
    ...     return wrapper
    ...
    >>> def foo(func):
    ...     def wrapper(*args, **kwargs):
    ...             return "evening " + func(*args, **kwargs)
    ...     return wrapper
    ...
    >>> @bar
    ... @foo
    ... def hello(name):
    ...     return name
    ...
    >>> @bar
    ... @foo
    ... def hi(firstname,lastname):
    ...     return firstname+lastname
    ...
    >>> hello('standby')
    'Good evening standby'
    >>> hi('liu','lixin')
    'Good evening liulixin'
    >>>
    

    带参装饰器参考:http://www.cnblogs.com/standby/p/6910613.html

    如果你的装饰器如果带参数呢?

    那么你就需要在原来的装饰器上再包一层,用于接收这些参数。

    这些参数(私货)传递到内层的装饰器里后,闭包就形成了。

    所以说当你的装饰器需要自定义参数时,一般都会形成闭包。(类装饰器例外)

    # 不带参数的装饰器
    
    >>>
    >>> def debug(func):
    ...     def wrapper(*args, **kwargs):
    ...         print("[DEBUG]: enter {}()".format(func.__name__))
    ...         return func(*args, **kwargs)
    ...     return wrapper
    ...
    >>> @debug
    ... def say(something):
    ...     print("say {}!".format(something))
    ...
    >>> say('goodbye')
    [DEBUG]: enter say()
    say goodbye!
    >>>
    
    
    # 4. 装饰器之带参数的装饰器
    
    >>>
    >>> def logger(level):
    ...     def debug(func):
    ...         def wrapper(*args, **kwargs):
    ...             print("[{level}]: enter {func}()".format(level=level, func=func.__name__))
    ...             return func(*args, **kwargs)
    ...         return wrapper
    ...     return debug
    ...
    >>> @logger(level='INFO')
    ... def say(something):
    ...     print("hello {}!".format(something))
    ...
    >>> @logger(level='DEBUG')
    ... def talk(somebody):
    ...     print("talk to {}".format(somebody))
    ...
    >>> say('evening')
    [INFO]: enter say()
    hello evening!
    >>> talk('eric')
    [DEBUG]: enter talk()
    talk to eric
    >>>
    

    装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。

    在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。

    >>>
    >>> class Test():
    ...     def __call__(self):
    ...         print('call me!')
    ...
    >>> obj = Test()
    >>> obj()
    call me!
    >>>
    

      

    # 5. 装饰器之不带参数的类装饰器
    
    >>>
    >>> class logging(object):
    ...     def __init__(self, func):
    ...         self.func = func
    ...     def __call__(self, *args, **kwargs):
    ...         print("[DEBUG]: enter function {func}()".format(func=self.func.__name__))
    ...         return self.func(*args, **kwargs)
    ...
    >>> @logging
    ... def say(something):
    ...     print("say {}!".format(something))
    ...
    >>> say('evening')
    [DEBUG]: enter function say()
    say evening!
    >>>
    >>>
    

      

    带参的类装饰器在构造函数里接受的就不是一个函数,而是传入的参数。

    通过类把这些参数保存起来。然后在重载__call__方法是就需要接受一个函数并返回一个函数。

    # 6. 装饰器之带参数的类装饰器
    
    >>>
    >>> class logging(object):
    ...     def __init__(self, level='INFO'):
    ...         self.level = level
    ...     def __call__(self, func): # 接受函数
    ...         def wrapper(*args, **kwargs):
    ...             print("[{level}]: enter function {func}()".format(level=self.level, func=func.__name__))
    ...             func(*args, **kwargs)
    ...         return wrapper        #返回函数
    ...
    >>> @logging(level='INFO')
    ... def say(something):
    ...     print("say {}!".format(something))
    ...
    >>> say('evening')
    [INFO]: enter function say()
    say evening!
    >>>
    

      

    2018-06-21 补充

    不带参数的类装饰器

    class logging(object):
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args, **kwargs):
            print("[DEBUG]: enter function {func}()".format(func=self.func.__name__))
            return self.func(*args, **kwargs)
    
    @logging
    def say(something):
        print("say {}!".format(something))
    
    say('hi')
    
    '''
    - logging(say)
    - say = logging(say)
    
    - say():
        - say = logging(say) == obj
        - say() == obj() ==  __call__()
    '''

    带参数的类装饰器

    class logging(object):
        def __init__(self, level):
            self.level = level
    
        def __call__(self, func):
            def wrapper(*args,**kwargs):
                print("[{level}]: enter function {func}()".format(level=self.level,func=func.__name__))
                return func(*args, **kwargs)
            return wrapper
    
    
    @logging(level='INFO')
    def say(something):
        print("say {}!".format(something))
    
    say('hi')
    
    '''
    - logging(level='debug')  return obj 
    
        @obj
        def say(something):
            print("say {}!".format(something))
     
    - say == obj(say) == __call__(say)  ==  wrapper
    - say() == obj() == wrapper()
    '''
    

     

    作者:Standby一生热爱名山大川、草原沙漠,还有妹子
    出处:http://www.cnblogs.com/standby/

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    Nacos安装部署文档
    mysql安装部署文档
    Postgresql数据库安装部署文档
    Sentinel安装部署文档
    Nginx安装部署手册
    golang 7. defer
    golang 6. 指针 *
    golang 5. import
    golang 4. 函数 func
    golang 3. 常量 const iota
  • 原文地址:https://www.cnblogs.com/standby/p/8271157.html
Copyright © 2020-2023  润新知