Code
from functools import partial
def multiply(x, y):
return x * y
double = partial(multiply, y=2)
print(double(5)) # Output: 1010
Python’s functools module provides a collection of higher-order functions that operate on or return other functions.
Let’s explore four powerful tools from this module: partial, singledispatch, wraps, and partialmethod.
The partial function allows you to create new functions with pre-set arguments, effectively specializing existing functions for specific use cases
from functools import partial
def multiply(x, y):
return x * y
double = partial(multiply, y=2)
print(double(5)) # Output: 1010
from functools import partial
def scale_data(value, factor):
return value * factor
scale_by_2 = partial(scale_data, factor=2)
scale_by_10 = partial(scale_data, factor=10)
data = [1, 2, 3, 4, 5]
scaled_by_2 = list(map(scale_by_2, data))
scaled_by_10 = list(map(scale_by_10, data))
print(scaled_by_2) # Output: [2, 4, 6, 8, 10]
print(scaled_by_10) # Output: [10, 20, 30, 40, 50][2, 4, 6, 8, 10]
[10, 20, 30, 40, 50]
The singledispatch decorator enables function overloading based on the type of the first argument, allowing different implementations for different data types.
from functools import singledispatch
from dataclasses import dataclass
@dataclass
class Book:
title: str
author: str
year: int
def __str__(self):
return f"{self.title} by {self.author} ({self.year})"
@singledispatch
def process_data(data):
return f"processing for {data}"
@process_data.register(int)
def _(data):
return f"integer: {data}"
@process_data.register(str)
def _(data):
return f"string: {data}"
@process_data.register(Book)
def _(data):
return f"book: {data.title} written by {data.author} in {data.year}"
print(process_data(42))
print(process_data("Hello, World!"))
print(process_data(Book("1984", "George Orwell", 1949)))integer: 42
string: Hello, World!
book: 1984 written by George Orwell in 1949
The wraps decorator helps maintain the original function’s metadata when creating decorators, ensuring that important information is not lost
import logging
from functools import wraps
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def my_decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
logger.info(f"Calling {f.__name__}")
logger.info(f"Function docstring: {f.__doc__}")
logger.info(f"Function annotations: {f.__annotations__}")
print("Before function call")
result = f(*args, **kwargs)
print("After function call")
return result
return wrapper
@my_decorator
def greet(name: str) -> str:
"""Returns a friendly greeting"""
return f"Hello, {name}!"
print(greet("Alice"))
print(f"Function name: {greet.__name__}")
print(f"Function docstring: {greet.__doc__}")
print(f"Function annotations: {greet.__annotations__}")INFO:__main__:Calling greet
INFO:__main__:Function docstring: Returns a friendly greeting
INFO:__main__:Function annotations: {'name': <class 'str'>, 'return': <class 'str'>}
Before function call
After function call
Hello, Alice!
Function name: greet
Function docstring: Returns a friendly greeting
Function annotations: {'name': <class 'str'>, 'return': <class 'str'>}
Similar to partial, partialmethod allows partial application of arguments to methods within a class
from functools import partialmethod
class Calculator:
def add(self, x, y):
return x + y
add_five = partialmethod(add, 5)
calc = Calculator()
print(calc.add_five(10)) # Output: 1515