闭包和装饰器相关
闭包参考: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() '''
出处:http://www.cnblogs.com/standby/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。