CPython Internals and Advanced Semantics
Names and Binding
Names, Not Variables
In JavaScript's V8 engine, declaring a primitive variable like let x = 10; creates a storage location—a box—and places the value 10 inside it. Assignment copies this value. Python's model is fundamentally different. The term 'variable' is a misnomer; we work with 'names' and 'objects'. Every piece of data is an object, a structure in memory with a value, a type, and an identity. A statement like x = 10 doesn't assign a value. Instead, it binds the name x to the integer object that holds the value 10. The name is just a label pointing to an object.
If you then execute y = x, you are not copying anything. You are simply attaching a second label, y, to the very same integer object that x currently points to. If x is later rebound to a different object (e.g., x = "hello"), y remains bound to the original integer object. This universal reference-based binding applies to all types, eliminating the primitive/object duality seen in JavaScript.
This has profound consequences for mutability. When a name is bound to a mutable object like a list, any modification to that object is visible through all names bound to it. There is only one object, and all names are simply aliases for it.
# a and b are two names for the same list object
a = [1, 2, 3]
b = a
# Modifying the list via the name 'b'
b.append(4)
# The change is reflected when accessing via 'a'
print(a) # Output: [1, 2, 3, 4]
print(b) # Output: [1, 2, 3, 4]
# Check if they point to the exact same object
print(id(a) == id(b)) # Output: True
Namespaces and Scope
So where does Python store these name-to-object bindings? The answer is in namespaces, which are implemented as dictionaries. Each module, function call, and class creates its own namespace. When you access a name, Python searches for it through a specific hierarchy of scopes. This search order is defined by the an acronym for Local, Enclosing, Global, and Built-in.
Python starts by looking in the innermost local scope. If the name isn't found, it moves to the scope of any enclosing functions. If it's still not found, it checks the global (module-level) scope. Finally, if all else fails, it checks the built-in scope. If the name isn't found there, a NameError is raised.
x = "I am global"
def outer_func():
# The name 'x' is not defined in the local scope.
# Python will look in the enclosing scope (global, in this case).
print(f"outer_func sees: {x}") # Accesses global x
def inner_func():
# This assignment creates a *new* local variable 'x'.
# It does not affect the global 'x'.
x = "I am local"
print(f"inner_func sees: {x}") # Accesses its local x
inner_func()
print(f"outer_func still sees: {x}") # Accesses global x
outer_func()
# Output:
# outer_func sees: I am global
# inner_func sees: I am local
# outer_func still sees: I am global
A Note on Interning
To optimize memory and performance, pre-allocates and reuses certain immutable objects. This process is called interning. For example, integers from -5 to 256 are singletons. No matter how many times you bind a name to the integer 100, you are always pointing to the exact same object in memory.
The same optimization applies to short strings that look like identifiers. When the compiler encounters these strings, it ensures only one copy of each is stored.
# For small integers, 'a' and 'b' point to the same object.
a = 100
b = 100
print(f"100 is 100: {a is b}") # True
# For larger integers, they are different objects.
c = 257
d = 257
print(f"257 is 257: {c is d}") # False
# String interning can be implementation-dependent
# but often applies to simple strings.
e = "hello_world"
f = "hello_world"
print(f"'hello_world' is 'hello_world': {e is f}") # Often True
This behavior is an implementation detail, not a language guarantee. You should always use == for testing value equality and is only for testing object identity, but understanding interning helps clarify why is sometimes works for values when you might not expect it.
In Python's data model, what occurs during the statement x = 10?
What is the primary function of a namespace in Python?
This model of names, objects, and namespaces is the foundation of Python's execution. It explains why function arguments behave like they are passed by 'assignment' and how Python manages memory.