Fundamentals 19 min read

Understanding Python Bytecode and Variable Assignment Mechanics

This article examines Python's bytecode instructions, explains how common operations like variable assignment, multiple assignment, and comparison are implemented at the interpreter level, and illustrates the underlying mechanisms with detailed code examples and disassembled bytecode.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Understanding Python Bytecode and Variable Assignment Mechanics

The article explores the inner workings of Python's virtual machine by analyzing bytecode instructions and their corresponding logic, focusing on how variables are assigned and compared.

Common Bytecode Instructions

Typical operations such as loading constants, storing variables, and calling functions are demonstrated with a disassembled example:

<code>import dis

name = "古明地觉"

def foo():
    age = 16
    print(age)
    global name
    print(name)
    name = "古明地恋"

dis.dis(foo)
"""
  1           0 RESUME                0

  2           2 LOAD_CONST            1 (16)
              4 STORE_FAST            0 (age)

  3           6 LOAD_GLOBAL           1 (NULL + print)
              16 LOAD_FAST           0 (age)
              18 CALL                1
              26 POP_TOP

  5          28 LOAD_GLOBAL           1 (NULL + print)
              38 LOAD_GLOBAL         2 (name)
              48 CALL                1
              56 POP_TOP

  6          58 LOAD_CONST           2 ('古明地恋')
              60 STORE_GLOBAL        1 (name)
              62 RETURN_CONST        0 (None)
"""
</code>

The article then details each instruction, such as LOAD_CONST loading a constant and STORE_FAST creating a local variable.

Variable Assignment Details

For simple assignments like age = 16 , two bytecode instructions are generated: LOAD_CONST and STORE_FAST . More complex assignments involve additional operations.

Multiple Assignment and Swapping

The mechanics of statements like a, b = b, a are explained using the SWAP instruction:

<code>TARGET(SWAP) {
    // Get top of stack
    PyObject *top = stack_pointer[-1];
    // oparg indicates number of elements to swap
    PyObject *bottom = stack_pointer[-(2 + (oparg-2))];
    assert(oparg >= 2);
    // Swap elements
    stack_pointer[-1] = bottom;
    stack_pointer[-(2 + (oparg-2))] = top;
    DISPATCH();
}
</code>

For larger tuples, the interpreter builds a tuple with BUILD_TUPLE and then unpacks it using UNPACK_SEQUENCE :

<code>TARGET(BUILD_TUPLE) {
    PyObject **values = (stack_pointer - oparg);
    PyObject *tup = _PyTuple_FromArraySteal(values, oparg);
    STACK_SHRINK(oparg);
    STACK_GROW(1);
    stack_pointer[-1] = tup;
    DISPATCH();
}
</code>

Similarly, BUILD_LIST is used when the right‑hand side is a list.

Chain Assignment

Statements like a = b = c = 123 use the COPY instruction to duplicate the top of the stack before each STORE_NAME :

<code>TARGET(COPY) {
    // Get bottom element (same as top when only one element)
    PyObject *bottom = stack_pointer[-(1 + (oparg-1))];
    assert(oparg > 0);
    PyObject *top = Py_NewRef(bottom);
    STACK_GROW(1);
    stack_pointer[-1] = top;
    DISPATCH();
}
</code>

Identity vs Equality

The difference between is and == is illustrated with the corresponding bytecode instructions IS_OP and COMPARE_OP :

<code>TARGET(IS_OP) {
    PyObject *right = stack_pointer[-1];
    PyObject *left = stack_pointer[-2];
    int res = Py_Is(left, right) ^ oparg;
    Py_DECREF(left);
    Py_DECREF(right);
    PyObject *b = res ? Py_True : Py_False;
    STACK_SHRINK(1);
    stack_pointer[-1] = b;
    DISPATCH();
}

TARGET(COMPARE_OP) {
    PyObject *right = stack_pointer[-1];
    PyObject *left = stack_pointer[-2];
    PyObject *res = PyObject_RichCompare(left, right, oparg>>4);
    Py_DECREF(left);
    Py_DECREF(right);
    if (res == NULL) goto pop_2_error;
    STACK_SHRINK(1);
    stack_pointer[-1] = res;
    DISPATCH();
}
</code>

Examples show that a is b checks pointer equality, while a == b invokes rich comparison logic, which can be overridden by custom __eq__ methods.

Pitfalls

Chain assignment with mutable objects can cause unexpected shared references, as demonstrated with dictionaries. The article also discusses the special case of nan , where nan == nan is False, yet container membership tests treat nan as present due to identity checks.

Conclusion

The piece summarizes the examined bytecode instructions and the underlying principles of Python variable assignment, providing readers with a deeper understanding of how Python code is executed at the C level.

PythonBytecodeComparisonInterpreterVariable Assignment
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.