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
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.
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.
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.
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.
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')
).
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:
f_locals
- the names local to the current frame object.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.f_globals
, it then searches f_builtins
. f_builtins
is a dictionary of the builtin names Python uses.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:
f_globals
- the names in surrounding scopes that the current frame object references.f_globals
, it then searches f_builtins
. f_builtins
is a dictionary of the builtin names Python uses.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();
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With