ProgrammingPython Developer

Explain the principles of the __call__ method in Python, where it is applied, and how an object implementing __call__ differs from a regular function.

Pass interviews with Hintsage AI assistant

Answer.

Background:

In Python, everything is an object, and any function is also an object. A mechanism was implemented that allows a class instance to behave like a function. For this purpose, the special magic method call was introduced, which allows objects to be callable.

Problem:

Sometimes, there is a need to pass an object that can perform actions when called, such as stateful functions, closures, event handler objects, etc. Developing an architecture requires understanding how to properly implement such functionality.

Solution:

By implementing the call method in a class, its instance can be made callable "like a function". This combines the capabilities of classes (encapsulation of state, inheritance, methods) and functions (callability). This approach is used to create command objects, complex handlers, wrappers, etc.

Example code:

class Adder: def __init__(self, x): self.x = x def __call__(self, y): return self.x + y add5 = Adder(5) print(add5(10)) # Will print 15

Key features:

  • Objects with call maintain internal state (unlike a regular function).
  • Can be invoked like a function, but have class methods and properties.
  • Allows creating more expressive design patterns (e.g., strategies, commands).

Trick questions.

Does the call method inherit attributes of a regular function—like name and doc?

No, the object with the call method will not have the name attribute (or it will take from the class). Function metadata is not preserved.

Is an object with an implemented call a real function?

No, it is an instance of a class and not a function. It only implements "callable" behavior. For example, comparing it to a function using isinstance(obj, types.FunctionType) will return False.

Can a decorator intended for a function be applied to an object with call?

Such decorators usually expect a function, not an object (e.g., functools.lru_cache). Using them may lead to errors or may not work at all.

Common mistakes and anti-patterns

Pros:

  • Flexibility of design patterns.
  • Ability to combine data and behavior. Cons:
  • Code becomes less transparent for beginner Python developers.
  • Loss of standard function attributes; it may become impossible to use some tools/decorators.

Real-life example

Negative case: In a project, a logger was implemented via a class with call, to store settings (level, filename). However, it was forgotten that signal handler functions expect a function, and errors occurred during handler registration (object is not a function).

Pros: flexible logger configuration. Cons: incompatibility with expected interfaces.

Positive case: In another project, a class with call was used to create complex decorator functions while preserving parameters, which simplified testing.

Pros: extensibility, convenience. Cons: more code compared to a function or lambda.