Functional Patterns in Python using functools module
Python is a dynamic programming language that supports higher-order functions, also known as functions that work with other functions. These higher-order functions allow developers to write cleaner and more efficient code.
Python’s functools module provides a set of functions that implement functional programming patterns that can be used to extend the functionality of existing functions, improve code readability, and write efficient code. In this article, we will explore some of the commonly used functools functions and their usage.
List of functools functions covered
This article covers the following functools functions that are commonly used in functional programming patterns in Python:
– partial
– partialmethod
– reduce
– wraps
– lru_cache
– cache
– cached_property
– total_ordering
– singledispatch
Explanation and Usage
partial()
The partial() function is one of the most commonly used functions in the functools module. This function allows the developer to create a new function by fixing a set of arguments to an existing function.
The partial() function is defined as follows:
“`python
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
return func(*(args + fargs), **dict(keywords, **fkeywords))
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
“`
The partial() function takes an existing function and fixes a set of input arguments to it. The returned function can then be called with the remaining arguments, which are passed to the original function.
This allows the developer to create a new function that is a simplified version of the original, taking fewer arguments.
Example
Let’s look at an example of using the partial() function in Python:
“`python
from functools import partial
def square(number, power=2):
return number ** power
square_of_numbers = partial(square, power=2)
print(square(2)) # Output: 4
print(square_of_numbers(2)) # Output: 4
“`
In the example above, we define a function called square() that takes two parameters, number and power, and returns the square of the number raised to the power. We then use the partial() function to create a new function called square_of_numbers, which fixes the power argument to 2.
This creates a new function that is a simpler version of the original, taking only one argument, number.
partialmethod()
The
partialmethod() function is similar to the partial() function, but it is used to create partial objects for methods of a class. The
partialmethod() function is defined as follows:
“`python
def partialmethod(func, *args, **keywords):
def newfunc(self, *fargs, **fkeywords):
return func(self, *(args + fargs), **dict(keywords, **fkeywords))
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
“`
Example
Let’s look at an example of using the
partialmethod() function in Python:
“`python
from functools import partialmethod
class Character:
def __init__(self, name):
self._name = name
def attack(self, power, target):
print(f'{self._name} attacks {target} with power of {power}.’)
orc = Character(‘Orc’)
orc.attack(10, ‘Elf’)
orc_slash = partialmethod(Character.attack, power=5)
orc_slash(orc, ‘Elf’)
“`
In the example above, we define a class called Character and a method called attack(), which takes two arguments, power and target, and prints a message. We then use the
partialmethod() function to create a new method called orc_slash, which fixes the value of the power argument to 5.
This gives us a new method that is a simplified version of the original, taking only one argument, the target.
reduce()
The
reduce() function is used to apply a function to a list of elements, reducing it to a single accumulated value. The
reduce() function is defined as follows:
“`python
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
value = next(it)
else:
value = initializer
for element in it:
value = function(value, element)
return value
“`
Example
Let’s look at an example of using the
reduce() function in Python:
“`python
from functools import reduce
numbers = [1, 2, 3, 4, 5]
sum_of_numbers = reduce(lambda x, y: x + y, numbers)
print(sum_of_numbers) # Output: 15
“`
In the example above, we define a list called numbers and use the
reduce() function to apply a lambda function to it, which adds the elements of the list together. The result is a single accumulated value, the sum of the numbers in the list.
wraps()
The
wraps() function is a decorator function that is used to update the metadata of an existing function to preserve its information. The
wraps() function is defined as follows:
“`python
import functools
def wraps(wrapped,
assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
return functools.partial(functools.update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
“`
Example
Let’s look at an example of using the
wraps() function in Python:
“`python
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
“””Wrapper function”””
print(“Starting”)
result = func(*args, **kwargs)
print(“Finished”)
return result
return wrapper
@my_decorator
def my_function():
“””Function”””
print(“Hello, world”)
print(my_function.__name__) # Output: “my_function”
print(my_function.__doc__) # Output: “Function”
“`
In the example above, we define a decorator function called my_decorator that prints a message before and after executing an existing function. We then use the
wraps() function to update the metadata of the wrapper function, preserving the original function’s name and docstring.
lru_
cache()
The
lru_
cache() function is a decorator function used to cache results of the callable function based on the arguments. This function makes use of the Least Recently Used (LRU) cache algorithm to store frequent results and discard the rarely used cache as the cache size exceeds the maxsize.
The
lru_
cache() function is defined as follows:
“`python
def lru_cache(maxsize=128, typed=False):
def decorator(func):
… return decorator
“`
Example
Let’s look at an example of using the
lru_
cache() function in Python:
“`python
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(number):
“””Return the nth number in the Fibonacci series.”””
if number == 0:
return 0
elif number == 1:
return 1
else:
return fibonacci(number – 1) + fibonacci(number – 2)
print([fibonacci(n) for n in range(16)]) # Output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
“`
In the example above, we define a function called fibonacci() that recursively calculates the nth number in the Fibonacci series. We then use the
lru_
cache() function to cache previously calculated results, which improves the performance of the function and reduces the number of recursive calls.
cache()
The
cache() function is used to cache the results of a callable function based on the arguments. The
cache() function is defined as follows:
“`python
def cache(user_function):
“””
Simple cache function that saves the result of the callable for future look ups.
“””
cache_map = {}
def wrapped_function(*args, **kwargs):
kernel = (args, tuple(kwargs.items()))
if kernel not in cache_map:
cache_map[kernel] = user_function(*args, **kwargs)
return cache_map[kernel]
return wrapped_function
“`
Example
Let’s look at an example of using the
cache() function in Python:
“`python
from functools import cache
@cache
def factorial(number):
“””Return the factorial of a number.”””
if number == 1:
return 1
else:
return number * factorial(number – 1)
print(factorial(5)) # Output: 120
“`
In the example above, we define a function called factorial() that recursively calculates the factorial of a number. We then use the
cache() function to cache previously calculated results, which improves the performance of the function and reduces the number of recursive calls.
cached_property()
The
cached_property() function is a decorator function that is used to create managed attributes with caching functionality. The
cached_property() function is defined as follows:
“`python
class cached_property:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner=None):
if instance is None:
return self
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value
“`
Example
Let’s look at an example of using the
cached_property() function in Python:
“`python
from functools import cached_property
class Circle:
def __init__(self, radius):
self.radius = radius
@cached_property
def area(self):
“””Return the area of the circle.”””
return 3.14 * self.radius ** 2
circle = Circle(5)
print(circle.area) # Output: 78.5
circle.radius = 10 # the cached value will still be 3.14 * 5 * 5, since it’s already calculated
print(circle.area) # Output: 78.5
“`
In the example above, we define a class called Circle that has a property called area which returns the area of the circle. We use the
cached_property() function to cache the property value so that it is calculated once and stored for future lookups.
total_ordering()
The
total_ordering() function is a class decorator that is used to define overloaded implementations of comparison operators. The
total_ordering() function is defined as follows:
“`python
class total_ordering:
def __init__(self, cls):
self.cls = cls
def __set_name__(self, owner, name):
if not hasattr(self.cls, “__lt__”):
raise ValueError(f”Cannot create total_ordering after {name} was already defined.”)
def __call__(self, cls, **kwargs):
self.cls = cls
self.__lt__ = kwargs.pop(“__lt__”, self.__lt__)
self.__le__ = kwargs.pop(“__le__”, self.__le__)
self.__eq__ = kwargs.pop(“__eq__”, self.__eq__)
self.__ne__ = kwargs.pop(“__ne__”, self.__ne__)
self.__ge__ = kwargs.pop(“__ge__”, self.__gt__)
self.__gt__ = kwargs.pop(“__gt__”, self.__gt__)
return self
def __lt__(self, other):
raise TypeError(“Cannot compare two objects”)
def __le__(self, other):
return self == other or self < other
def __eq__(self, other):
raise TypeError(“Cannot compare two objects”)
def __ne__(self, other):
return not self == other
def __ge__(self, other):
return self == other or self > other
def __gt__(self, other):
raise TypeError(“Cannot compare two objects”)
“`
Example
Let’s look at an example of using the
total_ordering() function in Python:
“`python
from functools import total_ordering
@total_ordering
class Player:
def __init__(self, name, score):
self.name = name
self.score = score
def __lt__(self, other):
return self.score < other.score
john = Player(‘John’, 45)
mary = Player(‘Mary’, 99)
print(john <= mary) # Output: True
“`
In the example above, we define a class called Player that has two properties, name and score. We use the
total_ordering() function to define overloaded implementations of comparison operators so that we can compare instances of the Player class using the less than, greater than, less than or equal to, and greater than or equal to operators.
singledispatch()
The
singledispatch() function is a decorator function that is used to define a generic implementation of a function as well as the specific implementations of that function for different argument types. The
singledispatch() function is defined as follows:
“`python
def singledispatch(function):
registry = {}
def dispatch(type_):
return registry.get(type_, function)
def decorator(func):
registry[object] = function
registry[func.__annotations__.get(‘type’, object)] = func
func.dispatch = dispatch
func.registry = registry
return func
dispatch.register = decorator
dispatch.registry = registry
return dispatch
“`
Example
Let’s look at an example of using the
singledispatch() function in Python:
“`python
from functools import singledispatch
@singledispatch
def greet(name):
print(f’Hello, {name}!’)
@greet.register(int)
def _(age):
print(f’Hello, age is {age}!’)
greet(‘Alice’) # Output: “Hello, Alice!”
greet(25) # Output: “Hello, age is 25!”
“`
In the example above, we define a function called greet that has a generic implementation for greeting a person. We use the
singledispatch() function to define specific implementations for greeting a person based on their type, like int.
We then call the function twice, once with a string argument and once with an integer argument, and it prints out the respective messages.
Conclusion
The functools module provides a set of functions that allow developers to write cleaner and more efficient code. These higher-order functions can be used to extend the functionality of existing functions, improve code readability, and write efficient code.
By using functions like partial(),
reduce(),
cached_property(), and
total_ordering(), developers can write code that is more modular, reusable, and maintainable. With the help of these functions, Python becomes even more powerful and versatile, providing developers with the tools they need to write code that is easy to understand and maintainable.
3)
partialmethod() function in functools module
In Python classes, a method is a function that is defined inside a class and is intended to operate on an instance of that class. The method is often used to perform some kind of behavior on the object.
Python’s functools module provides the
partialmethod() function which is used to apply partial arguments to class methods, making it easier to set default values for arguments in methods. This article will focus on the
partialmethod() function and how it can be used to create convenient methods that set values for class instances.The
partialmethod() function in the functools module is a variant of the partial() function, and is used to apply partial arguments to class methods.
Instead of creating a new function object, a partial method object is created that can be used to set default values for the arguments of the method.
Example
Let’s look at an example of using the
partialmethod() function to create convenience methods in Python:
“`python
from functools import partialmethod
class Character:
def __init__(self, name, has_magic=False, magic=None):
self.name = name
self.has_magic = has_magic
self.magic = magic
def set_has_magic(self, has_magic: bool):
self.has_magic = has_magic
def set_magic(self, magic: str):
self.magic = magic
set_has_magic_on = partialmethod(set_has_magic, True)
set_has_magic_off = partialmethod(set_has_magic, False)
set_magic_fire = partialmethod(set_magic, “fire”)
set_magic_ice = partialmethod(set_magic, “ice”)
“`
In the example above, we have defined a class called `Character` with an `__init__` method that takes in arguments such as name, has_magic, and magic. We also have defined two methods called `set_has_magic` and `set_magic` that allow users to set values for these attributes.
We use the `partialmethod` function to create four new methods that set default values for these attributes:
– `set_has_magic_on` sets the value