5. 查找对象
Python 在执行的过程中, 是怎么根据名字来查找变量的. 为了搞清楚这个问题, 我们写三段代码, 分别是 Listing 5.1, Listing 5.2 和 Listing 5.3.
def function():
a = 1
return a
a = 1
def function():
return a
def get_function():
a = 1
def function():
return a
return function
这三段代码的反汇编分别是 Python Disassembly 5.1, Python Disassembly 5.2 和 Python Disassembly 5.3.
1 0 LOAD_CONST 0 (<code object function at 0x7f45c9d6d660, file "<disassembly>", line 1>)
2 LOAD_CONST 1 ('function')
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (function)
8 LOAD_CONST 2 (None)
10 RETURN_VALUE
1 0 LOAD_CONST 0 (1)
2 STORE_NAME 0 (a)
3 4 LOAD_CONST 1 (<code object function at 0x7f45caf5ea80, file "<disassembly>", line 3>)
6 LOAD_CONST 2 ('function')
8 MAKE_FUNCTION 0
10 STORE_NAME 1 (function)
12 LOAD_CONST 3 (None)
14 RETURN_VALUE
1 0 LOAD_CONST 0 (<code object get_function at 0x7f45c9da43a0, file "<disassembly>", line 1>)
2 LOAD_CONST 1 ('get_function')
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (get_function)
8 LOAD_CONST 2 (None)
10 RETURN_VALUE
根据 Listing 5.1 和 Python Disassembly 5.1 可以看出, 在引用
a
时, Python 使用的是LOAD_FAST
.根据 Listing 5.2 和 Python Disassembly 5.2 可以看出, 在引用
a
时, Python 使用的是LOAD_GLOBAL
.根据 Listing 5.3 和 Python Disassembly 5.3 可以看出, 在引用
a
时, Python 使用的是LOAD_DEREF
.
那么问题来了, LOAD_FAST
, LOAD_GLOBAL
和 LOAD_DEREF
各是做什么的呢? 这个在 Python 的官方文档中是有说明的:
LOAD_FAST(var_num)
: Pushes a reference to the localco_varnames[var_num]
onto the stack.LOAD_GLOBAL(namei)
: Loads the global namedco_names[namei]
onto the stack.LOAD_DEREF(i)
: Loads the cell contained in sloti
of the cell and free variable storage. Pushes a reference to the object the cell contains on the stack.
其中 LOAD_FAST(var_num)
和 LOAD_GLOBAL(namei)
都很好理解, 分别是加载局部变量和加载全局变量. 我们注意到, 在 Python Disassembly 5.3 中除了 LOAD_DEREF(i)
还有一个特殊的操作 STORE_DEREF(i)
, 关于这个操作在 Python 的官方文档中也有说明:
STORE_DEREF(i)
: Stores TOS into the cell contained in slot i of the cell and free variable storage.
实际上 LOAD_DEREF(i)
和 STORE_DEREF(i)
这两个操作就是用来实现闭包特性的.
那什么时候 Python 会使用闭包呢? 为了搞清楚这个问题, 我们又写了一段代码, 如 Listing 5.4 所示, 其反汇编代码如 Python Disassembly 5.4 所示.
a, b, c = 1, 1, 1
def get_function():
b, c = 1, 1
def function():
c = 1
return a, b, c, d
return function
在 Listing 5.4 第 11 行中的 4 个变量:
a
只在全局被定义.b
在全局以及函数get_function
内部被定义.c
在全局, 函数get_function
, 以及function
内部被定义.d
在任何地方都没用被定义.
1 0 LOAD_CONST 0 ((1, 1, 1))
2 UNPACK_SEQUENCE 3
4 STORE_NAME 0 (a)
6 STORE_NAME 1 (b)
8 STORE_NAME 2 (c)
3 10 LOAD_CONST 1 (<code object get_function at 0x7f45caf6bf50, file "<disassembly>", line 3>)
12 LOAD_CONST 2 ('get_function')
14 MAKE_FUNCTION 0
16 STORE_NAME 3 (get_function)
18 LOAD_CONST 3 (None)
20 RETURN_VALUE
通过 Python Disassembly 5.4, 即对 Listing 5.4 的反编译代码可以看出:
a
使用的是LOAD_GLOBAL
,b
使用的是LOAD_DEREF
,c
使用的是LOAD_FAST
,d
使用的是LOAD_GLOBAL
.
这样的话, 我们可以得出 Python 根据变量名字查找变量值的优先级.
Python 会优先使用局部变量;
如果在局部变量中找不到, 会在闭包中进行查找;
如果在局部变量和闭包中都找不到, 则在全局变量中进行查找.
Hint
Python 选择使用何种方式来查找变量是在运行前就已经确定了.
Hint
我们注意到, 变量 d
并没有在任何地方被定义, 但是 Python 也会使用 LOAD_GLOBAL
查找其值. LOAD_FAST
可以看作是一种兜底策略, 如果在全局当中也找不到, 则抛出 NameError
.