Python 闭包
闭包(closure)是函数式编程中重要的语法结构,可以提高了代码的可重用性(reusability)。不同的语言实现闭包的方式不同,Python 是以函数对象为基础,为闭包这一语法结构提供支持的。
函数对象
函数在 Python 中是第一类对象,即函数可以作为一个变量的值,也可以作为另一个函数的参数或返回值。除此之外,函数还可以嵌套定义。需要注意的是,函数对象也有其作用域。
把函数当作数据处理时,它将显式地携带与定义该函数的周围环境的相关信息。这将影响到函数中自由变量的绑定方式。例如,在 foo.py 文件中定义了一个变量 x
和 一个函数 callf
:
# foo.py
x = 42 # a global variable define in foo.py
def callf(func):
return func()
现在观察下面这个例子的行为:
>>> import foo
>>> x = 37
>>> def helloworld():
... return "Hello World. x is %d" % x
...
>>> foo.callf(helloworld) # 传递一个函数作为参数
'Hello World. x is 37'
>>>
在这个例子中,函数helloworld()
使用的 x
的值是在与它相同的环境中定义的(37
),即使 helloworld()
实际上是在 foo.py 文件中调用的而且 foo.py 也定义了一个变量 x
。
闭包的概念
维基百科上对闭包(Closure)的解释如下:
闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
对于嵌套函数来说,如果一个内部函数对外部作用域中的变量进行了引用,那么内部函数就被认为是闭包(closure)。内部函数由一个名字(变量)来指代,而这个名字(变量)对于“外层”包含它的函数而言,是本地变量。使用嵌套函数时,闭包将捕获内部函数执行所需的整个环境。
所有函数都拥有两个与闭包相关的重要属性:
- 指向了定义该函数的全局命名空间的
__globals__
属性,这始终对应于定义函数的闭包模块。 __closure__
属性,包含函数引用的除全局变量之外的自由变量信息。
x = 10
def wrap(fun):
a = 10
def call(*args, **kwargs):
return fun(*args, **kwargs) + a
return call
@wrap
def helloword():
return 10
print helloword.__globals__
# {'helloword': <function call at 0x02AD82B0>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:/exercise/ReportLibTest/closure.py', '__package__': None, 'x': 10, 'wrap': <function wrap at 0x02A10BF0>, '__name__': '__main__', '__doc__': None}
print helloword.__closure__
# (<cell at 0x02AD6190: int object at 0x029C7874>, <cell at 0x02AD61B0: function object at 0x02AD82F0>)
print helloword.__closure__[0].cell_contents
# 10
闭包的实例
闭包(Closure)最常见的应用场景是装饰器(Decorator)。在下面的例子中,嵌套函数
wrapprr
和它引用的外部函数的参数func
组成了一个闭包。def decorator_func(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @decorator_func def func(name): print 'my name is', name
如果要编写惰性求值(lazy evaluation)或延迟求值的代码,闭包和嵌套函数特别有用。例如:
from urllib import urlopen def page(url): def get(): return urlopen(url).get() return get
在这个例子中,
page()
函数实际上并不执行任何有意义的计算。它只会创建和返回函数get()
,调用该函数时才会获取 Web 页面的内容。因此,get()
函数中执行的计算实际上延迟到了程序后面对get()
求值的时候。例如:python = page('http://www.python.org') jython = page('http://www.jython.org') print(python) # <function get at 0x95d5f0> print(jython) # <function get at 0x9735f0> pydata = python() # 获取 http://www.python.org 页面的内容 jydata = jython() # 获取 http://www.jython.org 页面的内容
在这个例子中,两个变量
python
和jython
实际上是get()
函数的两个版本,隐式地携带在创建get()
函数时定义的外部变量的值。如果需要在一系列函数调用中保存某个状态,使用闭包是种非常有效的方式。例如考虑下面运行的一个简单的计数器代码:
def countdown(n): count = [n] # 注意: count 是一个可变对象 def next(): r = count[0] count[0] -= 1 return r return next """ 用例 """ next = countdown(10) while True: v = next() # 获得下一个值 if not v: break
在这段代码中,闭包用于保存内部计数器的值
count[0]
。每次调用内部函数next()
时,它都更新并返回这个计数器的前一个值。
使用闭包的注意事项
在闭包中不能修改外部作用域的变量的值,这与 Python 的作用域规则有关。例如:
def foo(): m = 0 def foo1(): m = 1 # 定义了一个局部变量,而不是修改了外部作用域的变量的值 print m print m foo1() print m foo() """ 运行结果 0 1 0 """
下面的例子是在 Python 中使用闭包时可能遇到的一段经典的错误代码:
def foo(): a = 1 def foo1(): a = a + 1 # UnboundLocalError return a return foo1
注意:在 Python3 中可以使用
nonlocal
显式的申明一个变量不是闭包的局部变量。只有对函数对象进行求值时,才会去找函数体内的变量的值。例如:
def foo(): foo_list = [] for i in range(3): def inner_foo(x): print(x + i) foo_list.append(inner_foo) return foo_list for f in foo(): print f(2) """ 结果是 4 4 4,而不是 2 3 4。 """