Python Data Model Foundations: Objects, Types, and Reliable Boundaries
Understand names, object identity, mutability, typing, and validation so Python code behaves predictably as systems grow.
On this page
Key takeaways
- Names refer to objects; assigning a name does not copy a mutable object.
- Mutability and aliasing explain many surprising bugs better than syntax does.
- Type hints document and check intent, while runtime validation protects external boundaries.
- Use immutable values and narrow interfaces to make state changes easy to trace.
Names point to objects
Python variables are names bound to objects. If two names refer to the same list, a mutation through either name changes the same object. This differs from immutable values such as integers, strings, and tuples, where an apparent update creates or binds to another value rather than mutating the original object.
first = ["queued"]
second = first
second.append("running")
assert first == ["queued", "running"]
This model matters at function boundaries. Passing a mutable object gives the callee a reference to that object. A function that mutates an argument should make that behavior clear, or return a new value instead.
Avoid shared mutable defaults
Default argument expressions are evaluated once when the function is defined, not once per call. A list default can therefore retain state across calls. Prefer None and create the mutable value inside the function, or use a data-model feature such as default_factory when defining structured records.
The same principle applies to caches, module globals, and singleton clients: shared state has a lifetime and ownership model. Make both explicit, especially in applications that handle concurrent requests.
Hints are design documentation
Type hints describe expected values and relationships in code. A checker can catch many mistakes before runtime, and a reader can see whether a function accepts an iterable, a mapping, or a domain-specific record. Hints do not validate arbitrary JSON, environment variables, database rows, or HTTP input at runtime.
Treat untrusted input as an unvalidated payload at the boundary. Parse and validate it into a domain type once, then let internal code rely on the validated representation. This keeps defensive checks near the edge instead of scattering uncertainty through every function.
Choose data structures by behavior
Use a list when order and repeated items matter, a set when membership is the main operation, and a dictionary when values are looked up by stable keys. Dataclasses or typed models can group related fields and make invariants visible. Keep conversions explicit: a tuple may communicate fixed structure, while a mutable list communicates that edits are expected.
Foundation for larger systems
Good Python architecture starts with clear object ownership, small functions, and validated boundaries. These choices make later topics such as asyncio, dependency injection, persistence, and testing more manageable. Before optimizing, make it easy to answer: what object is this name referring to, who may mutate it, and where was its shape validated?