Repeated expensive calculations slow applications, and repetitive function signatures clutter code. The functools module provides caching decorators for performance optimization, partial application for cleaner APIs, and reduction operations for aggregating sequences.

Total Ordering

Generate comparison methods automatically:

reduce.py
# functools.reduce examples

from functools import reduce

# Basic reduce
print("Basic reduce:")

# Sum with reduce
numbers = 
total = reduce(lambda x, y: x + y, numbers)
print(f"Sum of {numbers}: {total}")

# Product with reduce
product = reduce(lambda x, y: x * y, numbers)
print(f"Product of {numbers}: {product}")

# Max with reduce
values = [15, 42, 8, 93, 27]
maximum = reduce(lambda x, y: x if x > y else y, values)
print(f"Max of {values}: {maximum}")

# Reduce with initial value
print("\nReduce with initial value:")

# Sum with initial value
nums = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, nums, 10)
print(f"Sum of {nums} + 10: {result}")

# Count occurrences
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
count = reduce(lambda acc, word: acc + 1 if word == 'apple' else acc, words, 0)
print(f"Count 'apple' in {words}: {count}")

# String operations
print("\nString operations:")

# Concatenate strings
strings = ['Hello', ' ', 'World', '!']
message = reduce(lambda x, y: x + y, strings)
print(f"Concatenated: {message}")

# Build sentence
words_list = ['Python', 'is', 'awesome']
sentence = reduce(lambda x, y: f"{x} {y}", words_list)
print(f"Sentence: {sentence}")

# List operations
print("\nList operations:")

# Flatten nested lists
nested = [[1, 2], [3, 4], [5, 6]]
flattened = reduce(lambda x, y: x + y, nested)
print(f"Nested: {nested}")
print(f"Flattened: {flattened}")

# Merge dictionaries
dicts = [{'a': 1}, {'b': 2}, {'c': 3}]
merged = reduce(lambda x, y: {**x, **y}, dicts)
print(f"Merged dicts: {merged}")

# Mathematical operations
print("\nMathematical operations:")

# Factorial
n = 5
factorial = reduce(lambda x, y: x * y, range(1, n + 1))
print(f"{n}! = {factorial}")

# Power
base = 2
exponent = 10
power = reduce(lambda x, y: x * y, [base] * exponent)
print(f"{base}^{exponent} = {power}")

# GCD of list
import math
numbers_gcd = [48, 64, 80]
gcd = reduce(math.gcd, numbers_gcd)
print(f"GCD of {numbers_gcd}: {gcd}")

# Custom accumulator
print("\nCustom accumulator:")

# Build histogram
data = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
histogram = reduce(
    lambda acc, x: {**acc, x: acc.get(x, 0) + 1},
    data,
    {}
)
print(f"Data: {data}")
print(f"Histogram: {histogram}")

# Running totals
values_list = [10, 20, 30, 40]
def running_sum(acc, x):
    return acc + [acc[-1] + x]

totals = reduce(running_sum, values_list, [0])
print(f"Values: {values_list}")
print(f"Running totals: {totals}")

# Comparison with alternatives
print("\nComparison with alternatives:")

nums_compare = [1, 2, 3, 4, 5]

# Using reduce
sum_reduce = reduce(lambda x, y: x + y, nums_compare)
print(f"reduce: {sum_reduce}")

# Using sum()
sum_builtin = sum(nums_compare)
print(f"sum():  {sum_builtin}")

# Using loop
sum_loop = 0
for n in nums_compare:
    sum_loop += n
print(f"loop:   {sum_loop}")

# Prefer built-ins when available
print(f"max() is clearer than reduce: {max(nums_compare)}")

# functools.reduce examples

from functools import reduce

# Basic reduce
print("Basic reduce:")

# Sum with reduce
numbers = 
total = reduce(lambda x, y: x + y, numbers)
print(f"Sum of {numbers}: {total}")

# Product with reduce
product = reduce(lambda x, y: x * y, numbers)
print(f"Product of {numbers}: {product}")

# Max with reduce
values = [15, 42, 8, 93, 27]
maximum = reduce(lambda x, y: x if x > y else y, values)
print(f"Max of {values}: {maximum}")

# Reduce with initial value
print("\nReduce with initial value:")

# Sum with initial value
nums = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, nums, 10)
print(f"Sum of {nums} + 10: {result}")

