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
Example LinePrinter class
Here is a simple class that performs a similar job to
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
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
list(map(printn, [10, 20, 30]))
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
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
**kwargs, that is a variable number of unnamed and named parameters. These are passed on to the
sep (one of the standard
0 a b c 1-10-20-30
Notice that the
sep parameter is passed to
- rather than space.
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")
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.
Join the PythonInformer Newsletter
Sign up using this form to receive an email when new content is added:
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