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.
examples/object/decorator/define_a_function_with_local.py1 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
examples/object/decorator/define_a_function_with_global.py1 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
examples/object/decorator/define_a_function_with_closure.py1 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 slotiof 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在任何地方都没用被定义.
examples/object/decorator/load_order.py1 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.