Everything is an object in Python
Overview
In Python, every value is an object — an entity with a type, a unique identity, and attributes. This includes things other languages treat as 'not values': functions, classes, and modules are all objects too. Here are three direct consequences of this design: - Every type is itself an object, meaning `type(int)` returns the `type` class. - Functions are objects and can store custom metadata via their own attribute namespace. - Classes and functions can be stored in data structures like variables, lists, or dictionaries. For example, because types are objects, you can inspect them directly: print(type(42)) # <class 'int'> print(type(int)) # <class 'type'> Similarly, because functions are objects, you can attach custom attributes to them: def handler(): pass handler.route = '/health' print(handler.route) Think of Python as a world where there are no 'primitives' standing outside the object system. Even an integer is a full object with methods, though you must wrap literals in parentheses to call them, like `(42).bit_length()`. The uniform rule that 'everything is an object' is what makes introspection functions like `type()`, `getattr()`, and `dir()` work on absolutely anything. Because every value is a distinct object in memory, two data structures with identical contents can still be separate objects: a = [1, 2] b = [1, 2] print(a == b) # True print(a is b) # False This distinction between value equality and object identity is covered fully in `is-vs-equals`.
This uniformity is why Python can pass functions as arguments, build registries of classes, and introspect any value at runtime. Frameworks like FastAPI rely on it: a route decorator receives your function as an object and stores it; dependency injection (passing dependencies as arguments rather than creating them inside) stores classes in a registry. Understanding it explains why `function.attribute`, `type(x)`, and storing a class in a `dict` all just work.
Where used: FastAPI route/dependency registries (functions and classes stored as objects), Plugin systems that map names to classes, Runtime introspection with `type()` and `getattr()`
Why learn this
- Explains how FastAPI stores your route functions and dependency classes in registries — they are objects
- Enables introspection: using `type()`, `getattr()`, and attaching metadata to functions
Code walkthrough
def handler():
return 'ok'
print(type(handler))
print(handler.__name__)
handler.route = '/health'
print(handler.route)
Focus: `handler` is an object: it has a type (`function`), a built-in `__name__` attribute, and you can even attach a brand-new custom attribute (`route`) to it.
Aha moment
def ping():
return 'pong'
ping.call_count = 0
ping.call_count += 1
print(ping.call_count)
Prediction: Can you attach your own attribute (`call_count`) to a function and read it back?
Common guess: Error — functions can't hold custom attributes
A function is an object, so it has a writable attribute namespace just like any instance. Setting `ping.call_count = 0` stores state directly on the function object, and `ping.call_count` reads it back — printing `1`.
Common mistakes
- Thinking ints and strings are 'primitives' without methods: Unlike Java, Python has no primitive types. Method calls like `(42).bit_length()` and `'hi'.upper()` work because `int` and `str` are full objects. There is no separate boxed/unboxed distinction.
- Calling a function object when you meant to reference it: Storing `handler()` in a registry stores its return value; storing `handler` stores the function object itself. Omit the parentheses to keep the object.
- Assuming classes cannot be passed around: A class is an object (an instance of `type`), so it can be stored in a variable, put in a `list`, or passed to a function. This is how factory and plugin patterns work.
Glossary
- introspection
- The ability of a program to examine its own structure, like figuring out the type of a variable while it runs. Example: `type(42)`
- boxed/unboxed distinction
- A concept in some languages where simple data types (like numbers) are handled differently than complex objects; Python does not do this. Example: Java `int` vs `Integer`.
Recall questions
- Are functions and classes considered objects in Python?
- Can you attach custom attributes to a function in Python?
- If two lists have the identical contents, are they guaranteed to be the exact same object?
Understanding checks
What will the following code output?
api_route
Because functions are objects in Python, they have an attribute namespace where you can dynamically attach custom attributes.
Why is it possible to put a class, like `int`, into a dictionary as a key or value?
Because classes themselves are objects in Python.
In Python, everything is an object, including classes. They have their own type (`type`), identity, and can be passed around just like instances.
Practice tasks
Build a Handler Registry
Modify the registry logic so that the `registry` dictionary correctly stores the function objects for `/health` and `/users` rather than calling them.
Challenge
Type-keyed serializer dispatch
Write `serialize(value)` that uses `type(value)` as a dictionary key to pick a serializer function. Map `int` -> a function returning `'num:<v>'`, `str` -> `'str:<v>'`, and `bool` -> `'bool:<v>'`. Store the serializer functions as objects in the dictionary. Test with `42` and `'hello'`. (Note: checking `bool` before `int` is not required here since the keys are exact types.)
Continue learning
Previous: Dynamic typing and type()
Previous: Defining functions and return values
Previous: First-class functions: passing and returning functions
Next: is vs == : Identity vs Equality