Python function first-class objects

Python Functions are First-Class Objects. As mentioned previously, Python functions are first-class objects, ordinary objects with some attributes. For more information on the special methods available on function objects, see the Python Language Reference Manual. Similar to how you get object attributes, you can access a function’s docstring and name through the __doc__ and __name__ attributes, and the function body through the __code__ attribute. In compiled languages, implementing this type of introspection is relatively complex because of the need to preserve source code information, but Python makes it simple.

Functions can be assigned to variables, passed as arguments, and returned from other functions. These techniques make it easy to write higher-order functions.

Because functions are already objects, Python has many elements of functional languages. Furthermore, since functions are ordinary objects, they can be created using callable objects, and even callable classes can be considered higher-order functions. When defining the __init__() method of a callable object, try to avoid setting stateful class variables in it. A common approach is to define the __init__() method using the Strategy pattern.

A class created using the Strategy pattern uses another object to provide an algorithm, or a portion of an algorithm, allowing the algorithm to be dynamically injected at runtime without having to lock the specific logic into the class.

An example of embedding a strategy object in a callable object is as follows:

from typing import Callable
class Mersenne1:
def __init__(self, algorithm: Callable[[int], int]) -> None:
self.pow2 = algorithm
def __call__(self, arg: int) -> int:
return self.pow2(arg) - 1

This class uses the __init__() method to store a reference to another function, algorithm, in self.pow2. No stateful variables are created, and self.pow2 should not change. The type annotation of the algorithm parameter is Callable[[int], int], indicating that both the input and output of this function are integer values.

This function calculates the power of 2 using the algorithm provided by the strategy object. The three possible algorithms that can be used as class constructor parameters are as follows:

def shifty(b: int) -> int:
return 1 << b

def multy(b: int) -> int:
if b == 0: return 1
return 2 * multy(b - 1)

def faster(b: int) -> int:
if b == 0: return 1
if b % 2 == 1: return 2 * faster(b - 1)
t = faster(b // 2)
return t * t

shifty() calculates the power of 2 by shifting bits left. multy() uses a simple recursive multiplication. The faster() function uses a divide-and-conquer strategy, requiring only log_2{(b)} multiplications instead of b.

The signatures of the three functions above are identical: Callable[[int], int], matching the algorithm parameter in the Mersenne1.__init__(self) method.

Now, you can create instances of the Mersenne class based on different algorithms, as shown below:

m1s = Mersenne1(shifty)
m1m = Mersenne1(multy)
m1f = Mersenne1(faster)

This way, using different algorithms and defining different functions, we achieve the same result.

Python can calculate M^{89} = 2^{89} – 1, which doesn’t even approach its nested recursion depth limit. This is a large prime number, with 27 digits.

Leave a Reply

Your email address will not be published. Required fields are marked *