Callable objects

By Martin McBride, 2021-08-28
Tags: callable object closure
Categories: magic methods


A callable object is an object that can be used like a function. That is, it can be invoked, ie "called", using brackets (). It can also be passed in wherever a callable object is required, for example in a map function.

Normal Python functions are one example of callable objects. You can also make your own callable object by implementing the __call__ method.

Example LinePrinter class

Here is a simple class that performs a similar job to print, except that it adds an incrementing line number before each print:

class LinePrinter:  

    def __init__(self):
        self.line = 0

    def print(self, x):
        print(self.line, x)
        self.line += 1

printn = LinePrinter()
printn.print("a")
printn.print("b")

This creates the following output:

0 a
1 b

Making the object callable

This works fine, but it might be even better if instead of writing printn.print() we could just use printn(). We can do this by defining a method __call__:

class LinePrinter:  

    def __init__(self):
        self.line = 0

    def __call__(self, x):
        print(self.line, x)
        self.line += 1

printn = LinePrinter()
printn("a")
printn("b")

Apart from being neater, this approach has the advantage that we can also use the object printn as if it were a function object. For example, with map:

list(map(printn, [10, 20, 30]))

Note that map is a lazy function, it will not execute printn until it needs to. By creating a list of the map we force it to evaluate, and therefore to print the output. The result is:

0 10
1 20
2 30

Changing the signature

You can give the __call__ method any signature you wish. The object will behave like a function of the required signature (excluding the hidden self parameter):

class LinePrinter:  

    def __init__(self):
        self.line = 0

    def __call__(self, *args, **kwargs):
        print(self.line, *args, **kwargs)
        self.line += 1

printn = LinePrinter()
printn("a", "b", "c")
printn(10, 20, 30, sep='-')

In this case we have allowed the function to accept *args and **kwargs, that is a variable number of unnamed and named parameters. These are passed on to the print function. This means that we can pass through multiple values per line, and also named parameters such as sep (one of the standard print parameters). The result is:

0 a b c
1-10-20-30

Notice that the sep parameter is passed to print to control the character between values, setting it to - rather than space.

Closures

For the sake of simplicity, this example is a little contrived. There are other ways to solve the same problem. In this case a closure would provide the same functionality, but in a way that is arguably a little simpler. Here is the code:

def LinePrinter():  
    line = 0

    def fn(*args, **kwargs):
        nonlocal line
        print(line, *args, **kwargs)
        line += 1

    return fn

printn = LinePrinter()
printn("a", "b", "c")
printn(10, 20, 30, sep='-')

Here, rather than creating a class, we have created a LinePrinter function (the outer function). That function has a variable line and an inner function fn (that corresponds to the __call__ method). The outer function, when called, returns an object of the inner function.

The fun part about closures is that the inner function object can still access it's own private copy of the line variable! So each time the function is called it will increment the count.

In reality, for problems such as this you would most likely use a closure rather than creating your own callable object. Firstly because it is simpler and secondly because it is the standard way to solve this type of problem. If you do things in a non-standard way, anyone reading your code will be scratching their head trying to figure out why you didn't just use a closure.

When to use a callable object

It isn't always possible to use a closure instead of a callable object. A particular example is when you need to change the state of the callable object between calls.

For example, suppose we wanted the ability to reset our LinePrinter, so it starts countuing from zero again. We can simple add a reset method to our class:

class LinePrinter:  

    def __init__(self):
        self.line = 0

    def __call__(self, *args, **kwargs):
        print(self.line, *args, **kwargs)
        self.line += 1

    def reset(self):
        self.line = 0

printn = LinePrinter()
printn("a", "b", "c")
printn(10, 20, 30, sep='-')
printn.reset()
printn("x", "y", "x")

This prints:

0 a b c
1-10-20-30
0 x y z

Because of the call to the reset method, the line number restarts at zero on the third line. This would not be easy to do with a closure.

See also

If you found this article useful, you might be interested in the book NumPy Recipes or other books by the same author.

Join the PythonInformer Newsletter

Sign up using this form to receive an email when new content is added:

Popular tags

2d arrays abstract data type alignment and angle animation arc array arrays bar chart bar style behavioural pattern bezier curve built-in function callable object chain circle classes clipping close closure cmyk colour combinations comparison operator comprehension context context manager conversion count creational pattern data science data types decorator design pattern device space dictionary drawing duck typing efficiency ellipse else encryption enumerate fill filter font font style for loop formula function function composition function plot functools game development generativepy tutorial generator geometry gif global variable gradient greyscale higher order function hsl html image image processing imagesurface immutable object in operator index inner function input installing iter iterable iterator itertools join l system lambda function latex len lerp line line plot line style linear gradient linspace list list comprehension logical operator lru_cache magic method mandelbrot mandelbrot set map marker style matplotlib monad mutability named parameter numeric python numpy object open operator optimisation optional parameter or pandas partial application path pattern permutations pie chart pil pillow polygon pong positional parameter print product programming paradigms programming techniques pure function python standard library radial gradient range recipes rectangle recursion reduce regular polygon repeat rgb rotation roundrect scaling scatter plot scipy sector segment sequence setup shape singleton slice slicing sound spirograph sprite square str stream string stroke structural pattern subpath symmetric encryption template tex text text metrics tinkerbell fractal transform translation transparency triangle truthy value tuple turtle unpacking user space vectorisation webserver website while loop zip zip_longest