5. 查找对象

Python 在执行的过程中, 是怎么根据名字来查找变量的. 为了搞清楚这个问题, 我们写三段代码, 分别是 Listing 5.1, Listing 5.2Listing 5.3.

Listing 5.1 examples/object/decorator/define_a_function_with_local.py
def function():
    a = 1
    return a
Listing 5.2 examples/object/decorator/define_a_function_with_global.py
a = 1

def function():
    return a
Listing 5.3 examples/object/decorator/define_a_function_with_closure.py
def get_function():
    a = 1

    def function():
        return a
    return function

这三段代码的反汇编分别是 Python Disassembly 5.1, Python Disassembly 5.2Python Disassembly 5.3.

Python Disassembly 5.1 examples/object/decorator/define_a_function_with_local.py
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
Python Disassembly 5.2 examples/object/decorator/define_a_function_with_global.py
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
Python Disassembly 5.3 examples/object/decorator/define_a_function_with_closure.py
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

那么问题来了, LOAD_FAST, LOAD_GLOBALLOAD_DEREF 各是做什么的呢? 这个在 Python 的官方文档中是有说明的:

  • LOAD_FAST(var_num): Pushes a reference to the local co_varnames[var_num] onto the stack.

  • LOAD_GLOBAL(namei): Loads the global named co_names[namei] onto the stack.

  • LOAD_DEREF(i): Loads the cell contained in slot i 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 所示.

Listing 5.4 examples/object/decorator/load_order.py
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 在任何地方都没用被定义.

Python Disassembly 5.4 examples/object/decorator/load_order.py
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.