logo
NotesNotesResumeResume
© 2026 Kiet Nguyen
← All notes
Zine collage — Python object as identity, type, and value with a name binding

July 3, 2026·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.

Conceptual map for CPython (the implementation you almost certainly run). Other Pythons share the language idea with different internals. Not a full GC course, not “OOP design patterns 101.”

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 readingUseful 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:

  1. A blob of memory managed by the runtime (reference counts + cyclic GC).
  2. A type (what operations are valid—type(x) / x.__class__).
  3. An identity (id(x)—in CPython related to address; treat it as “which object”).
  4. 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.

ConceptC mental model (rough)Python mental model
VariableStorage slot of a fixed typeName bound to an object
AssignmentCopy bits into the slot (for scalars)Rebind the name to (usually) another object
TypeProperty of the variable / declarationProperty of the object
== vs identityAddress-careful comparison== value equality; is same object
Function argPass 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

ThingIs it an object?Why you care
int, float, boolYesMethods exist ((7).bit_length()); bool is a subclass of int
str, bytesYesImmutable sequences
list, dict, setYesMutation + aliasing causes surprising shared state
FunctionsYesFirst-class: pass, return, decorate
ModulesYesimport binds a name to a module object
ClassesYesCallable factories; attributes live on the class object
NoneYesSingleton; prefer x is None
Slices, code objects, framesYesDebuggers 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

MythReality
Everything is slow OOPCPython implements many objects in C; the model is objects
You must use classes for everythingFunctions and modules are objects too; classes are optional structure
is is a faster ==Different questions; wrong tool → silent bugs
Types are checked at assignmentTypes ride on objects; a name can be rebound to another type anytime
Multithreading freely uses all coresGIL (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 sayingPrefer 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.

Back to notes

Was this page helpful?