#294: Callable Classes in Python
In Python, everything is an object - including functions. That flexibility goes both ways: not only can functions behave like objects, but objects can behave like functions. The mechanism that unlocks this duality is the special (or "dunder") method __call__. By defining __call__ in a class, we make it possible to invoke it with parentheses, just like an ordinary function, while still retaining all the advantages of stateful objects.
What exactly is __call__?
__call__(self, *args, **kwargs) is a method the interpreter looks for when we place (), i.e. the call operator, after an object. If it exists, Python executes that method instead of raising a TypeError. Conceptually, it is halfway between a regular method and operator overloading, something that other languages call a "functor".
We can now use our newly created class like this:
The line plus5(10) is syntactic sugar for plus5.__call__(10) but do only use the shortcut and not write the long variant.
Why should we use __call__?
The __call__ method allows us to invoke a class as if it was a regular function. This capability gives us these practical benefits:
- Function-like behaviour with object-oriented structure: By implementing
__call__, we create objects that we can use with function-call syntax (obj()), while still leveraging the features of classes such as encapsulation, inheritance, and composition. - Stateful and reusable components:
__call__makes it easy to build components that retain internal state across invocations. This allows us to bundle both data and behaviour into a single, reusable, and configurable object—useful in scenarios where a function alone would not be sufficient. - Common in advanced design patterns: Callable objects are frequently used in patterns such as decorators, event handlers, and callback systems. They are also a foundational concept in domains like machine learning, where models are often represented as objects that can be "called" with input data.
By understanding and using __call__, we can design more flexible, maintainable, and Pythonic interfaces in our applications and libraries.
A practical example: output cache for functions
We can create decorator to cache the output of a function that offers the same behaviour as we saw with the @cache decorator in post #210 with these few lines of code:
We can now use our decorator whenever we have a computationally expensive function:
The first call with a new argument computes it, while for every following call with the same argument we skip the computation and retrieve it from our cache.
When we run the fib() function with 3 as a parameter, it will print the different values down to 0 only the first time. The second time we call the function, it uses the cache and directly shows the result:
Caveats to remember
Before we jump in and put a __call__ method in all our classes, we need to talk about the downside:
- Introspection:
inspect.isfunction(obj)will returnFalsefor objects with__call__, so tooling needs to checkcallable(obj). - Readability: Overusing callable objects can blur the line between data and behaviour. Ask whether a plain function, closure, or even a
@staticmethodwould be clearer. - Pickling: If our callable object holds non‑picklable resources, distributing work across processes could break.
Conclusion
__call__ is a small hook with great potential. It let us blend object‑oriented structure with functional syntax, so that we can create APIs that are both expressive and intuitive. Understanding __call__ adds a subtle but powerful tool to our Python toolbox.