python 关闭是如何实现的?

How are python closures implemented?

我对python如何实现闭包感兴趣?

为了举例,考虑这个

def closure_test():
    x = 1
    def closure():
        nonlocal x
        x = 2
        print(x)
    return closure

closure_test()()

这里,函数 closure_test 有一个局部 x,嵌套函数 closure 捕获。

当我运行程序时,我得到以下输出

2

当我看到函数的反汇编时closure_test,

  2           0 LOAD_CONST               1 (1)
              2 STORE_DEREF              0 (x)

  3           4 LOAD_CLOSURE             0 (x)
              6 BUILD_TUPLE              1
              8 LOAD_CONST               2 (<code object closure at 0x7f14ac3b9500, file "<string>", line 3>)
             10 LOAD_CONST               3 ('closure_test.<locals>.closure')
             12 MAKE_FUNCTION            8 (closure)
             14 STORE_FAST               0 (closure)

  7          16 LOAD_FAST                0 (closure)
             18 RETURN_VALUE

Disassembly of <code object closure at 0x7f14ac3b9500, file "<string>", line 3>:
  5           0 LOAD_CONST               1 (2)
              2 STORE_DEREF              0 (x)

  6           4 LOAD_GLOBAL              0 (print)
              6 LOAD_DEREF               0 (x)
              8 CALL_FUNCTION            1
             10 POP_TOP
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

我看到了指令 STORE_DEREFLOAD_DEREFMAKE_FUNCTIONLOAD_CLOSURE,如果我编写没有函数和闭包的整个程序,我将无法理解这些指令。

我认为这些是使用闭包所需的说明。

但是 Python 如何做到这一点?它如何从封闭函数的局部变量 table 中捕获变量?在捕获变量之后它住在哪里?函数如何获取捕获变量的访问权限?

我想全面了解其工作原理。

提前致谢。

概述

Python 并不直接使用 变量 ,就像人们可能期望来自静态类型语言(如 C 或 Java 的方式一样,而是它使用 names 并用它们标记对象实例

在您的示例中,closure 只是具有该名称的函数的一个实例

这里真的是nonlocal导致LOAD_CLOSUREBUILD_TUPLE按照 and further in How to define free-variable in python?中的描述使用,指的是x,而不是内部函数按字面命名 closure

  3           4 LOAD_CLOSURE             0 (x)

关于nonlocal

对于你的情况,nonlocal 断言 x 存在于编译时不包括全局变量的外部作用域中,但实际上是多余的,因为它没有在其他地方使用 docs
最初我写的是由于重新声明这是多余的,但事实并非如此 - nonlocal 防止 重复使用名称,但 x 只是没有在其他任何地方显示,所以效果不明显
我添加了第三个示例,其中包含一个非常丑陋的生成器来说明效果

全局使用示例(注意SyntaxError是在编译时,而不是在运行时!)

>>> x = 3
>>> def closure_test():
...     def closure():
...         nonlocal x
...         print(x)
...     return closure
...
  File "<stdin>", line 3
SyntaxError: no binding for nonlocal 'x' found
>>> def closure_test():
...     def closure():
...         print(x)
...     return closure
...
>>> closure_test()()
3

与无效本地人相关的 SyntaxError 示例使用

>>> def closure_test():
...     def closure():
...         nonlocal x
...         x = 2
...         print(x)
...     return closure
...
  File "<stdin>", line 3
SyntaxError: no binding for nonlocal 'x' found
>>> def closure_test():
...     x = 1
...     def closure():
...         x = 2
...         nonlocal x
...         print(x)
...     return closure
...
  File "<stdin>", line 5
SyntaxError: name 'x' is assigned to before nonlocal declaration

使用nonlocal设置外部值的示例
(请注意,这是行为不端的,因为更正常的方法用 try:finally 包装 yield 显示 before closure 实际上被调用)

>>> def closure_test():
...     x = 1
...     print(f"x outer A: {x}")
...     def closure():
...         nonlocal x
...         x = 2
...         print(f"x inner: {x}")
...     yield closure
...     print(f"x outer B: {x}")
...
>>> list(x() for x in closure_test())
x outer A: 1
x inner: 2
x outer B: 2
[None]

没有 nonlocal 的原始示例(注意缺少 BUILD_TUPLELOAD_CLOSURE!)

>>> def closure_test():
...     x = 1
...     def closure():
...         x = 2
...         print(x)
...     return closure
...
>>>
>>> import dis
>>> dis.dis(closure_test)
  2           0 LOAD_CONST               1 (1)
              2 STORE_FAST               0 (x)

  3           4 LOAD_CONST               2 (<code object closure at 0x10d8132f0, file "<stdin>", line 3>)
              6 LOAD_CONST               3 ('closure_test.<locals>.closure')
              8 MAKE_FUNCTION            0
             10 STORE_FAST               1 (closure)

  6          12 LOAD_FAST                1 (closure)
             14 RETURN_VALUE

Disassembly of <code object closure at 0x10d8132f0, file "<stdin>", line 3>:
  4           0 LOAD_CONST               1 (2)
              2 STORE_FAST               0 (x)

  5           4 LOAD_GLOBAL              0 (print)
              6 LOAD_FAST                0 (x)
              8 CALL_FUNCTION            1
             10 POP_TOP
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

关于字节码和一个简单的比较

减少你的例子以删除所有的名字,这很简单

>>> import dis
>>> dis.dis(lambda: print(2))
 ​1           0 LOAD_GLOBAL              0 (print)
             ​2 LOAD_CONST               1 (2)
             ​4 CALL_FUNCTION            1
             ​6 RETURN_VALUE

字节码的其余部分只是移动名称

  • x 对于 12
  • closureclosure_test.<locals>.closure 用于内部函数(位于某个内存地址)
  • print字面上的打印功能
  • None 字面意思是 None 单例

特定 DIS 操作码


您可以通过 dis.show_code()

查看常量、名称和自由变量
>>> dis.show_code(closure_test)
Name:              closure_test
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  1
Stack size:        3
Flags:             OPTIMIZED, NEWLOCALS
Constants:
  ​0: None
  ​1: 1
  ​2: <code object closure at 0x10db282f0, file "<stdin>", line 3>
  ​3: 'closure_test.<locals>.closure'
Variable names:
  ​0: closure
Cell variables:
  ​0: x

在闭包本身挖掘

>>> dis.show_code(closure_test())  # call outer
Name:              closure
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        2
Flags:             OPTIMIZED, NEWLOCALS, NESTED
Constants:
  ​0: None
  ​1: 2
Names:
  ​0: print
Free variables:
  ​0: x
>>> dis.show_code(lambda: print(2))
Name:              <lambda>
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        2
Flags:             OPTIMIZED, NEWLOCALS, NOFREE
Constants:
  ​0: None
  ​1: 2
Names:
  ​0: print

使用 Python 3.9.10

其他相关问题

  • Python nonlocal statement
  • How does exec work with locals?