闭包与装饰器
这一篇学习闭包与装饰器,包括前置知识、 闭包、 装饰器
一、前置知识
理解闭包和装饰器需要函数相关知识
函数名指向函数对象
>>> def hello(): ... return 'hello' ... >>> hello # 这是函数名 <function hello at 0x7ff1db09f488 > >>> hello() # 这是执行函数 'hello' >>> p = hello # 可以给函数绑定个新的名字p >>> p() 'hello' # 然后执行函数
函数也是对象,函数名是指向函数对象的名字
函数可以嵌套
函数里面可以内嵌函数
// hello_word里面定义hello函数 >>> def hello_word(): ... def hello(): ... return 'hello' ... return hello() + 'word' >>> hello_word() # 输出'helloword'
也可以返回一个函数对象
// 执行一个函数返回另一个函数
>>> def hello_word(): ... def hello(): ... return 'helloword' ... return hello ... >>> hello_word # 函数名 <function hello_word at 0x7f049e2ffe18 > >>> hello_word() # 执行函数,返回内部的hello函数名(未执行) <function hello_word. < locals > .hello at 0x7f049e257488 > >>> hello_word()() # 相当于执行内层的hello函数 'helloword'
注:可以返回的是未执行的函数
高阶函数
可以把函数作为参数传给另一个函数
def inner(x, y): return x + y def outer(func, x, y): # outer函数接收一个函数,两个参数 return func(x, y) outer(inner, 10, 20) # 把inner函数作为参数传给outer函数
要点:函数可以传来传去
函数作用域
作用域是名字可以被搜索到的范围和规则,按照 L (本地)、E (闭合) 、G(全局)、B (内建) 四个作用域依次搜索

函数可以引用外部的作用域中的名字, 内层inner函数引用了enclosing作用域中的y
更多参考命名空间和作用域
二、闭包
前提:一定是嵌套的函数
如果一个内层函数对 enclosing (非全局非本地 )作用域中的名字进行了引用,那么这个内层函数就是个闭包 (closure)

