Everything is an object in Python

RoadmapsPython

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

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

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

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

Next: Reference Counting in CPython

Return to Python Roadmap