Python · Синтаксис · Просунутий рівень
functools: partial, lru_cache та інше
Опануйте модуль functools: `partial`, `wraps`, `lru_cache`, `cached_property`, `total_ordering` та `singledispatch`.
Короткий вступ до теми та пояснення перед вправами (вправи нижче):
partial, reduce, патерн compose, cmp_to_key та @wraps
#total_ordering, singledispatch та повний довідник functools
#Вправи:
functools.partial — попереднє заповнення аргументів
#import functools
import operator
def power(base: int, exp: int) -> int:
return base ** exp
# Створіть square, cube та add5 через partial
print(square(4)) # 16
print(cube(3)) # 27
print(add5(10)) # 15
Рішення
import functools
import operator
def power(base: int, exp: int) -> int:
return base ** exp
square = functools.partial(power, exp=2)
cube = functools.partial(power, exp=3)
add5 = functools.partial(operator.add, 5)
print(square(4)) # 16
print(cube(3)) # 27
print(add5(10)) # 15
lru_cache для мемоізації
#import functools
# Додайте декоратор lru_cache тут
def fib(n: int) -> int:
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(35)) # 9227465
print(fib.cache_info()) # CacheInfo(hits=..., misses=36, ...)
Рішення
import functools
@functools.lru_cache(maxsize=None)
def fib(n: int) -> int:
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(35)) # 9227465
print(fib.cache_info())
cached_property для затратних атрибутів
#import math
import functools
class Circle:
def __init__(self, radius: float) -> None:
self.radius = radius
# Додайте cached_property для area тут
c = Circle(5)
print(c.area) # computing... потім 78.539...
print(c.area) # 78.539... (без 'computing...')
Рішення
import math
import functools
class Circle:
def __init__(self, radius: float) -> None:
self.radius = radius
@functools.cached_property
def area(self) -> float:
print('computing...')
return math.pi * self.radius ** 2
c = Circle(5)
print(c.area)
print(c.area)
total_ordering — методи порівняння
#import functools
@functools.total_ordering
class Version:
def __init__(self, major: int, minor: int, patch: int) -> None:
self.major = major
self.minor = minor
self.patch = patch
def _tuple(self):
return (self.major, self.minor, self.patch)
def __eq__(self, other) -> bool:
pass
def __lt__(self, other) -> bool:
pass
v1 = Version(1, 2, 3)
v2 = Version(1, 3, 0)
print(v1 < v2) # True
print(v1 > v2) # False
print(v1 <= v1) # True
print(v1 == Version(1, 2, 3)) # True
Рішення
import functools
@functools.total_ordering
class Version:
def __init__(self, major: int, minor: int, patch: int) -> None:
self.major = major
self.minor = minor
self.patch = patch
def _tuple(self):
return (self.major, self.minor, self.patch)
def __eq__(self, other) -> bool:
return self._tuple() == other._tuple()
def __lt__(self, other) -> bool:
return self._tuple() < other._tuple()
v1 = Version(1, 2, 3)
v2 = Version(1, 3, 0)
print(v1 < v2) # True
print(v1 > v2) # False
print(v1 <= v1) # True
print(v1 == Version(1, 2, 3)) # True
singledispatch — диспетчеризація за типом
#import functools
@functools.singledispatch
def describe(value) -> str:
pass # типова реалізація
# Зареєструйте реалізації для int, str, list тут
print(describe(42)) # Integer: 42
print(describe('hello')) # String of length 5
print(describe([1, 2, 3])) # List with 3 items
print(describe(3.14)) # Unknown type: float
Рішення
import functools
@functools.singledispatch
def describe(value) -> str:
return f'Unknown type: {type(value).__name__}'
@describe.register(int)
def _(value: int) -> str:
return f'Integer: {value}'
@describe.register(str)
def _(value: str) -> str:
return f'String of length {len(value)}'
@describe.register(list)
def _(value: list) -> str:
return f'List with {len(value)} items'
print(describe(42)) # Integer: 42
print(describe('hello')) # String of length 5
print(describe([1, 2, 3])) # List with 3 items
print(describe(3.14)) # Unknown type: float
functools.reduce
#import functools nums = [2, 3, 4, 5] words = ['Привіт', 'світ', 'від', 'reduce'] product = functools.reduce(lambda a, b: a * b, nums) print(product) # 120 my_max = functools.reduce(lambda a, b: a if a > b else b, nums) print(my_max) # 5 flatten_str = functools.reduce(lambda a, b: a + ' ' + b, words) print(flatten_str) # Привіт світ від reduce
Рішення
import functools
nums = [2, 3, 4, 5]
words = ['Привіт', 'світ', 'від', 'reduce']
product = functools.reduce(lambda a, b: a * b, nums)
print(product) # 120
my_max = functools.reduce(lambda a, b: a if a > b else b, nums)
print(my_max) # 5
flatten_str = functools.reduce(lambda a, b: a + ' ' + b, words)
print(flatten_str)
functools.wraps у декораторі
#import functools
def log_calls(func):
# Застосуйте @functools.wraps тут
def wrapper(*args, **kwargs):
print(f'Calling {func.__name__}')
return func(*args, **kwargs)
return wrapper
@log_calls
def greet(name: str) -> str:
'''Привітатися.'''
return f'Привіт, {name}!'
print(greet('Аліса')) # Calling greet / Привіт, Аліса!
print(greet.__name__) # greet (не 'wrapper')
print(greet.__doc__) # Привітатися.
Рішення
import functools
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f'Calling {func.__name__}')
return func(*args, **kwargs)
return wrapper
@log_calls
def greet(name: str) -> str:
'''Привітатися.'''
return f'Привіт, {name}!'
print(greet('Аліса'))
print(greet.__name__) # greet
print(greet.__doc__) # Привітатися.
lru_cache з обмеженим розміром
#import functools
# Додайте @lru_cache(maxsize=3) тут
def slow_square(n: int) -> int:
print(f'Computing {n}^2')
return n * n
print(slow_square(1)) # Computing 1^2 -> 1
print(slow_square(2)) # Computing 2^2 -> 4
print(slow_square(3)) # Computing 3^2 -> 9
print(slow_square(1)) # кеш -> 1
print(slow_square(4)) # Computing 4^2, витісняє 2 -> 16
print(slow_square(2)) # Computing 2^2 знову -> 4
Рішення
import functools
@functools.lru_cache(maxsize=3)
def slow_square(n: int) -> int:
print(f'Computing {n}^2')
return n * n
print(slow_square(1))
print(slow_square(2))
print(slow_square(3))
print(slow_square(1)) # з кешу
print(slow_square(4)) # витісняє 2
print(slow_square(2)) # обчислюється знову
partial як ключ сортування
#import functools
def sort_by_field(items, field):
return sorted(items, key=lambda x: x[field])
sort_by_age = functools.partial(sort_by_field, field='age')
sort_by_name = functools.partial(sort_by_field, field='name')
people = [
{'name': 'Чарлі', 'age': 30},
{'name': 'Аліса', 'age': 25},
{'name': 'Боб', 'age': 35},
]
for p in sort_by_age(people):
print(p['name'], p['age'])
for p in sort_by_name(people):
print(p['name'])
Рішення
import functools
def sort_by_field(items, field):
return sorted(items, key=lambda x: x[field])
sort_by_age = functools.partial(sort_by_field, field='age')
sort_by_name = functools.partial(sort_by_field, field='name')
people = [
{'name': 'Чарлі', 'age': 30},
{'name': 'Аліса', 'age': 25},
{'name': 'Боб', 'age': 35},
]
for p in sort_by_age(people):
print(p['name'], p['age'])
for p in sort_by_name(people):
print(p['name'])
Комбінування інструментів functools
#import functools
@functools.singledispatch
def process(value):
return str(value)
@process.register(int)
@functools.lru_cache(maxsize=32)
def _(value: int):
print(f'processing int {value}')
return value * 2
@process.register(str)
@functools.lru_cache(maxsize=32)
def _(value: str):
print(f'processing str {value}')
return value.upper()
print(process(5))
print(process(5)) # з кешу
print(process('hello'))
print(process(3.14))
Рішення
import functools
@functools.singledispatch
def process(value):
return str(value)
@process.register(int)
@functools.lru_cache(maxsize=32)
def _(value: int):
print(f'processing int {value}')
return value * 2
@process.register(str)
@functools.lru_cache(maxsize=32)
def _(value: str):
print(f'processing str {value}')
return value.upper()
print(process(5))
print(process(5)) # з кешу
print(process('hello'))
print(process(3.14))