# Count occurrences
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
count = reduce(lambda acc, word: acc + 1 if word == 'apple' else acc, words, 0)
print(f"Count 'apple' in {words}: {count}")

# String operations
print("\nString operations:")

# Concatenate strings
strings = ['Hello', ' ', 'World', '!']
message = reduce(lambda x, y: x + y, strings)
print(f"Concatenated: {message}")

# Build sentence
words_list = ['Python', 'is', 'awesome']
sentence = reduce(lambda x, y: f"{x} {y}", words_list)
print(f"Sentence: {sentence}")

# List operations
print("\nList operations:")

# Flatten nested lists
nested = [[1, 2], [3, 4], [5, 6]]
flattened = reduce(lambda x, y: x + y, nested)
print(f"Nested: {nested}")
print(f"Flattened: {flattened}")

# Merge dictionaries
dicts = [{'a': 1}, {'b': 2}, {'c': 3}]
merged = reduce(lambda x, y: {**x, **y}, dicts)
print(f"Merged dicts: {merged}")

# Mathematical operations
print("\nMathematical operations:")

# Factorial
n = 5
factorial = reduce(lambda x, y: x * y, range(1, n + 1))
print(f"{n}! = {factorial}")

# Power
base = 2
exponent = 10
power = reduce(lambda x, y: x * y, [base] * exponent)
print(f"{base}^{exponent} = {power}")

# GCD of list
import math
numbers_gcd = [48, 64, 80]
gcd = reduce(math.gcd, numbers_gcd)
print(f"GCD of {numbers_gcd}: {gcd}")

# Custom accumulator
print("\nCustom accumulator:")

# Build histogram
data = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
histogram = reduce(
    lambda acc, x: {**acc, x: acc.get(x, 0) + 1},
    data,
    {}
)
print(f"Data: {data}")
print(f"Histogram: {histogram}")

# Running totals
values_list = [10, 20, 30, 40]
def running_sum(acc, x):
    return acc + [acc[-1] + x]

totals = reduce(running_sum, values_list, [0])
print(f"Values: {values_list}")
print(f"Running totals: {totals}")

# Comparison with alternatives
print("\nComparison with alternatives:")

nums_compare = [1, 2, 3, 4, 5]

# Using reduce
sum_reduce = reduce(lambda x, y: x + y, nums_compare)
print(f"reduce: {sum_reduce}")

# Using sum()
sum_builtin = sum(nums_compare)
print(f"sum():  {sum_builtin}")

# Using loop
sum_loop = 0
for n in nums_compare:
    sum_loop += n
print(f"loop:   {sum_loop}")

# Prefer built-ins when available
print(f"max() is clearer than reduce: {max(nums_compare)}")

# functools.reduce examples

from functools import reduce

# Basic reduce
print("Basic reduce:")

# Sum with reduce
numbers = 
total = reduce(lambda x, y: x + y, numbers)
print(f"Sum of {numbers}: {total}")

# Product with reduce
product = reduce(lambda x, y: x * y, numbers)
print(f"Product of {numbers}: {product}")

# Max with reduce
values = [15, 42, 8, 93, 27]
maximum = reduce(lambda x, y: x if x > y else y, values)
print(f"Max of {values}: {maximum}")

# Reduce with initial value
print("\nReduce with initial value:")

# Sum with initial value
nums = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, nums, 10)
print(f"Sum of {nums} + 10: {result}")

# Count occurrences
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
count = reduce(lambda acc, word: acc + 1 if word == 'apple' else acc, words, 0)
print(f"Count 'apple' in {words}: {count}")

# String operations
print("\nString operations:")

# Concatenate strings
strings = ['Hello', ' ', 'World', '!']
message = reduce(lambda x, y: x + y, strings)
print(f"Concatenated: {message}")

# Build sentence
words_list = ['Python', 'is', 'awesome']
sentence = reduce(lambda x, y: f"{x} {y}", words_list)
print(f"Sentence: {sentence}")

# List operations
print("\nList operations:")

# Flatten nested lists
nested = [[1, 2], [3, 4], [5, 6]]
flattened = reduce(lambda x, y: x + y, nested)
print(f"Nested: {nested}")
print(f"Flattened: {flattened}")

