
Designing with ABC callables
There are two easy ways to create callable objects in Python, as follows:
- Using the
def
statement to create a function - By creating an instance of a class that uses
collections.abc.Callable
as its base class
We can also assign a lambda form to a variable. A lambda is a small, anonymous function that consists of exactly one expression. We'd rather not emphasize saving lambdas in a variable as it leads to the confusing situation where we have a function-like callable that's not defined with a def
statement. The following is a simple callable object that has been created from a class:
import collections.abc class Power1( collections.abc.Callable ): def __call__( self, x, n ): p= 1 for i in range(n): p *= x return p pow1= Power1()
There are three parts to the preceding callable object, as follows:
- We defined the class as a subclass of
abc.Callable
- We defined the
__call__()
method - We created an instance of the class,
pow1()
Yes, the algorithm seems inefficient. We'll address that.
Clearly, this is so simple that a full class definition isn't really necessary. In order to show the various optimizations, it's slightly simpler to start with a callable object rather than mutate a function into a callable object.
We can now use the pow1()
function just as we'd use any other function. Here's how to use the pow1()
function in a Python command line:
>>> pow1( 2, 0 ) 1 >>> pow1( 2, 1 ) 2 >>> pow1( 2, 2 ) 4 >>> pow1( 2, 10 ) 1024
We've evaluated the callable object with various kinds of argument values. It's not required to make a callable object a subclass of abc.Callable
. However, it does help with debugging.
Consider this flawed definition:
class Power2( collections.abc.Callable ): def __call_( self, x, n ): p= 1 for i in range(n): p *= x return p
The preceding class definition has an error and doesn't meet the definition of the callable abstraction.
Found the error yet? If not, it's at the end of the chapter.
The following is what happens when we try to create an instance of this class:
>>> pow2= Power2() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class Power2 with abstract methods __call__
It may not be obvious exactly what went wrong, but we have a fighting chance to debug this. If we hadn't subclassed collections.abc.Callable
, we'd have a somewhat more mysterious problem to debug.
Here's what the more mysterious problem would look like. We'll skip the actual code for Power3
. It's the same as Power2
, except it doesn't subclass collections.abc.Callable
. It starts class Power3
; otherwise, it's identical.
The following is what happens when we try to use Power3
as a class that doesn't meet the expectations of callables and isn't a subclass of the abc.Callable
either:
>>> pow3= Power3() >>> pow3( 2, 5 ) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'Power3' object is not callable
This error provides less guidance as to why the Power3
class definition is flawed. The Power2
error is much more explicit about the nature of the problem.