闭包和装饰器相关

 闭包参考: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()
'''

 

posted @ 2018-01-12 00:46  青山应回首  阅读(288)  评论(0编辑  收藏  举报