Bind a name to a value; rebind later if its meaning changes.
Sticky note pointing at a box - the note can move, but the box itself does not.
x -- points to --> object(42)
|
| reassign x = 'hello'
v
x -- now points to --> object('hello')
(old int is GC'd)
Python variables are NAMED REFERENCES to objects on the heap. Assignment rebinds the name, never mutates the original value (ints, strings, tuples are immutable). Dynamic typing means the same name can refer to a list, then a dict, then a generator - type hints help tools catch mismatches before runtime. Multiple names can share one object; mutating through one is visible to the other.
x = 42
x = 'hello'
print(type(x).__name__)
x is just a label; type() reflects the current object’s class.
a = [1, 2, 3]
b = a # b is NOT a copy
b.append(4)
print(a) # [1, 2, 3, 4]
b = a creates an alias, not a copy; both names point to the SAME list.
import sys
nums = [1, 2, 3]
print(sys.getrefcount(nums)) # 2 or 3 (incl. arg)
Variables are references; CPython keeps a refcount dropped to zero on last unbind.
Tying variable identity to type. Use is None only for None/True/False/sentinel singletons; reserve == for value comparison. CPython interns small ints (-5..256) and short strings, so is accidentally returns True for them at small scales - and incorrectly False at 257 or longer strings.
Names are references, not boxes. is for identity/sentinels; == for values. Remember mutables are SHARED by aliasing.
def f(x, buf=[]) creates one list reused by every call.buf=None sentinel and bind buf = [] inside the function body.def ...(... = []).redefined-builtin; Sentry groups the TypeError stack.list = [...] won over the builtin under LEGB lookup.list_ids and restart the worker.