# Merge dictionaries
dicts = [{'a': 1}, {'b': 2}, {'c': 3}]
merged = reduce(lambda x, y: {**x, **y}, dicts)
print(f"Merged dicts: {merged}")

# Mathematical operations
print("\nMathematical operations:")

# Factorial
n = 5
factorial = reduce(lambda x, y: x * y, range(1, n + 1))
print(f"{n}! = {factorial}")

# Power
base = 2
exponent = 10
power = reduce(lambda x, y: x * y, [base] * exponent)
print(f"{base}^{exponent} = {power}")

# GCD of list
import math
numbers_gcd = [48, 64, 80]
gcd = reduce(math.gcd, numbers_gcd)
print(f"GCD of {numbers_gcd}: {gcd}")

# Custom accumulator
print("\nCustom accumulator:")

# Build histogram
data = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
histogram = reduce(
    lambda acc, x: {**acc, x: acc.get(x, 0) + 1},
    data,
    {}
)
print(f"Data: {data}")
print(f"Histogram: {histogram}")

# Running totals
values_list = [10, 20, 30, 40]
def running_sum(acc, x):
    return acc + [acc[-1] + x]

totals = reduce(running_sum, values_list, [0])
print(f"Values: {values_list}")
print(f"Running totals: {totals}")

# Comparison with alternatives
print("\nComparison with alternatives:")

nums_compare = [1, 2, 3, 4, 5]

# Using reduce
sum_reduce = reduce(lambda x, y: x + y, nums_compare)
print(f"reduce: {sum_reduce}")

# Using sum()
sum_builtin = sum(nums_compare)
print(f"sum():  {sum_builtin}")

# Using loop
sum_loop = 0
for n in nums_compare:
    sum_loop += n
print(f"loop:   {sum_loop}")

# Prefer built-ins when available
print(f"max() is clearer than reduce: {max(nums_compare)}")

cache.py
# functools.lru_cache examples

from functools import lru_cache
import time

# Basic caching
print("Basic caching:")

@lru_cache(maxsize=128)
def expensive_function(n):
    """Simulate expensive computation."""
    time.sleep(0.001)  # Simulate delay
    return n * n

# First call (slow)
start = time.time()
result1 = expensive_function(10)
time1 = time.time() - start

# Second call (fast - cached)
start = time.time()
result2 = expensive_function(10)
time2 = time.time() - start

print(f"First call:  {result1} ({time1:.4f}s)")
print(f"Second call: {result2} ({time2:.4f}s)")
print(f"Speedup: {time1/time2:.1f}x")

# Fibonacci with cache
print("\nFibonacci with cache:")

@lru_cache(maxsize=None)  # Unlimited cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

# Fast computation
start = time.time()
result = fibonacci(12)
elapsed = time.time() - start

print(f"fibonacci(12) = {result}")
print(f"Time: {elapsed:.4f}s")

# Without cache would take much longer
def fib_no_cache(n):
    if n < 2:
        return n
    return fib_no_cache(n - 1) + fib_no_cache(n - 2)

start = time.time()
result_no_cache = fib_no_cache(8)  # Much smaller n
elapsed_no_cache = time.time() - start

print(f"\nWithout cache:")
print(f"fibonacci(8) = {result_no_cache}")
print(f"Time: {elapsed_no_cache:.4f}s")

# Cache info
print("\nCache info:")

@lru_cache(maxsize=3)
def compute(x):
    return x * 2

# Make some calls
for i in range(5):
    compute(i)

# Check hits
for i in range(3):
    compute(i)

info = compute.cache_info()
print(f"Hits: {info.hits}")
print(f"Misses: {info.misses}")
print(f"Size: {info.currsize}")
print(f"Max size: {info.maxsize}")

# Cache clear
print("\nCache clear:")

@lru_cache(maxsize=128)
def add(x, y):
    print(f"  Computing {x} + {y}")
    return x + y

print("First calls:")
add(1, 2)
add(3, 4)
add(1, 2)  # Cached

print("\nAfter clearing:")
add.cache_clear()
add(1, 2)  # Not cached anymore

# Maxsize effects
print("\nMaxsize effects:")

@lru_cache(maxsize=2)
def limited(n):
    return n * n

# Fill cache
limited(1)  # Miss
limited(2)  # Miss
limited(1)  # Hit

