
7 min read
Decoding “Everything is an object” in Python
Python · Systems · Learning
Python’s slogan is a runtime contract, not poetry: values are objects with identity, type, and payload; names are bindings, not typed boxes. What it means, where it is honest, and where it bends.
“Everything is an object” shows up in tutorials and one-liners that end the conversation without explaining anything. This note decodes the slogan: what “object” actually means, what a name is, where the metaphor is honest, and where it becomes marketing.
The slogan is a uniform value model
Taken literally as “everything is a Java-style class instance you subclass,” the phrase misleads. Taken as runtime design, it is sharp:
Almost every value you can touch in Python is a heap object with three inseparable facts—identity, type, and value—and almost every operation goes through that object’s behavior (including integers, functions, modules, and classes themselves).
| Casual reading | Useful reading |
|---|---|
| “Python is only about classes and inheritance” | “Values are objects; names point at them” |
“int is not an object; objects are for OOP” | “int instances are objects; they are immutable and heavily optimized” |
| “Everything is slow because objects” | “The model is uniform; cost depends on what the object does and how CPython implements it” |
| “Variables hold types like in C” | “Variables are names in a namespace; types live on objects” |
So “everything is an object” really means: there is no second-class “naked” value tier in the language model—even if CPython optimizes under the hood for speed.
What “object” means here
In this context, an object is closer to:
- A blob of memory managed by the runtime (reference counts + cyclic GC).
- A type (what operations are valid—
type(x)/x.__class__). - An identity (
id(x)—in CPython related to address; treat it as “which object”). - A value / state (payload: the integer 7, the string characters, the list’s pointers, …).
name ──binds──► object
│
├── identity (is / id)
├── type (type / behavior)
└── value (== / contents)
That is why people also say “names reference objects.” Assignment does not fill a typed box; it reattaches a label.
| Concept | C mental model (rough) | Python mental model |
|---|---|---|
| Variable | Storage slot of a fixed type | Name bound to an object |
| Assignment | Copy bits into the slot (for scalars) | Rebind the name to (usually) another object |
| Type | Property of the variable / declaration | Property of the object |
== vs identity | Address-careful comparison | == value equality; is same object |
| Function arg | Pass by value (bits / pointer) | Pass a reference to an object (rebinding vs mutating still differs) |
Two names can bind the same object. Mutating through one name is visible through the other. That single fact explains half of “Python is weird” bugs.
The three facts, with code
1. Identity — which object
a = []
b = a
c = []
a is b # True — same object
a is c # False — equal shape, different object
a == c # True — same value (empty lists)
Rule you can live by: use is for singletons (None, and sometimes sentinels you create). Use == for value equality. Never use is to compare integers or strings for equality—interning is an implementation detail.
2. Type — what behavior
x = 7
type(x) # <class 'int'>
isinstance(x, int) # True
Types are objects too (type is a type; classes are instances of type). Decorators, metaclasses, and “class as factory” fall out of that—advanced, but not a separate universe.
3. Value — payload
Mutable objects can change payload in place (same identity). Immutable objects force new objects when you “change” them:
s = "hi"
print(id(s))
s = s + "!" # rebind name s to a new str object
print(id(s)) # different id
xs = [1, 2]
print(id(xs))
xs.append(3) # same list object, value changed
print(id(xs)) # same id
“Everything” includes things people forget
| Thing | Is it an object? | Why you care |
|---|---|---|
int, float, bool | Yes | Methods exist ((7).bit_length()); bool is a subclass of int |
str, bytes | Yes | Immutable sequences |
list, dict, set | Yes | Mutation + aliasing causes surprising shared state |
| Functions | Yes | First-class: pass, return, decorate |
| Modules | Yes | import binds a name to a module object |
| Classes | Yes | Callable factories; attributes live on the class object |
None | Yes | Singleton; prefer x is None |
| Slices, code objects, frames | Yes | Debuggers and dis touch them |
If it can appear on the right-hand side of assignment or as a function argument in normal code, treat it as an object.
Four places the idea shows up every day
1. Assignment and arguments (aliasing)
def append_one(bucket):
bucket.append(1)
data = []
append_one(data)
# data is [1] — function received a reference to the same list
Python always passes a reference. Whether the callee affects the caller depends on mutation vs rebinding:
def rebind(bucket):
bucket = [1] # local name only; caller unaffected
def mutate(bucket):
bucket[:] = [1] # mutates the object the caller still holds
2. Default arguments (the classic trap)
Defaults are evaluated once, at function definition time, and stored on the function object. A default list is one object shared across calls:
def add(item, bucket=[]): # bug: shared object
bucket.append(item)
return bucket
Fix: default None, create a new list inside. This is not a syntax quirk—it is the object model biting you.
3. Shared mutable configs
BASE = {"retries": 3, "hosts": []}
cfg = BASE
cfg["hosts"].append("lab-01")
# BASE is also mutated — you aliased a shared dict
When you mean “a copy of this data,” copy deliberately (copy.copy / copy.deepcopy when nested) or prefer immutable structures (tuple, frozen dataclasses).
4. Methods and uniform operations
(255).bit_count()
"sensor".upper()
[1, 2, 3].count(2)
The sugar x.method() means: look up the attribute on the object / its type and call it. Special methods (__eq__, __len__, __iter__) are how ==, len(), and for hook into the same model.
What the slogan does not mean
| Myth | Reality |
|---|---|
| Everything is slow OOP | CPython implements many objects in C; the model is objects |
| You must use classes for everything | Functions and modules are objects too; classes are optional structure |
is is a faster == | Different questions; wrong tool → silent bugs |
| Types are checked at assignment | Types ride on objects; a name can be rebound to another type anytime |
| Multithreading freely uses all cores | GIL (CPython): one thread runs Python bytecode at a time—related history, separate design choice |
CPython honesty (enough to stay grounded)
Under the hood, CPython uses PyObject headers (refcount, type pointer). Small integers and some strings may be interned/cached so is looks true for equal values—do not rely on that. The language guarantee is the abstract object model; interning is an optimization.
Garbage collection is mainly reference counting, plus a cycle detector for reference cycles (for example a list that contains itself). Holding references in globals, caches, or closures keeps objects alive—unexpected memory growth is often lingering names, not a missing free.
Mini lab
# 1) Alias
a = b = []
a.append(1)
assert b == [1]
# 2) Rebind vs mutate
def rebind(x):
x = x + [9]
def mutate(x):
x.append(9)
s = [1]
rebind(s)
assert s == [1]
mutate(s)
assert s == [1, 9]
# 3) Defaults
def f(x, acc=None):
if acc is None:
acc = []
acc.append(x)
return acc
assert f(1) == [1]
assert f(2) == [2] # not [1, 2]
# 4) Identity vs equality
assert [1] == [1] and [1] is not [1]
assert None is None
If any assert fails in your head before running, re-read the three-facts section.
Prefer precise language
| Avoid saying | Prefer saying |
|---|---|
| “Variables store values like C boxes” | “Names bind to objects” |
| “I changed the string in place” | “I rebound the name to a new str” (strings are immutable) |
“is checks equality” | “is checks identity; == checks value” |
| “Objects are only for OOP classes” | “Values are objects; classes are one kind of object among many” |
Closing
Everything is an object means: Python’s runtime is a graph of heap values with identity, type, and payload; names are edges into that graph—not typed storage slots.
Learn to see the graph—aliasing, mutation, rebinding—and half of Python’s “mystery” becomes mechanics. The slogan is not physics. It is a contract that repays the reader who knows where it bends, and who checks behavior instead of quoting stickers.
Was this page helpful?