在计算机科学中,闭包 又称 词法闭包 或 函数闭包,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即便已经离开了创造它的环境也不例外。闭包被普遍应用于函数式语言中。闭包
从上面这段话中能够看出闭包的两个重要条件是引用自由变量和函数,与闭包这个名称结合起来看,这个函数就像是一个包,而这个函数所引用的变量就比如函数这个包中封闭起来的东西,包中的东西被牢牢封闭在包中,函数所引用的变量也被与这个函数所绑定。app
首先来看两个概念 Nonlocal variable 和 Nested function函数
Nonlocal variable
是相对于某个函数来讲的,指的是这个函数所调用的在本函数做用域以外的变量,Nested function
指的被定义在一个函数(outer enclosing function)中的函数,这个nested function
能够调用包围它的做用域中的变量。code
看一个例子orm
def print_msg(msg): # outer enclosing function def printer(): # nested function print(msg) printer() >>> print_msg("Hello") Hello
在这个例子中函数printer
就是一个nested function
,而变量msg
就是一个nonlocal variable
。这里须要注意的是,printer
虽然能够访问msg
,可是不能够改变它,若是尝试更改会出现UnboundLocalError: local variable 'msg' referenced before assignment
。对象
def print_msg(msg): def printer(): msg += 'a' print(msg) printer() >>> print_msg("Hello") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in print_msg File "<stdin>", line 3, in printer UnboundLocalError: local variable 'msg' referenced before assignment local variable 'msg' referenced before assignment
若是必需要更改这个变量的值,在Python3中新引入的nonlocal
语句能够解决。资源
def print_msg(msg): def printer(): nonlocal msg msg += 'a' print(msg) printer() >>> print_msg("Hello") Helloa
在Python2中使用global
也可解决,可是global
会直接查找全局变量,而nonlocal
则是按优先级从本地-->全局
进行搜索。作用域
下面使外层函数(outer enclosing function)返回一个函数it
def print_msg(msg): def printer(): print(msg) return printer >>> another = print_msg("Hello") >>> another() Hello
将print_msg("Hello")
返回的函数赋值给another
,再调用another
函数时,发现已经离开了print_msg
函数的做用域,可是"Hello"
已经被绑定给another
,因此仍然可以正常调用,这就是Python中的闭包。
删除print_msg
以后,another
仍然可以正常调用。io
>>> del print_msg >>> print_msg("Hello") Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'print_msg' is not defined name 'print_msg' is not defined >>> another() Hello
当符合下面几个条件时就造成了闭包:
有一个Nested function
这个Nested function
访问了父函数做用域中的变量
父函数返回了这个Nested function
闭包主要运用在须要讲父函数做用域中的变量绑定到子函数的场景之中,在释放掉父函数以后子函数也不会受到影响。运用闭包能够避免对全局变量的使用。对于一个只有须要实现少数方法的类咱们也能够用闭包来替代,这样作能够减小资源的使用。
下面须要用类定义不一样动物的叫声
class Animal: def __init__(self, animal): self.animal = animal def sing(self, voice): return "{} sings {}".format(self.animal, voice) >>> dog = Animal("dog") >>> cow = Animal("cow") >>> dog.sing("wong") 'dog sings wong' >>> cow.sing("mow") cow sings mow'
用闭包替代
def make_sing(animal): def make_voice(voice): return "{} sings {}".format(animal, voice) return make_voice >>> dog = make_sing("dog") >>> dog("wong") 'dog sings wong' >>> cow = make_sing("cow") >>> cow("mow") 'cow sings mow'
闭包一般用来实现一个通用的功能,Python中的装饰器就是对闭包的一种应用,只不过装饰器中父函数的参数是一个函数,下面这个例子经过装饰器实现了在子函数执行先后输出提示信息。
def make_wrap(func): def wrapper(*args): print("before function") func(*args) print("after function") return wrapper @make_wrap def print_msg(msg): print(msg) >>> print_msg("Hello") before function Hello after function
装饰器也能够进行叠加
def make_another(func): def wrapper(*args): print("another begin") func(*args) print("another end") return wrapper @make_another @make_wrap def print_msg(msg): print(msg) >>> print_msg("Hello") another begin before function Hello after function another end
为了了解闭包的内部实现,须要用compile
命令得出相应的code object
>>> code_obj = compile("print_msg('Hello')", "", "single")
这里第一个参数是一个能够被exec
或 eval
解析的模块、语句或者表达式;
第二个参数是用来存放运行时错误的文件;
第三个选择single
模式,与前面第一个参数填写的表达式相匹配,若是第一个参数是表达式则须要用eval
模式,若是是模块则应该用exec
模式。
下面经过dis
讲code_obj
反编译成助记符
>>> dis.dis(code_obj) 1 0 LOAD_NAME 0 (print_msg) 2 LOAD_CONST 0 ('Hello') 4 CALL_FUNCTION 1 6 PRINT_EXPR 8 LOAD_CONST 1 (None) 10 RETURN_VALUE
Python3中经过__code__
访问函数的code object
(Python2中为func_code
)
>>> print_msg.__code__ <code object print_msg at 0x10d5c7300, file "<stdin>", line 1>
cell object
用来存储被多个做用域所引用的变量。
好比下面函数中msg
被print_msg
所引用,也被printer
所引用,因此msg
会被存在一个cell object
中
def print_msg(msg): def printer(): print(msg) return printer
查看其__closure__
属性能够验证咱们的想法
>>> print_msg("Hello").__closure__ (<cell at 0x10d121d38: str object at 0x10d4a6f48>,)
尽管这两个引用都被存在赞成个cell object
,可是他们仍然只在各自的做用域下做用。
首先反编译print_msg
>>> dis.dis(print_msg) 2 0 LOAD_CLOSURE 0 (msg) 2 BUILD_TUPLE 1 4 LOAD_CONST 1 (<code object printer at 0x10d5c7780, file "<stdin>", line 2>) 6 LOAD_CONST 2 ('print_msg.<locals>.printer') 8 MAKE_FUNCTION 8 10 STORE_FAST 1 (printer) 4 12 LOAD_FAST 1 (printer) 14 RETURN_VALUE
LOAD_CLOSURE 0 (msg)
将变量msg
进栈。
BUILD_TUPLE 1
将栈顶的元素取出,建立元组,并将该元组push进栈。
LOAD_CONST 1
从print_msg.__code__.co_consts[1]
中取出,为printer
的code object
的地址,将其push进栈。
LOAD_CONST 2
从print_msg.__code__.co_consts[2]
中取出,将其push进栈。
STORE_FAST 1
从栈顶取出以前建立的函数对象的地址信息赋给局部变量printer(局部变量名记录在__code__.co_varnames中)
__code__.co_varnames的内容为('msg','printer')
将变量msg(记录在__code__.co_cellvars[0])绑定栈顶的函数对象地址。
LOAD_FAST 1
将msg
的值压入栈。
RETURN_VALUE
返回栈顶。
能够看到在STORE_FAST 1
中将变量msg
绑定到了printer
函数,从而达到了闭包中内部函数访问外部函数变量的效果。