Utilities
Functools Module
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