讲解闭包前得先讲下变量做用域的问题python
理解闭包里引用的变量,就要理解变量做用域规则,请看下面的例子git
# coding: utf-8 b = 6 def f2(a): print(a) print(b) f2(1)
很显然,返回api
1 6
但假如这样呢bash
# coding: utf-8 b = 6 def f2(a): print(a) print(b) b = 9 f2(1)
就会报错闭包
1 Traceback (most recent call last): File "E:/code/git/source/master/coreTasker/api/controllers/a.py", line 8, in <module> f2(1) File "E:/code/git/source/master/coreTasker/api/controllers/a.py", line 5, in f2 print(b) UnboundLocalError: local variable 'b' referenced before assignment
首先输出了1,接着print(b)执行不了报错了,一开始觉得会打印6,由于b是个全局变量。app
可事实是,python编译函数的定义体时,它判断b是局部变量,由于在函数中给他赋值了(b=9),那么python就会尝试从本地环境获取b,而本地环境还没给其赋值。函数
若是在函数中赋值时想让解释器把b看成全局变量,那么用global声明:spa
# coding: utf-8 b = 6 def f2(a): global b print(a) print(b) b = 9 f2(1) 输出: 1 6
输出正常.net
好,下面切入闭包的概念3d
以实例来讲明,假若有名为avg的函数,它不断返回系列值得平均值,好比下面方式使用
>>> avg(10) # 10/1 10.0 >>> avg(11) # (10+11)/2 10.5 >>> avg(12) # (10+11+12)/3 11.0
avg从何而来,又如何保存历史值呢?
咱们很快可能会想到类-属性的方式, average_oo.py
class Averager(): def __init__(self): self.series = [] def __call__(self, new_value): self.series.append(new_value) total = sum(self.series) return total/len(self.series)
Averager 的实例是可调用对象:
>>> avg = Averager() >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(12) 11.0
或以下函数式实现,average.py
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager
以下方式调用
>>> avg = make_averager() >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(12) 11.0
很明显,Averager 类的实例 avg 在哪里存储历史值很明显:self.series 实例属性。可是第二个示例中的 avg 函数在哪里寻找 series 呢?
注意,series 是 make_averager 函数的局部变量,由于那个函数的定义体中初始化了series:series = []。但是,调用 avg(10) 时,make_averager 函数已经返回了,而它的本地做用域也一去不复返了。
这时就要引出闭包里的一个关键概念,自由变量(free variable),这是一个技术术语,指未在本地做用域中绑定的变量。在 averager 函数中,series 是自由变量(free variable),见下图
也即闭包的概念就出来了:
一个函数返回了一个内部函数,该内部函数引用了外部函数的相关参数和变量,咱们把该返回的内部函数称为闭包(Closure)。(实际上这个引用的变量叫自由变量)
继续,python把局部变量,自由变量放在__code__属性里,见下
>>> avg.__code__.co_varnames ('new_value', 'total') >>> avg.__code__.co_freevars ('series',)
实际上,series 的绑定在返回的 avg 函数的__closure__ 属性中。avg.__closure__ 中的各个元素对应于 avg.__code__.co_freevars 中的一个名称。这些元素是 cell 对象,有个cell_contents 属性,保存着真正的值,以下:
>>> avg.__code__.co_freevars ('series',) >>> avg.__closure__ (<cell at 0x107a44f78: list object at 0x107a91a48>,) >>> avg.__closure__[0].cell_contents [10, 11, 12] # 可见前面的10,11,12在这里
综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义做用域不可用了,可是仍能使用那些绑定。
注意,只有嵌套在其余函数中的函数才可能须要处理不在全局做用域中的外部变量。
注意,在python2中自由变量的认定是有必定条件的,看下面例子
def make_averager(): count = 0 total = 0 def averager(new_value): count += 1 total += new_value return total / count return averager
这里咱们保存总值以及总个数,按理说ok,但执行时
>>> avg = make_averager() >>> avg(10) Traceback (most recent call last): ... UnboundLocalError: local variable 'count' referenced before assignment >>>
由于咱们在averager内部函数中给count赋值了,这会把count变为局部变量,total也同样
而上面的series=[]没遇到这个问题,是由于咱们没有给series赋值,只是调用append,是基于列表是可变对象的事实
但对数字,字符串,元组等不可变类型来讲,只能读取,不能更新。若是尝试绑定,例如count = count + 1,其实会隐式建立局部变量count,这样count就不是自由变量了,所以不会保存在闭包中。
python3中已经解决了这个问题,引入了nonlocal声明。它的做用是把变量标记为自由变量,即便在函数中为变量赋予新值了,也会变成自由变量。以下:
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count, total count += 1 total += new_value return total / count return averager
以上闭包的概念基本讲完了,接下来说装饰器
装饰器本质上就是一个函数,这个函数接收其余函数做为参数,并将其以一个新的修改后的函数进行替换并返回它。后来python提供@语法糖,使程序看起来更简洁。
看以下简单的例子,咱们定义2个普通函数,其中函数d1装饰给f1
# coding: utf-8 def d1(): print('deco d1') @d1 def f1(): print('func f1') f1()
此时执行会报错
Traceback (most recent call last): File "t.py", line 4, in <module> @d1 TypeError: d1() takes 0 positional arguments but 1 was given
因此此处对应了装饰器函数的第一点定义:装饰器本质是个函数,须要接收其余被装饰函数做为参数
咱们加上此参数
# coding: utf-8 def d1(func): print('deco d1') @d1 def f1(): print('func f1') f2()
执行仍是报错
deco d1 Traceback (most recent call last): File "t.py", line 8, in <module> f1() TypeError: 'NoneType' object is not callable
装饰器函数d1执行了,有deco d1输出,但下面的报错了,由于d1装饰器函数并无返回可执行的函数对象,这也就对应了第二点定义:并将其以一个新的修改后的函数进行替换并返回它
此处咱们加上返回
# coding: utf-8 def d1(func): print('deco d1') return func @d1 def f1(): print('func f1') f1()
执行以下:
deco d1 func f1
可见成功了
因此装饰函数的两点:
1. 以被装饰函数为参数
2. 须要返回可执行函数对象
通常装饰器写法
def decorator(func): def wrapper(*args, **kwargs): # before do something(like: add_log, cache, check..) res = func(*args, **kwargs) # end do something return res return wrapper
但此时返回的是包装后的函数,__name__属性不是原函数名称,要改变这个状态可经过python内置的 functools模块解决
from functools import wraps def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # before do something(like: add_log, cache, check..) res = func(*args, **kwargs) # end do something return res return wrapper
若是装饰器须要处理被装饰函数参数时,用inspect.getcallargs来处理会更友好,他会转变为字典形式
# coding: utf-8 import inspect from functools import wraps def decorator(func): @wraps(func) def wrapper(*args, **kwargs): func_args = inspect.getcallargs(func, *args, **kwargs) print(func_args) # before do something(like: add_log, cache, check..) res = func(*args, **kwargs) # end do something return res return wrapper @decorator def f1(c, e, *args, **kwargs): print('func f1') f1(2, 'a', 1, 2, 3, a='1', b='2')
输出
{'c': 2, 'e': 'a', 'args': (1, 2, 3), 'kwargs': {'a': '1', 'b': '2'}} func f1
装饰器还一个重要特性:
在加载模块时当即执行,且只执行一次。此特性长被用做函数注册或组件注册
# coding: utf-8 registry = {} def register(func): print('running register(%s)' % func) registry[func.__name__]=func return func @register def f1(): print('running f1') @register def f2(): print('running f2') @register def f3(): print('running f3') def main(): print('running main()') print('registry ->', registry) f1() f2() f3() registry.get('f1')() if __name__=='__main__': main()
看成脚本运行时,输出
E:\virtualenv\nowamagic_venv\coreTasker_env1\Scripts\python.exe E:/code/git/source/master/coreTasker/api/controllers/c.py running register(<function f1 at 0x0000000002712840>) running register(<function f2 at 0x0000000002990400>) running register(<function f3 at 0x0000000002990378>) running main() registry -> {'f1': <function f1 at 0x0000000002712840>, 'f2': <function f2 at 0x0000000002990400>, 'f3': <function f3 at 0x0000000002990378>} running f1 running f2 running f3 running f1
可见后面单独执行f1()时并无输出running register ..., 只在加载模块时初始化一次
咱们知道一个装饰器
@d def f(): pass 等同于 f = d(f)
多个就是叠加的关系
@d1 @d2 def f(): pass 等同于 f = d1(d2(f))
装饰器如何带参数?很简单,再把装饰器封装一层,返回装饰器
# coding: utf-8 import inspect from functools import wraps def decorator_with_param(args1='', args2=''): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): func_args = inspect.getcallargs(func, *args, **kwargs) print(func_args) # before do something(like: add_log, cache, check..) res = func(*args, **kwargs) # end do something return res return wrapper return decorator @decorator_with_param() def f1(c, e, *args, **kwargs): print('func f1') f1(2, 'a', 1, 2, 3, a='1', b='2') print(f1.__name__)
实际就是@后面的函数加上()后,就会调用它,若是这个返回是装饰器就能够
简化下就是下面这样
@d(p1, p2) def f(): pass 等同于 f = d(p1, p2)(f)
多个的状况
@d1(p1) @d2(p2) def f(): pass 等同于 f = d1(p1)(d2(p2)(f))
见下面
# coding: utf-8 def d1(func): def deco(*args, **kwargs): print('begin deco d1..........') res = func(*args, **kwargs) print('end deco d1..........') return res return deco def d2(func): def deco(*args, **kwargs): print('begin deco d2.......') res = func(*args, **kwargs) print('end deco d2.......') return res return deco @d1 @d2 def f1(c, e, *args, **kwargs): print('func f1') return 1 print(f1(2, 'a', 1, 2, 3, a='1', b='2')) print(f1.__name__)
返回
begin deco d1.......... begin deco d2....... func f1 end deco d2....... end deco d1.......... 1 deco
注意有点像递归的返回,并且每一个装饰器里func执行结果不返回的话,最后是取不到f1()执行结果的。
前面都是基于函数的,其实基于类的也是能够的,利用类的__init__,__call__
class Bold(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return '<b>' + self.func(*args, **kwargs) + '</b>' @Bold def hello(name): return 'hello %s' % name >>> hello('world') '<b>hello world</b>'
能够看到,类 Bold
有两个方法:
__init__()
:它接收一个函数做为参数,也就是被装饰的函数__call__()
:让类对象可调用,就像函数调用同样,在调用被装饰函数时被调用还可让类装饰器带参数:
class Tag(object): def __init__(self, tag): self.tag = tag def __call__(self, func): def wrapped(*args, **kwargs): return "<{tag}>{res}</{tag}>".format( res=func(*args, **kwargs), tag=self.tag ) return wrapped @Tag('b') def hello(name): return 'hello %s' % name
其实就是@Tag('b')会调用__call__返回wrapped
但若是类装饰器要装饰类里的方法呢?
# coding: utf-8 class Bold(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return '<b>' + self.func(*args, **kwargs) + '</b>' class H(object): @Bold def hello(self, name): return 'hello %s' % name h = H() print h.hello('jack') # 输出 Traceback (most recent call last): File "/Users/Teron/Code/Git/Personal/Test/e.py", line 16, in <module> print h.hello('jack') File "/Users/Teron/Code/Git/Personal/Test/e.py", line 7, in __call__ return '<b>' + self.func(*args, **kwargs) + '</b>' TypeError: hello() takes exactly 2 arguments (1 given)
可见报错了,缘由就是被@Bold包装的方法变成了unbound method
print h.hello print h.hello.func # 输出 ''' <__main__.Bold object at 0x1023e43d0> <function hello at 0x1023d4cf8> '''
这里的<function hello at 0x1023d4cf8>是须要两个参数的,一个self,一个name,咱们只传了一个,因此报错,下面这种方式调用就能够
print h.hello(h, 'jack') # 输出 ''' <b>hello jack</b> '''
但咱们不能更改类方法的调用方式呢?只能经过修改类装饰器,经过描述符
# coding: utf-8 class Bold(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return '<b>' + self.func(*args, **kwargs) + '</b>' def __get__(self, instance, owner): return lambda *args, **kwargs: '<b>' + self.func(instance, *args, **kwargs) + '<b>' @Bold def hellof(name): return 'hello %s' % name class H(object): @Bold def hello(self, name): return 'hello %s' % name print hellof('lucy') h = H() print h.hello('jack') # 输出 ''' <b>hello lucy</b> <b>hello jack<b> '''
以上__call__针对函数hellof,__get__针对类H里的hello方法
或者下面这样
# coding: utf-8 import types class Bold(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return '<b>' + self.func(*args, **kwargs) + '</b>' def __get__(self, instance, owner): if instance is None: return self else: return types.MethodType(self, instance) # 手动建立一个绑定方法 @Bold def hellof(name): return 'hello %s' % name class H(object): @Bold def hello(self, name): return 'hello %s' % name print hellof('lucy') h = H() print h.hello('jack') # 输出 ''' <b>hello lucy</b> <b>hello jack</b> '''
函数装饰器装饰在类方法中是否能够?
def Boldf(func): def warp(*args, **kwargs): return '<b>' + func(*args, **kwargs) + '</b>' return warp @Boldf def hellof(name): return 'hello %s' % name class H(object): @Boldf def hello(self, name): return 'hello %s' % name print hellof('lucy') h = H() print h.hello('jack') # 输出 ''' <b>hello lucy</b> <b>hello jack</b> '''
因此最好是用函数装饰器,但函数装饰器有自由变量的问题,这经过nonlocal解决