# Evict oldest
limited(3)  # Miss, evicts 2
limited(1)  # Hit
limited(2)  # Miss (was evicted)

info = limited.cache_info()
print(f"Hits: {info.hits}, Misses: {info.misses}")

# Prime checking with cache
print("\nPrime checking with cache:")

@lru_cache(maxsize=1000)
def is_prime(n):
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    for i in range(3, int(n**0.5) + 1, 2):
        if n % i == 0:
            return False
    return True

# Check primes multiple times
primes = [p for p in range(30) if is_prime(p)]
print(f"Primes < 30: {len(primes)}")

# Recheck (cached)
start = time.time()
primes_again = [p for p in range(30) if is_prime(p)]
elapsed = time.time() - start

print(f"Rechecking (cached): {elapsed*1000:.2f}ms")

info = is_prime.cache_info()
print(f"Cache: {info.hits} hits, {info.misses} misses")

# Distance calculation
print("\nDistance calculation:")

@lru_cache(maxsize=256)
def distance(x1, y1, x2, y2):
    """Calculate Euclidean distance."""
    return ((x2 - x1)**2 + (y2 - y1)**2)**0.5

# Calculate distances
points = [(0, 0), (3, 4), (1, 1), (0, 0), (3, 4)]

for i in range(len(points)):
    for j in range(i + 1, len(points)):
        d = distance(*points[i], *points[j])
        print(f"  {points[i]} to {points[j]}: {d:.2f}")

info = distance.cache_info()
print(f"Cache: {info.hits} hits, {info.misses} misses")

# Memoization pattern
print("\nMemoization pattern:")

@lru_cache(maxsize=None)
def count_paths(m, n):
    """Count paths in m x n grid."""
    if m == 1 or n == 1:
        return 1
    return count_paths(m - 1, n) + count_paths(m, n - 1)

result = count_paths(5, 5)
print(f"Paths in 5x5 grid: {result}")

info = count_paths.cache_info()
print(f"Cache efficiency: {info.hits}/{info.hits + info.misses} hits")

partial.py
# functools.partial examples

from functools import partial

# Basic partial
print("Basic partial:")

def multiply(x, y):
    return x * y

# Create specialized functions
double = partial(multiply, 2)
triple = partial(multiply, 3)

print(f"double(5) = {double(5)}")
print(f"triple(5) = {triple(5)}")

# Partial with multiple args
print("\nPartial with multiple args:")

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

# Fix base
square = partial(power, exponent=2)
cube = partial(power, exponent=3)

print(f"square(5) = {square(5)}")
print(f"cube(5) = {cube(5)}")

# Fix exponent
power_of_2 = partial(power, 2)
power_of_10 = partial(power, 10)

print(f"2^8 = {power_of_2(8)}")
print(f"10^3 = {power_of_10(3)}")

# String formatting
print("\nString formatting:")

def format_message(prefix, message, suffix):
    return f"{prefix}{message}{suffix}"

# Create specialized formatters
error = partial(format_message, "[ERROR] ", suffix="")
warning = partial(format_message, "[WARN] ", suffix="")
info = partial(format_message, "[INFO] ", suffix="")

print(error("File not found"))
print(warning("Deprecated function"))
print(info("Process started"))

# Sorting with partial
print("\nSorting with partial:")

# Sort by custom key
students = [
    {'name': 'Alice', 'age': 20, 'grade': 85},
    {'name': 'Bob', 'age': 22, 'grade': 92},
    {'name': 'Charlie', 'age': 21, 'grade': 78}
]

from operator import itemgetter

# Create reusable key functions
by_name = partial(sorted, key=itemgetter('name'))
by_age = partial(sorted, key=itemgetter('age'))
by_grade = partial(sorted, key=itemgetter('grade'))

print("By name:")
for s in by_name(students):
    print(f"  {s['name']}: {s['grade']}")

print("By grade:")
for s in by_grade(students):
    print(f"  {s['name']}: {s['grade']}")

# File operations
print("\nFile operations:")

def read_file(filename, mode='r', encoding='utf-8'):
    """Simulate file reading."""
    return f"Reading {filename} (mode={mode}, encoding={encoding})"

# Create specialized readers
read_text = partial(read_file, mode='r', encoding='utf-8')
read_binary = partial(read_file, mode='rb', encoding=None)
read_json = partial(read_file, mode='r', encoding='utf-8')

