zoukankan      html  css  js  c++  java
  • 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 'dx01x00x89x00x00dx02x00x89x01x00x87x00x00x87x01x00dx03x00x86x00x00}x00x00|x00x00S'
     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

  • 相关阅读:
    java 字节流与字符流的区别
    什么是缓冲区
    java流输入输出
    Apache安装配置
    Maven学习
    Redis
    数据结构与算法
    pig ERROR 2997: Encountered IOException. File or directory null does not exist.
    hadoop学习路线(转)
    86标准SQL与92标准SQL用法区别
  • 原文地址:https://www.cnblogs.com/gsblog/p/3361929.html
Copyright © 2011-2022 走看看