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_DEREF
、LOAD_DEREF
、MAKE_FUNCTION
和 LOAD_CLOSURE
,如果我编写没有函数和闭包的整个程序,我将无法理解这些指令。
我认为这些是使用闭包所需的说明。
但是 Python 如何做到这一点?它如何从封闭函数的局部变量 table 中捕获变量?在捕获变量之后它住在哪里?函数如何获取捕获变量的访问权限?
我想全面了解其工作原理。
提前致谢。
概述
Python 并不直接使用 变量 ,就像人们可能期望来自静态类型语言(如 C 或 Java 的方式一样,而是它使用 names 并用它们标记对象实例
在您的示例中,closure
只是具有该名称的函数的一个实例
这里真的是nonlocal
导致LOAD_CLOSURE
和BUILD_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_TUPLE
和 LOAD_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
对于 1
和 2
closure
和 closure_test.<locals>.closure
用于内部函数(位于某个内存地址)
print
字面上的打印功能
None
字面意思是 None
单例
特定 DIS 操作码
STORE_DEREF
将值放入插槽 i
LOAD_DEREF
从插槽 i
中检索值
MAKE_FUNCTION
在堆栈上创建一个新函数并将其放入插槽 i
LOAD_CLOSURE
就是这样做的,把它放在堆栈上 i
您可以通过 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?
我对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_DEREF
、LOAD_DEREF
、MAKE_FUNCTION
和 LOAD_CLOSURE
,如果我编写没有函数和闭包的整个程序,我将无法理解这些指令。
我认为这些是使用闭包所需的说明。
但是 Python 如何做到这一点?它如何从封闭函数的局部变量 table 中捕获变量?在捕获变量之后它住在哪里?函数如何获取捕获变量的访问权限?
我想全面了解其工作原理。
提前致谢。
概述
Python 并不直接使用 变量 ,就像人们可能期望来自静态类型语言(如 C 或 Java 的方式一样,而是它使用 names 并用它们标记对象实例
在您的示例中,closure
只是具有该名称的函数的一个实例
这里真的是nonlocal
导致LOAD_CLOSURE
和BUILD_TUPLE
按照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_TUPLE
和 LOAD_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
对于1
和2
closure
和closure_test.<locals>.closure
用于内部函数(位于某个内存地址)print
字面上的打印功能None
字面意思是None
单例
特定 DIS 操作码
STORE_DEREF
将值放入插槽i
LOAD_DEREF
从插槽i
中检索值
MAKE_FUNCTION
在堆栈上创建一个新函数并将其放入插槽i
LOAD_CLOSURE
就是这样做的,把它放在堆栈上i
您可以通过 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?