Variables and Dynamic Typing
|
This section documents the current Python release line as published at the official Python documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Python variables are not typed storage slots — they are names bound to objects. The object carries the type; the name is just a label that can be re-pointed at any time.
Names, Not Slots
Assignment binds a name to an object — it never copies the object. Several names can be bound to the same object at once, and a name can be rebound to an object of a completely different type:
x = [1, 2, 3] # the name 'x' is bound to a list object
y = x # 'y' is bound to the *same* list object, not a copy
y.append(4)
print(x) # [1, 2, 3, 4] -- seen through 'x' too
x = "now a string" # 'x' rebound to a new, unrelated object
This name-binding model is specified in the language reference’s data model chapter: every value in Python is an object, and a variable is nothing more than a reference to one.
Mutability: Rebinding vs. Mutating
Two very different things look similar at a glance:
-
Rebinding a name points it at a new object; the old object is unaffected (and is garbage-collected once nothing else references it).
-
Mutating an object changes its contents in place; every name bound to it sees the change.
x = 5
x = x + 1 # rebinding: 'x' now points to a *different* int object (6)
lst = [1, 2, 3]
lst.append(1) # mutation: the *same* list object gains an element
Whether an operation rebinds or mutates depends on the object’s type. Numbers, strings, and tuples are
immutable — every "modifying" operation on them actually produces a new object. Lists, dicts, and sets
are mutable — methods like .append(), .update(), and .add() change the object in place. The
full catalogue of built-in types and which category each falls into is in
the standard type hierarchy reference.
A preview of the mutable-default-argument pitfall
Because a default argument value is created once, at function-definition time, a mutable default is
shared across every call that doesn’t supply its own — covered in depth, with a worked example and the
None-sentinel fix, on Functions.
Shared References: is vs. ==
== asks whether two objects have equal value; is asks whether two names are bound to the same
object (identity). id() returns a number that uniquely identifies an object for its lifetime — CPython
uses its memory address, but that is an implementation detail, not something to depend on.
a = [1, 2, 3]
b = [1, 2, 3]
c = a
a == b # True -- same contents
a is b # False -- two distinct list objects
a is c # True -- 'c' is bound to the same object as 'a'
id(a) == id(c) # True, consistent with 'a is c'
Python’s small-integer and (in many, but not all, situations) string caching means is can appear
to succeed for immutable values that were never explicitly shared:
m = 5
n = 5
m is n # True in CPython -- small ints (-5..256) are cached/interned
p = 1000
q = 1000
p is q # not guaranteed -- may be True or False depending on context
This caching is an implementation detail of CPython, not a language guarantee — see
the is / is not operators in the
language reference. Never write code whose correctness depends on interning; use == to compare values
and reserve is for identity checks such as x is None.
The Built-In Type Hierarchy, at a Glance
Every object’s type falls into one of a small number of built-in categories. This map is worth keeping in mind as a table of contents for the rest of this section — Collections covers sequences, mappings, and sets in depth; Functions covers callables:
# numbers -- int, float, complex, bool (bool is a subtype of int)
# sequences -- str, bytes, list, tuple, range
# mappings -- dict
# sets -- set, frozenset
# the null type -- None (the sole instance of NoneType)
# callables -- functions, methods, classes, and any object with __call__
# classes -- every class is itself an object, an instance of 'type'
type(42) # <class 'int'>
type(3.14) # <class 'float'>
type("hi") # <class 'str'>
type([1, 2]) # <class 'list'>
type({"a": 1}) # <class 'dict'>
type(None) # <class 'NoneType'>
type(len) # <class 'builtin_function_or_method'>
The full, authoritative breakdown — including numeric towers, the iterator protocol, and special methods each category implements — lives in the data model chapter and the built-in types reference.
The figure above contrasts the two behaviors from the mutability section: on the left, x = x + 1
detaches the name from the old int object and points it at a new one; on the right,
lst.append(1) keeps the name pointing at the same list object, whose contents change underneath it.
See Also
-
Collections — sequences, mappings, and sets built on top of this type hierarchy.
-
Functions — callables, and the full mutable-default-argument story previewed above.