print(read_text('data.txt'))
print(read_binary('image.png'))

# Logging
print("\nLogging:")

def log(level, component, message):
    print(f"[{level}] {component}: {message}")

# Create component-specific loggers
db_log = partial(log, component='Database')
api_log = partial(log, component='API')
cache_log = partial(log, component='Cache')

db_log(level='INFO', message='Connected to PostgreSQL')
api_log(level='WARN', message='Rate limit approaching')
cache_log(level='ERROR', message='Redis connection failed')

# Map with partial
print("\nMap with partial:")

def add(x, y):
    return x + y

# Add 10 to each number
numbers = [1, 2, 3, 4, 5]
add_10 = partial(add, 10)
result = list(map(add_10, numbers))

print(f"Original: {numbers}")
print(f"Add 10:   {result}")

# Filter with partial
print("\nFilter with partial:")

def greater_than(threshold, value):
    return value > threshold

# Filter values > 50
values = [25, 60, 45, 80, 30, 95]
above_50 = partial(greater_than, 50)
filtered = list(filter(above_50, values))

print(f"Values: {values}")
print(f"Above 50: {filtered}")

# Callback functions
print("\nCallback functions:")

def process_data(data, validator, transformer):
    if validator(data):
        return transformer(data)
    return None

def is_positive(x):
    return x > 0

def is_even(x):
    return x % 2 == 0

def square(x):
    return x * x

# Create specialized processors
process_positive = partial(process_data, validator=is_positive, transformer=square)
process_even = partial(process_data, validator=is_even, transformer=square)

print(f"Process positive 5: {process_positive(5)}")
print(f"Process positive -3: {process_positive(-3)}")
print(f"Process even 4: {process_even(4)}")
print(f"Process even 3: {process_even(3)}")

# Partial attributes
print("\nPartial attributes:")

greet = partial(format_message, "Hello, ")

print(f"Function: {greet.func}")
print(f"Args: {greet.args}")
print(f"Keywords: {greet.keywords}")

dispatch.py
# functools.singledispatch examples

from functools import singledispatch
from decimal import Decimal

# Basic singledispatch
print("Basic singledispatch:")

@singledispatch
def process(arg):
    """Default implementation."""
    print(f"Processing {type(arg).__name__}: {arg}")

@process.register(int)
def _(arg):
    print(f"Integer: {arg} (squared: {arg**2})")

@process.register(str)
def _(arg):
    print(f"String: '{arg}' (length: {len(arg)})")

@process.register(list)
def _(arg):
    print(f"List: {arg} (sum: {sum(arg)})")

# Call with different types
process(10)
process("hello")
process([1, 2, 3, 4, 5])
process(3.14)  # Uses default

# Format function
print("\nFormat function:")

@singledispatch
def format_value(val):
    """Default formatter."""
    return str(val)

@format_value.register(int)
def _(val):
    return f"{val:,}"

@format_value.register(float)
def _(val):
    return f"{val:.2f}"

@format_value.register(bool)
def _(val):
    return "Yes" if val else "No"

@format_value.register(list)
def _(val):
    return f"[{len(val)} items]"

print(f"Integer: {format_value(1000000)}")
print(f"Float: {format_value(3.14159)}")
print(f"Bool: {format_value(True)}")
print(f"List: {format_value([1, 2, 3, 4, 5])}")

# Serialize function
print("\nSerialize function:")

@singledispatch
def serialize(obj):
    """Default serialization."""
    raise NotImplementedError(f"Cannot serialize {type(obj)}")

@serialize.register(int)
@serialize.register(float)
@serialize.register(str)
def _(obj):
    return obj

@serialize.register(list)
def _(obj):
    return [serialize(item) for item in obj]

@serialize.register(dict)
def _(obj):
    return {key: serialize(value) for key, value in obj.items()}

data = {
    'name': 'Alice',
    'age': 30,
    'scores': [85, 92, 78],
    'active': True
}

try:
    result = serialize(data)
    print(f"Serialized: {result}")
except NotImplementedError as e:
    print(f"Error: {e}")

# Area calculation
print("\nArea calculation:")

@singledispatch
def area(shape):
    """Calculate area of shape."""
    raise NotImplementedError(f"Unknown shape: {type(shape)}")

