Designing callables

There are two easy and commonly-used ways to create callable objects in Python, which are as follows:

  • By using the def statement to create a function.
  • By creating an instance of a class that implements the _call()_ method. This can be done by using collections.abc.Callable as its base class.

Beyond these two, 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 this 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, pow1, created from a class:

from typing import Callable
IntExp = Callable[[int, int], int]
class Power1:
def __call__(self, x: int, n: int) -> int:
p = 1
for i in range(n):
p *= x
return p
pow1: IntExp = Power1()

There are three parts to creating a callable object, as follows:

  • The type hint defines the parameters and return values from the resulting callable object. In this example, Callable[[int, int], int] defines a function with two integer parameters and an integer result. To save repeating it, a new type name, IntExp, is assigned.
  • We defined the class with a __call__() method. The type signature here matches the IntExp type definition. 
  • We created an instance of the class, pow1(). This object is callable and behaves like a function. We've also provided a type hint so that mypy can confirm that the callable object will have the proper signature.

The algorithm for computing seems to be inefficient. We'll address that later.

Clearly, the body of the __call__() method is so simple that a full class definition isn't really necessary. In order to show the various optimizations, we'll start with this simple 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 when working with mypy; however, using the abstract base class can 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.

The following is what happens when we try to create an instance of this class:

>>> pow2: IntExp = 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 a version of a broken callable that relies on type hints to detect the problem. This is nearly identical to the correct Power class shown previously. The code that contains a tragic flaw is as follows:

class Power3:

def __call_(self, x: int, n: int) -> int:
p = 1
for i in range(n):
p *= x
return p

When we run mypy, we will see complaints about this code. The expected type of the callable object doesn't match the defined IntExp type:

# Chapter_6/ch06_ex1.py:68: error: Incompatible types in assignment (expression has type "Power3", variable has type "Callable[[int, int], int]")

If we ignore the mypy error and try to use this class, we'll see runtime problems. 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 abc.Callable either:

>>> pow3: IntExp = 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 mypy hint provides some assistance in locating the problem.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset