Python

Using __call__ in Python to Make Objects Callable

Learn how the __call__ dunder method turns any Python object into a callable, letting you use object instances like functions for cleaner, more expressive code.

August 2026 4 min read 12 views 0 hearts

Making Your Python Objects Callable with __call__

Have you ever wanted to use an object like a function? Maybe you've written code where you call obj() and thought, "Wait, that shouldn't work." But it does—and here's how.

When I first discovered __call__ in Python, it felt like finding a hidden superpower. It's one of those magic methods that transforms how you think about objects and functions.

Think about functions in Python. They're just callable objects, right? Under the hood, functions have a __call__ method. When you do my_func(), Python actually calls my_func.__call__(). The __call__ dunder method is what makes something callable.

Here's the trick: you can add __call__ to any of your classes. That's it. Once you define this method, your objects become callable just like functions.

Let me show you what I mean with a practical example from PythonSkillset.com's real-world usage:

class RateLimiter:
    def __init__(self, calls_per_minute):
        self.calls_per_minute = calls_per_minute
        self.calls = []

    def __call__(self, func, *args, **kwargs):
        from time import time
        now = time()

        # Remove calls older than 60 seconds
        self.calls = [t for t in self.calls if now - t < 60]

        if len(self.calls) >= self.calls_per_minute:
            raise RuntimeError("Rate limit exceeded. Try again later.")

        self.calls.append(now)
        return func(*args, **kwargs)

# Usage
limiter = RateLimiter(calls_per_minute=10)

# Now limiter works like a function wrapper
result = limiter(some_api_call, user_id=42)

See what happened there? We created an object that tracks rate limits, but we can use it with the simple syntax limiter(...). No separate execute() or call() method needed.

This pattern shows up everywhere in professional Python code. Flask uses it for class-based views. Django has it for managing request handlers. It's clean, it's Pythonic, and it makes your code read like English.

Another place I've used __call__ at PythonSkillset is for configuration objects:

class APIConfig:
    def __init__(self, base_url, api_key=None):
        self.base_url = base_url
        self.api_key = api_key
        self.endpoints = {}

    def __call__(self, endpoint_name):
        return self.endpoints.get(endpoint_name)

    def add_endpoint(self, name, path):
        self.endpoints[name] = f"{self.base_url}/{path}"

config = APIConfig("https://api.example.com", api_key="sk-...")
config.add_endpoint("users", "v2/users")
config.add_endpoint("posts", "v3/posts")

# Clean access
users_url = config("users")  # Returns "https://api.example.com/v2/users"

The beauty here? Your code becomes self-documenting. When someone reads config("users"), they immediately understand "Oh, this is fetching the users endpoint URL from config." Compare that to config.get_endpoint("users") or config.endpoints["users"]—the meaning is the same, but the callable version feels more natural.

There are a few practical things to remember about __call__:

  • You can accept any arguments and keyword arguments, just like a regular function
  • The returned value is whatever you want it to be
  • You can create callable objects that remember state across calls
  • It works with inheritance, so you can have base classes that define __call__ as an interface

One word of caution: don't overuse it. If your class does one thing and needs a single method, making it callable makes sense. But if you have multiple methods, stick with regular method names. __call__ works best when you want your object to act more like a function than a container of methods.

Next time you're writing a class that essentially does one job with some setup, ask yourself: could this be a callable object? The answer might surprise you, and your code will be cleaner for it.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.