class Circle:
    def __init__(self, radius):
        self.radius = radius

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

@area.register(Circle)
def _(shape):
    import math
    return math.pi * shape.radius ** 2

@area.register(Rectangle)
def _(shape):
    return shape.width * shape.height

@area.register(Triangle)
def _(shape):
    return 0.5 * shape.base * shape.height

# Calculate areas
circle = Circle(5)
rectangle = Rectangle(4, 6)
triangle = Triangle(3, 8)

print(f"Circle area: {area(circle):.2f}")
print(f"Rectangle area: {area(rectangle):.2f}")
print(f"Triangle area: {area(triangle):.2f}")

# Type conversion
print("\nType conversion:")

@singledispatch
def to_string(val):
    """Convert to string."""
    return str(val)

@to_string.register(int)
def _(val):
    return f"Integer: {val}"

@to_string.register(float)
def _(val):
    return f"Float: {val:.4f}"

@to_string.register(Decimal)
def _(val):
    return f"Decimal: {val}"

@to_string.register(list)
def _(val):
    return f"List with {len(val)} elements"

print(to_string(42))
print(to_string(3.14159))
print(to_string(Decimal('10.50')))
print(to_string([1, 2, 3]))

# HTML rendering
print("\nHTML rendering:")

@singledispatch
def to_html(data):
    """Render data as HTML."""
    return f"<span>{data}</span>"

@to_html.register(int)
@to_html.register(float)
def _(data):
    return f"<strong>{data}</strong>"

@to_html.register(str)
def _(data):
    return f"<p>{data}</p>"

@to_html.register(list)
def _(data):
    items = ''.join(f"<li>{to_html(item)}</li>" for item in data)
    return f"<ul>{items}</ul>"

@to_html.register(dict)
def _(data):
    rows = ''.join(
        f"<tr><td>{key}</td><td>{to_html(value)}</td></tr>"
        for key, value in data.items()
    )
    return f"<table>{rows}</table>"

print(to_html("Hello"))
print(to_html(42))
print(to_html([1, 2, 3]))

# Validator
print("\nValidator:")

@singledispatch
def validate(val):
    """Validate value."""
    return True

@validate.register(str)
def _(val):
    return len(val) > 0

@validate.register(int)
def _(val):
    return val >= 0

@validate.register(list)
def _(val):
    return len(val) > 0 and all(validate(item) for item in val)

print(f"Validate 'hello': {validate('hello')}")
print(f"Validate '': {validate('')}")
print(f"Validate 10: {validate(10)}")
print(f"Validate -5: {validate(-5)}")
print(f"Validate [1, 2]: {validate([1, 2])}")

# Type dispatch info
print("\nType dispatch info:")

@singledispatch
def demo(arg):
    return "default"

@demo.register(int)
def _(arg):
    return "int"

@demo.register(str)
def _(arg):
    return "str"

# Check registered types
print(f"Registry: {demo.registry.keys()}")
print(f"Dispatch(int): {demo.dispatch(int)}")
print(f"Dispatch(str): {demo.dispatch(str)}")

ordering.py
# functools.total_ordering examples

from functools import total_ordering

# Basic total_ordering
print("Basic total_ordering:")

@total_ordering
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade
    
    def __eq__(self, other):
        return self.grade == other.grade
    
    def __lt__(self, other):
        return self.grade < other.grade
    
    def __repr__(self):
        return f"Student('{self.name}', {self.grade})"

# Create students
alice = Student("Alice", 85)
bob = Student("Bob", 92)
charlie = Student("Charlie", 85)

# All comparison operations work
print(f"alice < bob: {alice < bob}")
print(f"alice <= bob: {alice <= bob}")
print(f"alice > bob: {alice > bob}")
print(f"alice >= bob: {alice >= bob}")
print(f"alice == charlie: {alice == charlie}")
print(f"alice != bob: {alice != bob}")

# Sorting
print("\nSorting:")

students = [
    Student("David", 78),
    Student("Eve", 95),
    Student("Frank", 82),
    Student("Grace", 88)
]

print("Unsorted:")
for s in students:
    print(f"  {s}")

sorted_students = sorted(students)

print("Sorted by grade:")
for s in sorted_students:
    print(f"  {s}")

# Temperature class
print("\nTemperature class:")