上面的outer函数里面定义了inner函数(内层函数), 这个inner函数引用了enclosing作用域中的a,b,x,那么这个inner函数就是个闭包,引用的a,b,x也称作自由变量。
闭包函数有个__closure__属性可以得到引用的自由变量,它返回的是由cell对象组成的tuple
>>> f = outer(3) >>> f.__closure__ # 获得引用的自由变量 (<cell at 0x7fb902b3ba98: int object at 0x8a8d40>, <cell at 0x7fb902b3b918: int object at 0x8a8e80>, <cell at 0x7fb902b3b978: int object at 0x8a8c60>) >>>
// 通过cell_contens属性可以获取到值 >>> f.__closure__[0].cell_contents 10 >>> f.__closure__[1].cell_contents 20 >>> f.__closure__[2].cell_contents 3
上面的例子需要理解: 闭包所引用自由变量不会随着外层函数执行完毕而销毁,闭包就好像携带了并记住这些变量名
闭包的例子
写了两个类似的功能
- 如果是100分那么60为及格返回True,否则False
- 如果是150分那么80为及格返回True,否则False
....... 等等, 更多类似要求 .....
def pass_100(score): """100分, 如果超过60分为及格, 返回True,否则False""" standard = 60 return score > standard def pass_150(score): """150分, 如果超过80分为及格, 返回True,否则False""" standard = 80 return score > standard ......
这时又要添加一个类似的功能,比如200分, 120分及格为True, 否则False,于是如法炮制
def pass_200(score): """200分, 如果超过120分为及格, 返回True,否则False""" standard = 120 return score > standard
但你发现这些功能都是类似的, 能不能做一些合并、减少重复代码呢? 那么可以使用闭包写成这样
>>> def set_pass(standard): ... def inner(score): ... return score > standard # 内层inner函数引用上层作用域中的'standard' ... return inner ... >>> pass_100 = set_pass(60) >>> pass_150 = set_pass(80) >>> pass_200 = set_pass(120) >>> >>> pass_100(59) False >>> pass_100(61) True >>> pass_150(79) False >>> pass_150(81) True >>> pass_200(119) False >>> pass_200(121) True
闭包小结:
- 内层函数对enclosing作用域中的名字(自由变量)进行了引用,那么这个携带了自由变量的内层函数就是闭包
- 装饰器就是对闭包的使用
三、装饰器
有这样的需求,需要对现有的对象比如函数、方法或类进行扩展而不修原有的代码,这是装饰器
装饰器可以动态地扩展函数、方法或类的功能而不必使用子类或修改原有代码,这就是所谓的装饰,用大白话说就是 对现有功能进行一些 "修饰 ",加点功能但又不修改原先的代码
装饰器用于切面(AOP)的场景,也是一种设计模式,较为经典的有插入日志、性能测试、事务处理等.
装饰器 = 闭包 + 高阶函数
1. 引出装饰器
很早以前写了一些功能,它是这样的
def foo(): return 'foo' def boo(): return 'boo'
现在要给这个功能在添加一些功能,在函数执行的前后打印一些提示信息,你这样做的
print('开始执行foo函数') foo() print('foo函数执行完毕!') print('开始执行boo函数') boo() print('boo函数执行完毕!') ...... 更多类似需求...
但如果还有n多函数要这样做呢? low一点复制粘贴、复制粘贴.., 这样重复的代码就更多了.
那么能不能把重复代码提前出来,并且用更优雅的方式来实现呢, 利用闭包和高阶函数
def decorator(func): def inner(): # inner是个闭包,引用了enclosing作用域中的func print('开始执行 %s 函数 ' % func.__name__) result = func() print('%s函数执行完毕! ' % func.__name__) return result return inner def foo(): return 'foo' def boo(): return 'boo' foo = decorator(foo) # 把foo函数作为参数传给decorator函数并执行, 最终的结果是 foo -> inner (foo实际上指向的是inner) boo = decorator(boo) # 把boo函数作为参数传给decorator函数并执行, 最终的结果是 boo -> inner (boo实际上指向的是inner) foo() # 实际上执行的是inner函数 boo() # 实际上执行的是inner函数
我们可以使用@装饰符语法,和上面的是等价的
def decorator(func): # 这是一个装饰器 def inner(): print('开始执行 %s 函数' % func.__name__) result = func() print('%s 函数完毕!' % func.__name__) return result return inner
# foo被decorator装饰, 等价于"foo = decorator(foo)", 所以结果是 foo 指向 inner @decorator def foo(): return 'foo'
# boo被decorator装饰, 等价于"boo = decorator(boo)", 所以结果是 foo 指向 inner @decorator def boo(): return 'boo' foo() boo()
========== 结果 =========== 开始执行 foo 函数 foo 函数完毕! 'foo' 开始执行 boo 函数 boo 函数完毕! 'boo'
遇到 @decorator会把下面的 foo 与 boo 函数作为参数传给 decorator 函数并执行
即 " foo = decorator(foo) 、boo =decorator(boo)" , 那么返回的是inner (闭包), 最终的结果就是foo与boo都执向inner,当执行foo()、boo()实际上就是inner()。这时候原先的函数被重新加工了
以上就是装饰器,可以在不修改原先功能的情况下动态添加功能
2. 装饰带有参数的函数
被装饰的函数是可能有参数的,如果装饰器没有对应的参数则会抛出异常
def decorator(func): def inner(): result = func() return result return inner @decorator def foo(content): return 'foo ' + content @decorator def boo(content): return 'boo ' + content foo('abc') =============================== 结果 =============================== Traceback (most recent call last): File "E:\NOTE\python\learnpython\deco.py", line 100, in <module> foo('abc') TypeError: inner() takes 0 positional arguments but 1 was given # 提示inner没有位置参数,但却提供了一个位置参数
因为foo函数被decorator装饰,foo指向的是闭包函数inner,当执行foo的时候实际上是执行inner,而inner并没有提供参数,这样就不能为原boo函数提供参数,所以传参的时候异常.
因此闭包函数inner也应该有参数,这样才能对应起来,可以原函数有几个,闭包函数inner也写几个
def decorator(func): def inner(content): result = func(content) return result return inner @decorator def foo(content): return 'foo' + content @decorator def boo(content): return 'foo' + content foo('abc')
boo('abc')
但更灵活的方法是在闭包函数 inner 中使用收集参数,这样就可以接收任意位置参数或关键字参数
def decorator(func): def inner(*args, **kwargs): return func(*args, **kwargs) return inner @decorator def foo(content): return 'foo ' + content @decorator def boo(content): return 'boo + content foo('abc') boo('abc')
3、函数可以被多个装饰器装饰 (多层装饰器)
可以理解为给函数添了加多重功能、被装饰了n道,最后返回一个闭包。对于多层装饰器主要是理解它的执行流程,看个例子
def bold(func): " 字符串加粗装饰器 " def inner(*args, **kwargs): result = '<b>' + func(*args, **kwargs) + '</b>' return result return inner def italic(func): " 字符串斜体装饰器 " def inner(*args, **kwargs): result = '<i>' + func(*args, **kwargs) + '</i>' return result return inner # 等价于 italic(bold(foo)) @italic # 后被 italic 装饰,返回一个闭包函数 @bold # 先被 bold 装饰,返回一个闭包函数 def foo(): return 'foo' # 等价于 bold(italic(boo)) @bold # 后被 bold 装饰, 返回一个闭包函数 @italic # 先被 italic 装饰,返回一个闭包函数 def boo(): return 'boo' boo() # <i><b>foo</b></i> foo() # <b><i>boo</i></b>
所以多层装饰器的执行流程是 ‘由下往上’ 执行,先被离的最近的装饰器装饰,返回的是一个闭包函数,然后又被上层的装饰器装饰,最终返回的是另一个闭包函数,依此类推
4、类装饰器
装饰器可以是函数或类,互相可以装饰。 使用类装饰器需要在类中定义两个特殊方法 __init__和__call__
__init__方法接收一些参数, __call__方法可以让实例像函数一样可以调用
from functools import wraps class decorate(object): def __init__(self, func): self.func = func wraps(func)(self) def __repr__(self): return f'{self.func}' def __call__(self, *args, **kwargs): print(f"调用{self.func.__name__}函数") ret = self.func(*args, **kwargs) return ret @decorate # 遇到就把helloword函数对象传给decorate这个类装饰器并实例化,此时helloword是decorate的一个实例 def helloword(): "myfunc" return 'hellword' if __name__ == '__main__': print(isinstance(hellword, decorate)) print(helloword()) # 当helloword执行时,会调用__call__方法,因为helloword被装饰了,是decorate的实例 # 输出 True 调用hellword函数 hellword
def decorator(cls): def inner(*args, **kwargs): print('call %s' % cls.__name__) return cls(*args, **kwargs) return inner # 把Person类作为参数传给decorator,返回inner, 即Person 指向 inner @decorator class Person(object): def __init__(self, value): self.value = value Person p = Person('me') p p.value ======== 结果 ======== <function inner at 0x000000000249ABA8> call Person <__main__.Person object at 0x00000000024A2080> me
class Decoclass(object): def __init__(self, cls): self.cls = cls def __call__(self, *args, **kwargs): print('Call class ---> {}'.format(self.cls.__name__)) result = self.cls(*args, **kwargs) return result # 把Person作为参数传给Decoclass类并实例化, 此时Person变成了Decoclass的一个实例 # 当执行Person('参数') 实际上是执行 Person.__call__('参数') @Decoclass class Person(object): def __init__(self, value): self.value = value # Person if __name__ == '__main__': print(Person) # <__main__.Decoclass object at 0x00000000027D9208> // Person是Decoclass的一个实例 p = Person('abc') # Call class ---> Person print(p) # <__main__.Person object at 0x0000000001E99F60> print(p.value) # abc
注:只要明白装饰器的意思就行, 函数或类都可以做装饰器
5、带有参数的装饰器
装饰器是可以有参数的,写起来层次很多但功能加灵活,它会生成一个装饰器然后进行装饰,理解就是一个能生成装饰器的装饰器.
from functools import wraps def htmltag(tag=None): def decorator(f): @wraps(f) def wrapper(*args, **kwargs): if tag is None: return f(*args, **kwargs) return '<{0}>{1}</{2}>'.format(tag, f(*args, **kwargs), tag) return wrapper return decorator # 第一步 执行htmltag(tag='b') 返回一个装饰器函数decorator # 第二步 把helloword对象 作为参数传给decorator装饰器,返回一个闭包函数wrapper # 最后 helloword 指向的是wrapper, 执行helloword()等价于htmltag(tag='b')(helloword)() @htmltag(tag='b') def helloword(): return 'helloword' # 第一步 执行htmltag(tag='i') 返回一个装饰器函数decorator # 第二步 把hello_word对象作为参数传给decorator装饰器,返回一个闭包函数wrapper # 最后hello_word指向的是wrapper, 执行helloword()等价于htmltag(tag='b')(helloword)() @htmltag(tag='i') def hello_word(): return 'hello_word' # 多层装饰,先被htmltag(tag='b')装饰 # 又被htmltag(tag='i')装饰 @htmltag(tag='i') @htmltag(tag='b') def hello(): return 'hello' helloword() # <b>helloword</b> hello_word() # <i>hello_word</i> hello() # <i><b>hello</b></i>
from functools import update_wrapper class htmltag(object): """docstring for htmltag""" def __init__(self, tag=None): self.tag = tag def __call__(self, f): def inner(*args, **kwargs): if self.tag is None: return f(*args, **kwargs) return '<{0}>{1}<{2}>'.format(self.tag, f(*args, **kwargs), self.tag) return update_wrapper(inner, f) @htmltag(tag='b') # 执行htmltag(tag='b'),得到是htmltag类的实例,实际上是个装饰器,然后将helloword函数传进去(调用__call__方法)并执行 def helloword(): "docstring for func" return 'helloword' print(helloword()) # 输出 <b>helloword<b>
from functools import wraps class Flask(object): """docstring for Flask""" def __init__(self): self.view_maps = {} def route(self, url): def decorate(func): self.view_maps[url] = func @wraps(f) def inner(*args, **kwargs): return func(*args, **kwargs) return inner return decorate def run(self): while 1: url = input('请输入url') view_func = self.view_maps.get(url, None) if not view_func: else: view_func() app = Flask() @app.route('/') def index(): return 'index' app.run()
6、保留被装饰的对象的元数据
装饰器有个问题是被装饰的对象元数据发送了变化,可以使用functools模块中的wraps、update_wrapper,参考这里
四、总结
理解函数相关知识
- 函数名是函数对象的指向,可以将新的名字绑定到这个函数对象
- 函数可以内嵌函数、返回一个函数对象
- 高阶函数,将一个函数作为参数传给函数
- 四个命名空间
理解闭包,内层函数引用了enclosing作用域中的名字, 像是记住携带了这些名字(自由变量),那么这个函数就是闭包
理解装饰器
- 用于面向切面的场景,核心是给原有对象增加功能而不修改原有的代码,场景有性能测试、插入日志、拦截器、打补丁等
- 提取一些重复的代码复用
- 装饰器就是闭包和高阶函数的使用
- 类横向扩展比较麻烦,而装饰器比较好解决这种问题
浙公网安备 33010602011771号