Code
from functools import partial
def multiply(x, y):
return x * y
= partial(multiply, y=2)
double print(double(5)) # Output: 10
10
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
= partial(multiply, y=2)
double print(double(5)) # Output: 10
10
from functools import partial
def scale_data(value, factor):
return value * factor
= partial(scale_data, factor=2)
scale_by_2 = partial(scale_data, factor=10)
scale_by_10
= [1, 2, 3, 4, 5]
data = list(map(scale_by_2, data))
scaled_by_2 = list(map(scale_by_10, data))
scaled_by_10
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:
str
title: str
author: int
year:
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.INFO)
logging.basicConfig(level= logging.getLogger(__name__)
logger
def my_decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
f"Calling {f.__name__}")
logger.info(f"Function docstring: {f.__doc__}")
logger.info(f"Function annotations: {f.__annotations__}")
logger.info(print("Before function call")
= f(*args, **kwargs)
result 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
= partialmethod(add, 5)
add_five
= Calculator()
calc print(calc.add_five(10)) # Output: 15
15