@total_ordering
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius
    
    def __eq__(self, other):
        return self.celsius == other.celsius
    
    def __lt__(self, other):
        return self.celsius < other.celsius
    
    def __repr__(self):
        return f"{self.celsius}°C"

temps = [
    Temperature(20),
    Temperature(5),
    Temperature(30),
    Temperature(15)
]

print(f"Temperatures: {temps}")
print(f"Sorted: {sorted(temps)}")
print(f"Min: {min(temps)}")
print(f"Max: {max(temps)}")

# Version class
print("\nVersion class:")

@total_ordering
class Version:
    def __init__(self, version_string):
        self.parts = tuple(map(int, version_string.split('.')))
    
    def __eq__(self, other):
        return self.parts == other.parts
    
    def __lt__(self, other):
        return self.parts < other.parts
    
    def __repr__(self):
        return '.'.join(map(str, self.parts))

versions = [
    Version("1.2.3"),
    Version("1.10.0"),
    Version("1.2.10"),
    Version("2.0.0")
]

print(f"Versions: {versions}")
print(f"Sorted: {sorted(versions)}")

# Money class
print("\nMoney class:")

@total_ordering
class Money:
    def __init__(self, amount, currency='USD'):
        self.amount = amount
        self.currency = currency
    
    def __eq__(self, other):
        if self.currency != other.currency:
            raise ValueError("Cannot compare different currencies")
        return self.amount == other.amount
    
    def __lt__(self, other):
        if self.currency != other.currency:
            raise ValueError("Cannot compare different currencies")
        return self.amount < other.amount
    
    def __repr__(self):
        return f"${self.amount:.2f} {self.currency}"

prices = [
    Money(19.99),
    Money(5.99),
    Money(12.50),
    Money(25.00)
]

print(f"Prices: {prices}")
print(f"Cheapest: {min(prices)}")
print(f"Most expensive: {max(prices)}")

# Date class
print("\nDate class:")

@total_ordering
class SimpleDate:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
    
    def __eq__(self, other):
        return (self.year, self.month, self.day) == \
               (other.year, other.month, other.day)
    
    def __lt__(self, other):
        return (self.year, self.month, self.day) < \
               (other.year, other.month, other.day)
    
    def __repr__(self):
        return f"{self.year}-{self.month:02d}-{self.day:02d}"

dates = [
    SimpleDate(2024, 5, 15),
    SimpleDate(2023, 12, 1),
    SimpleDate(2024, 1, 10),
    SimpleDate(2024, 5, 1)
]

print(f"Dates: {dates}")
print(f"Sorted: {sorted(dates)}")

# Range checking
print("\nRange checking:")

@total_ordering
class Score:
    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
    
    def __repr__(self):
        return f"Score({self.value})"

score = Score(75)
min_score = Score(60)
max_score = Score(100)

print(f"Score: {score}")
print(f"In range [60, 100]? {min_score <= score <= max_score}")

# Priority
print("\nPriority:")

@total_ordering
class Task:
    PRIORITIES = {'low': 3, 'medium': 2, 'high': 1}
    
    def __init__(self, name, priority):
        self.name = name
        self.priority = priority
    
    def __eq__(self, other):
        return self.PRIORITIES[self.priority] == \
               self.PRIORITIES[other.priority]
    
    def __lt__(self, other):
        return self.PRIORITIES[self.priority] < \
               self.PRIORITIES[other.priority]
    
    def __repr__(self):
        return f"Task('{self.name}', '{self.priority}')"

tasks = [
    Task("Write docs", "low"),
    Task("Fix bug", "high"),
    Task("Review code", "medium"),
    Task("Deploy", "high")
]

print("Tasks:")
for t in tasks:
    print(f"  {t}")

print("\nSorted by priority:")
for t in sorted(tasks):
    print(f"  {t}")

higher-order function A function that takes other functions as arguments or returns functions - the foundation of functional programming patterns.
reduce() Applies a function cumulatively to sequence items, reducing them to a single value - like folding a list into one result.
lru_cache Least Recently Used cache decorator that stores recent function results, dramatically speeding up recursive algorithms and repeated calculations.
partial() Creates a new function with some arguments pre-filled, reducing repetition and creating more specific functions from general ones.

Exercise: practical.py

Implement a cached recursive function and create partial functions for common operations