__call__: Making Objects Callable
Overview
The `__call__` dunder method allows an instance of a class to be executed exactly like a function. Variables are labels, objects are boxes; when you add `()` to a label, Python looks for `__call__` on the box. This means an object can maintain internal state (like a database connection or a counter) while exposing a simple, function-like interface. Defining `__call__` makes instances invocable with `()` syntax, which directly dispatches to the `__call__` method. For example, PyTorch models use `__call__` to run the forward pass, and FastAPI uses it for dependency injection classes. class Counter: def __init__(self): self.count = 0 def __call__(self): self.count += 1 return self.count my_counter = Counter() my_counter() # Returns 1 A common trap is trying to make an object callable by assigning a function to `self.__call__` dynamically. Python ignores instance-level dunder methods and only looks them up on the class definition. class Dummy: pass obj = Dummy() obj.__call__ = lambda: print("Hello") obj() # TypeError: 'Dummy' object is not callable Another sharp edge is using classes as decorators. The signature of `__call__` drastically changes depending on whether you include parentheses during decoration, a trap covered fully in decorators-with-state.
It lets you combine the statefulness of a class with the clean syntax of a function call. It is the foundation for stateful decorators (decorators that remember data across calls) and many framework APIs.
Where used: FastAPI, PyTorch, Django
Why learn this
- Building stateful decorators
- Understanding PyTorch model execution (`model(x)`)
- Writing FastAPI dependencies that require configuration
Code walkthrough
class Greeter:
def __init__(self, greeting):
self.greeting = greeting
def __call__(self, name):
return f'{self.greeting}, {name}'
hello_greeter = Greeter('Hello')
print(hello_greeter('Alice'))
Focus: hello_greeter('Alice')
Aha moment
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, value):
return self.factor * value
double = Multiplier(2)
triple = Multiplier(3)
print(double(5) + triple(4))
Prediction: What does the final print statement output?
Common guess: It raises a `TypeError` because you cannot call an object.
Since `Multiplier` defines `__call__`, the instances `double` and `triple` act exactly like functions. `double(5)` returns `10`, `triple(4)` returns `12`, so the sum is `22`.
Common mistakes
- Calling the class vs the instance: Adding `()` to the class calls `__init__` to create an instance. Adding `()` to the instance calls `__call__`. Beginners often confuse `MyClass(args)` with `my_instance(args)`.
- Forgetting to return a value: Just like a regular function, `__call__` must explicitly `return` a value if you want the caller to receive one. Otherwise, it implicitly returns `None`.
- Assigning `__call__` to an instance: Because of how Python implements dunder method lookup, assigning `obj.__call__ = my_func` does not make `obj` callable. You must define `__call__` on the class itself.
Glossary
- dunder method
- A special built-in method in Python that has double underscores before and after its name, e.g. `__init__` or `__call__`.
- dependency injection
- A programming technique where an object receives other objects it needs to function, rather than creating them itself, e.g. `handler = Handler(db=my_db_connection)`.
Recall questions
- What happens when you add parentheses `()` to an instance of a class?
- Why would you use an object with `__call__` instead of a regular function?
Understanding checks
What is the output of this code?
`15`
The instance `add_five` is created with `val = 5`. Calling it with `10` invokes the `__call__` method, which adds `5` and `10`.
What is the output of this code?
`1`
The code creates two separate instances of `Counter`. The first `Counter()()` creates an instance and immediately calls it, then discards it. The `print(Counter()())` creates a completely new instance with a fresh `count = 0` and calls it, so it returns `1`.
What happens when this code is executed?
It raises a `TypeError`.
Python looks up dunder methods like `__call__` on the class, not the instance. Adding `__call__` to `p` dynamically does not make `p` callable.
Practice tasks
Stateful Rate Limiter API
Currently, `RateLimiter` has a `check` method that must be called explicitly like `limiter.check()`. Rename and refactor the `check` method to use the `__call__` dunder method instead, so that instances of `RateLimiter` can be called directly like `limiter()`.
Challenge
A Configurable Caching Decorator
Write a `Cache` class. The `__init__` should take a function. The `__call__` method should accept a single argument, check if it's in an internal dictionary cache, return the cached value if present, or call the original function, store the result, and return it.