Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between load name and load global in python bytecode?

load name takes its argument and pushes onto the stack the value of the name stored by store name at the position indicated by the argument . load global does something similar, but there appears to be no store global in the bytecode. So what is the difference and how does load global work

like image 699
Sreeraj T A Avatar asked Aug 19 '17 15:08

Sreeraj T A


People also ask

How is Python bytecode different from Python source code?

When we execute a source code (a file with a . py extension), Python first compiles it into a bytecode. The bytecode is a low-level platform-independent representation of your source code, however, it is not the binary machine code and cannot be run by the target machine directly.

What is meant by bytecode in Python?

Bytecode is an intermediate language for the Python virtual machine that's used as a performance optimization. Instead of directly executing the human-readable source code, compact numeric codes, constants, and references are used that represent the result of compiler parsing and semantic analysis.

How is Python bytecode executed?

This is the job of the compiler to translate Python code to bytecode. The compiler stores bytecode in a code object, which is a structure that fully describes what a code block, like a module or a function, does. To execute a code object, CPython first creates a state of execution for it called a frame object.

How do you write bytecode in Python?

Byte Code is automatically created in the same directory as . py file, when a module of python is imported for the first time, or when the source is more recent than the current compiled file. Next time, when the program is run, python interpretator use this file to skip the compilation step.


2 Answers

There is a STORE_GLOBAL. Trigger it by using the global directive and assigning to that name:

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 are, afaik, instructions that mostly make sense when locals() != globals() and Python knows (by using a SymbolTable during compilation) that it needs to skip the locals dict altogether. This is something that is true inside functions.

In the top level scope, locals() == globals() so the compiler follows the normal look-up rule by using LOAD_NAME (unless you directly use global, see dis('global b; b = 40')).

like image 44
Dimitris Fasarakis Hilliard Avatar answered Sep 20 '22 13:09

Dimitris Fasarakis Hilliard


The difference between LOAD_NAME and LOAD_GLOBAL is where they search for a given name.

LOAD_NAME

When Python encounters a LOAD_NAME opcode:

  • It first searches in f_locals - the names local to the current frame object.
  • If it doesn't find the given name in f_locals, it then proceeds to search in f_globals - the global names of the frame object. These are the names in the surrounding scope of the frame object.
  • If doesn't find the name in f_globals, it then searches f_builtins. f_builtins is a dictionary of the builtin names Python uses.
  • If all of the above fails, Python raises a NameError.

Here the relevant C code where the virtual machine executes a LOAD_NAME instruction:

    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

When Python encounters a LOAD_GLOBAL opcode:

  • Python first searches for the name in f_globals - the names in surrounding scopes that the current frame object references.
  • If doesn't find the name in f_globals, it then searches f_builtins. f_builtins is a dictionary of the builtin names Python uses.
  • If all of the above fails, Python raises a NameError.

Here the relevant C code where the virtual machine executes a LOAD_GLOBAL instruction:

    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();
    }

So, what's the difference?

As you could probably see, the difference is that LOAD_GLOBAL skips straight to searching the global names of the frame object, whereas LOAD_NAME starts searching the local names and makes it way up. The LOAD_GLOBAL opcode is useful for the case where Python already knows that a name cannot be local, so it skips searching local names altogether.

Note: if your looking to learn more about how the Python virtual machine works, I looking over Byterun, a pure-python implementation of the CPython virtual machine. It also has an accompanying article by Allison Kaptur.

like image 127
Christian Dean Avatar answered Sep 19 '22 13:09

Christian Dean