Python字节码中load name和load global有什么区别?

7

load name命令需要一个参数,并将store name所存储的名称对应位置的值推入堆栈。load global命令也类似,但是字节码中没有store global命令。那么它们之间有什么区别,load global又是如何工作的呢?

2个回答

7
LOAD_NAMELOAD_GLOBAL 的区别在于它们搜索给定 name 的位置。

LOAD_NAME

当 Python 遇到 LOAD_NAME 操作码时:
  • 首先在当前帧对象的局部名字中搜索 f_locals
  • 如果在 f_locals 中找不到给定的名称,则继续搜索 f_globals,即帧对象的全局名称。这些是帧对象所在范围内的名称。
  • 如果在 f_globals 中找不到该名称,则搜索 f_builtinsf_builtins 是 Python 使用的内置名称的字典。
  • 如果所有上述方法都失败,则 Python 引发一个 NameError
下面是虚拟机执行 LOAD_NAME 指令的相关 C 代码:
    TARGET(LOAD_NAME) {
        PyObject *name = GETITEM(names, oparg);
        PyObject *locals = f->f_locals;
        PyObject *v;
        if (locals == NULL) {
            PyErr_Format(PyExc_SystemError,
                         "no locals when loading %R", name);
            goto error;
        }
        if (PyDict_CheckExact(locals)) {
            v = PyDict_GetItem(locals, name);
            Py_XINCREF(v);
        }
        else {
            v = PyObject_GetItem(locals, name);
            if (v == NULL) {
                if (!PyErr_ExceptionMatches(PyExc_KeyError))
                    goto error;
                PyErr_Clear();
            }
        }
        if (v == NULL) {
            v = PyDict_GetItem(f->f_globals, name);
            Py_XINCREF(v);
            if (v == NULL) {
                if (PyDict_CheckExact(f->f_builtins)) {
                    v = PyDict_GetItem(f->f_builtins, name);
                    if (v == NULL) {
                        format_exc_check_arg(
                                    PyExc_NameError,
                                    NAME_ERROR_MSG, name);
                        goto error;
                    }
                    Py_INCREF(v);
                }
                else {
                    v = PyObject_GetItem(f->f_builtins, name);
                    if (v == NULL) {
                        if (PyErr_ExceptionMatches(PyExc_KeyError))
                            format_exc_check_arg(
                                        PyExc_NameError,
                                        NAME_ERROR_MSG, name);
                        goto error;
                    }
                }
            }
        }
        PUSH(v);
        DISPATCH();
    }

LOAD_GLOBAL

当Python遇到LOAD_GLOBAL操作码时:

  • 首先,Python会在当前帧对象引用的周围作用域中搜索名称f_globals
  • 如果在f_globals中找不到该名称,则会在f_builtins中进行搜索。 f_builtins是Python使用的内置名称字典。
  • 如果所有上述步骤都失败了,Python会引发一个NameError错误。

这里是虚拟机执行LOAD_GLOBAL指令的相关C代码:

    TARGET(LOAD_GLOBAL) {
        PyObject *name = GETITEM(names, oparg);
        PyObject *v;
        if (PyDict_CheckExact(f->f_globals)
            && PyDict_CheckExact(f->f_builtins))
        {
            v = _PyDict_LoadGlobal((PyDictObject *)f->f_globals,
                                   (PyDictObject *)f->f_builtins,
                                   name);
            if (v == NULL) {
                if (!_PyErr_OCCURRED()) {
                    /* _PyDict_LoadGlobal() returns NULL without raising
                     * an exception if the key doesn't exist */
                    format_exc_check_arg(PyExc_NameError,
                                         NAME_ERROR_MSG, name);
                }
                goto error;
            }
            Py_INCREF(v);
        }
        else {
            /* Slow-path if globals or builtins is not a dict */

            /* namespace 1: globals */
            v = PyObject_GetItem(f->f_globals, name);
            if (v == NULL) {
                if (!PyErr_ExceptionMatches(PyExc_KeyError))
                    goto error;
                PyErr_Clear();

                /* namespace 2: builtins */
                v = PyObject_GetItem(f->f_builtins, name);
                if (v == NULL) {
                    if (PyErr_ExceptionMatches(PyExc_KeyError))
                        format_exc_check_arg(
                                    PyExc_NameError,
                                    NAME_ERROR_MSG, name);
                    goto error;
                }
            }
        }
        PUSH(v);
        DISPATCH();
    }

那么,它们有何不同?

你可能已经看到了,区别在于LOAD_GLOBAL直接跳转到搜索帧对象的全局名称,而LOAD_NAME从本地名称开始搜索并向上移动。如果Python已知某个名称不能是本地名称,则LOAD_GLOBAL操作码非常有用,因此完全跳过搜索本地名称。

注意:如果您想了解有关Python虚拟机如何工作的更多信息,请查看Byterun,这是CPython虚拟机的纯Python实现。 Allison Kaptur还撰写了一篇相关文章


载入名称和载入全局。 - Sreeraj T A
@SreerajTA 感谢您提醒。我已经编辑了我的答案。 - Christian Dean

5

有一个名为STORE_GLOBAL的变量。通过使用global指令并分配给该名称来触发它:

def foo():
    global a 
    a = 3

dis(foo)
  3           0 LOAD_CONST               1 (3)
              2 STORE_GLOBAL             0 (a)
              4 LOAD_CONST               0 (None)
              6 RETURN_VALUE
LOAD_GLOBAL/STORE_GLOBAL是指令,当locals() != globals()并且Python在编译时(使用SymbolTable)知道它需要完全跳过locals字典时,它才有意义。这只适用于函数内部。
在顶级范围内,locals() == globals(),因此编译器遵循正常的查找规则,使用LOAD_NAME(除非您直接使用global,请参见dis('global b; b = 40'))。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接