πŸ› οΈ Python Tutorial β€” Functools Module

Introduction 🌟

The functools module provides higher-order functionsβ€”tools that act on or return other functions. It enables memoization, function partial application, ordering, caching, and more. These tools make your Python code cleaner, faster, and more functional.

Note

πŸ’‘ β€œFunctional Programming Helpers”
πŸ’‘ Contains powerful decorators like @lru_cache
πŸ’‘ Perfect for optimization, callbacks, sorting, pipelines

1. Importing functools 🧱

import_functools.py

import functools

2. partial() β€” Pre-Fill Function Arguments 🎯

partial() allows you to pre-set some arguments of a function, creating a new function.

partial_example.py

import functools

def power(base, exp):
    return base ** exp

square = functools.partial(power, exp=2)
cube = functools.partial(power, exp=3)

print(square(5))  # 25
print(cube(4))    # 64

βœ” Converts multi-argument functions into simpler ones
βœ” Great for callbacks, mappings, default configurations

3. lru_cache() β€” Memoization for Speed ⚑

@functools.lru_cache caches function results β†’ dramatically speeds up repeated calls.

lru_cache_example.py

import functools

@functools.lru_cache(maxsize=1000)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

print(fib(30))

Note

πŸ’‘ lru_cache prevents recomputation
πŸ’‘ Makes expensive recursive algorithms extremely fast

4. reduce() β€” Functional Reduction πŸ”„

reduce() applies a function cumulatively to items in a sequence.

reduce_example.py

import functools

nums = [1, 2, 3, 4]

total = functools.reduce(lambda a, b: a + b, nums)
print(total)  # 10

βœ” Useful for sums, products, custom fold operations

5. cmp_to_key() β€” Custom Sorting Logic πŸ”‘

Converts old-style compare functions into key functions for sorting.

cmp_to_key_example.py

import functools

def compare(a, b):
    if a % 10 < b % 10: return -1
    if a % 10 > b % 10: return 1
    return 0

nums = [12, 25, 33, 47]
sorted_nums = sorted(nums, key=functools.cmp_to_key(compare))

print(sorted_nums)

βœ” Enables fine-grained control over sorting behavior
βœ” Great for custom ranking systems

6. singledispatch() β€” Function Overloading in Python 🚦

singledispatch_example.py

from functools import singledispatch

@singledispatch
def process(x):
    print("Default:", x)

@process.register(int)
def _(x):
    print("Integer:", x)

@process.register(list)
def _(x):
    print("List:", x)

process(10)
process([1, 2, 3])
process("Hello")

Note

πŸ’‘ Allows multiple implementations based on argument type
πŸ’‘ β€œFunction overloading” in Python

7. wraps() β€” Preserve Metadata for Decorators 🎁

When writing custom decorators, @wraps preserves function name, docstring, and metadata.

wraps_example.py

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("Calling function...")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def greet():
    "Say hello"
    print("Hello!")

print(greet.__name__)   # greet
print(greet.__doc__)    # Say hello

βœ” Essential when building clean decorators

8. cached_property β€” Fast Property Caching 🏎️

cached_property_example.py

from functools import cached_property
import time

class Data:
    @cached_property
    def value(self):
        time.sleep(2)
        return 100

d = Data()
print(d.value)  # slow first time
print(d.value)  # instant

βœ” Ideal for expensive calculations
βœ” Result stored after first access

9. total_ordering β€” Auto-Generate Comparison Methods βš–οΈ

total_ordering_example.py

from functools import total_ordering

@total_ordering
class Number:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        return self.value == other.value

    def __lt__(self, other):
        return self.value < other.value

n1 = Number(5)
n2 = Number(10)

print(n1 < n2)
print(n1 >= n2)

βœ” Only define __eq__ + one comparison method
βœ” Automatically gets: >, <=, >=

10. Practical Example β€” Rate Limiter ⏳

rate_limiter.py

import functools, time

def rate_limit(func):
    last_called = [0]
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        if time.time() - last_called[0] < 2:
            print("Too fast!")
            return
        last_called[0] = time.time()
        return func(*args, **kwargs)
    return wrapper

@rate_limit
def api_call():
    print("API call executed")

api_call()
api_call()

βœ” Real-world use of decorators + wraps

Functools Cheat Sheet πŸ“˜

FunctionPurpose
partialPre-fill parameters
lru_cacheMemoization/caching
reduceFunctional reduction
cmp_to_keyCustom sorting logic
singledispatchFunction overloading
wrapsDecorator metadata preservation
cached_propertyProperty caching
total_orderingAuto-generate comparison operators

Best Practices πŸ’‘

  • βœ” Use @lru_cache for expensive functions
  • βœ” Use partial() to simplify callbacks & repetitive code
  • βœ” Always wrap decorators with @wraps
  • βœ” Prefer singledispatch for type-based logic
  • βœ” Use total_ordering to reduce boilerplate

Conclusion πŸŽ‰

>>β€œfunctools enhances Python functionsβ€”making your code faster, cleaner, and beautifully functional.” ✨

You now fully understand the Functools module! Want the next topic? Try Statistics, Decimal, Pathlib, Shutil, or Subprocess. Just tell me! 😊