__call__: Making Objects Callable

RoadmapsPython

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

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

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

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.

Return to Python Roadmap