Python闭包

1、概述

闭包是在其词法上下文中引用自由变量的函数。

>>> def foo():
...     m=3
...     n=5
...     def bar():
...         a=4
...         return m+n+a
...     return bar
... 
>>> foo()
<function bar at 0x7f9eb9549848>
>>> bar=foo()
>>> bar()
12

说明:

bar在foo函数的代码块中定义。bar是foo的内部函数。在bar的局部作用域中可以直接访问foo局部作用域中定义的m、n变量。这种内部函数可以使用外部函数变量的行为就叫闭包。

 

python中闭包表示内部函数,由一个变量指代,而这个变量对于外层包含的函数而言是本地变量。

2、code object

code object是python代码经过编译后的对象,它用来存储一些与代码有关的信息以及bytecode。

>>> code_obj = compile('sum([1,2,3])','','single')
>>> 
>>> exec(code_obj)
6
>>> 
>>> dis.dis(code_obj)
  1           0 LOAD_NAME                0 (sum)
              3 LOAD_CONST               0 (1)
              6 LOAD_CONST               1 (2)
              9 LOAD_CONST               2 (3)
             12 BUILD_LIST               3
             15 CALL_FUNCTION            1
             18 PRINT_EXPR          
             19 LOAD_CONST               3 (None)
             22 RETURN_VALUE        
>>> 

上述演示如何通过编译产生code object以及使用exec运行该代码,和使用dis方便地查看字节码。

 

3、cell object

cell对象的引入,是为了实现被多个作用域引用的变量。对每一个这样的变量,都用一个cell对象来保存其值。

 

拿之前的示例来说,m和n既在foo函数的作用域中被引用,又在bar函数的作用域中被引用,所以m,n引用的值,都会在一个cell对象中。

可以通过内部函数的__closure__或者func_closure特性查看cell对象

>>> bar.func_closure
(<cell at 0x7f9eb954fad0: int object at 0x6092f8>, <cell at 0x7f9eb954fb78: int object at 0x6092c8>)

这两个int型的cell分别存储了m和n的值

 

无论在外部函数中定义,还是在内部函数中调用,引用的指向都是cell对象。

注:内部函数无法修改cell对象中的值,如果尝试修改m的值,编译器会认为m是函数bar的局部变量,同时foo代码块中的m也会被认为是函数foo的局部变量,就会再把m认作闭包变量,两个m分别在各自的作用域下起作用。

 

4、闭包分析

 1 >>> foo.func_code.co_code 
 2 'd\x01\x00\x89\x00\x00d\x02\x00\x89\x01\x00\x87\x00\x00\x87\x01\x00d\x03\x00\x86\x00\x00}\x00\x00|\x00\x00S'
 3 >>> code_obj=foo.func_code.co_code  
 4 >>> dis.dis(code_obj)             
 5           0 LOAD_CONST          1 (1)
 6           3 STORE_DEREF         0
 
7
6 LOAD_CONST 2 (2) 8 9 STORE_DEREF 1
9
12 LOAD_CLOSURE 0 10 15 LOAD_CLOSURE 1 11 18 LOAD_CONST 3 (3) 12 21 MAKE_CLOSURE 0 13 24 STORE_FAST 0 (0)

14 27 LOAD_FAST 0 (0) 15 30 RETURN_VALUE
2          0 LOAD_CONST              1 (3)
            3 STORE_DEREF               0 (m)

3          6 LOAD_CONST              2 (5)
            9 STORE_DEREF               1 (n)

4          12 LOAD_CLOSURE         0 (m)
            15 LOAD_CLOSURE         1 (n)
            18 BUILD_TUPLE              2
            21 LOAD_CONST             3 (<code object bar at 018D9848, file "<pyshell#1>", line 4>)
            24 MAKE_CLOSURE         0
            27 STORE_FAST               0 (bar)

7          30 LOAD_FAST                 0 (bar)
            33 RETURN_VALUE    

LOAD_CONST 1 (3) :
将foo.func_code.co_consts [1] 的值"3"压入栈。

STORE_DEREF 0 (m) :
从栈顶Pop出"3"包装成cell对象存入cell与自由变量的存储区的第0槽。
将cell对象的地址信息赋给变量m(闭包变量名记录在func_code.cellvars)。
func_code.cellvars的内容为('m', 'n')

LOAD_CLOSURE 0 (m) :
将变量m的值压入栈,类似如下信息:
<cell at 0x01D572B0: int object at 0x0180D6F8>

LOAD_CLOSURE 1 (n) :
类似变量m的处理,不在累述。

当前栈区状态:

 
1 <cell at 0x01D572B0: int object at 0x0180D6F8>
2 <cell at 0x01D86510: int object at 0x0180D6E0>
3


BUILD_TUPLE 2 :
将栈顶的两项取出,创建元组,并将该元组压入栈。

LOAD_CONST 3 :
从foo.func_code.co_consts [3] 取出,该项为内部函数bar的code object的地址,将其压入栈
<code object bar at 018D9848, file "<pyshell#1>", line 4>

栈区状态:

 
1 <code object bar at 018D9848, file "<pyshell#1>", line 4>
2 (<cell at 0x01D572B0: int object at 0x0180D6F8>, <cell at 0x01D86510: int object at 0x0180D6E0>)
3


MAKE_CLOSURE 0 :
创建一个函数对象,将位于栈顶的code object(bar函数的code)地址信息赋
给该函数对象的func_code特性;
将栈顶第二项(包含cell对象地址的元组)赋给该函数对象的func_closure特性;
最后将该函数对象地址信息压入栈。

STORE_FAST 0 (bar) :
从栈顶取出之前创建的函数对象的地址信息赋给局部变量bar(局部变量名记录在func_code.co_varnames中)
func_code.co_varnames的内容为('bar',)
将变量bar(记录在func_code.cellvars [0] )绑定栈顶的函数对象地址。
LOAD_FAST 0 (bar) :
将变量bar的值压入栈。

RETURN_VALUE
返回栈顶项,print bar可以看到<function bar at 0x01D899F0>

  • 再分析bar函数就简单了
5           0 LOAD_CONST            1 (4)
             3 STORE_FAST               0 (a)

6           6 LOAD_DEREF               0 (m)
             9 LOAD_DEREF               1 (n)
             12 BINARY_ADD          
             13 LOAD_FAST               0 (a)
             16 BINARY_ADD          
             17 RETURN_VALUE        

重点是LOAD_DEREF,该方法主要是将cell对象中的object内容压入栈。大致过程如下:

根据变量m的值找到包装在cell内的int object的地址信息
m的值:<cell at 0x01D572B0: int object at 0x0180D6F8>

根据地址取出int值,压入栈。

http://www.cnblogs.com/ChrisChen3121/p/3208119.html

 

posted @ 2013-10-10 18:29  小郭学路  阅读(300)  评论(0编辑  收藏  举报