functools

Narzędzia do pracy z funkcjami: zapamiętywanie wyników (lru_cache), częściowe wywołania (partial) i zwijanie kolekcji (reduce).

Na tej stronie

Przykład

#
Python
from functools import lru_cache

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

print(fib(10))
print(fib(80))
print(fib.cache_info())
Wynikzapisany wynik, możesz go sprawdzić
55
23416728348467685
CacheInfo(hits=79, misses=81, maxsize=None, currsize=81)

Bez @lru_cache obliczenie fib(80) wymagałoby kilkudziesięciu biliardów wywołań funkcji, czyli dziesiątek lat pracy nawet szybkiego komputera. Z pamięcią podręczną każda wartość jest liczona tylko raz.

Definicja i zastosowanie

#

Moduł functools zawiera narzędzia do pracy z funkcjami: takie, które przyjmują funkcje jako argumenty, opakowują je albo tworzą z nich nowe. Najczęściej używa się z niego dekoratora @lru_cache, funkcji partial() i reduce() oraz dekoratora @wraps, niezbędnego przy pisaniu własnych dekoratorów.

@lru_cache zapamiętuje wyniki funkcji dla użytych już argumentów. Przy kolejnym wywołaniu z tymi samymi argumentami wynik jest zwracany od razu, bez ponownego liczenia, co daje ogromne przyspieszenie przy kosztownych obliczeniach i rekurencji. Argumenty muszą być hashowalne (liczby, napisy, krotki), a wynik powinien zależeć tylko od nich. Statystyki trafień pokazuje metoda cache_info().

partial() tworzy nową funkcję z częścią argumentów ustawionych na stałe, np. partial(int, base=2) tworzy funkcję, która zamienia na liczby napisy zapisane w systemie dwójkowym. reduce() zwija kolekcję do jednej wartości: wywołuje funkcję dwuargumentową najpierw dla dwóch pierwszych elementów, potem dla wyniku i trzeciego elementu, i tak dalej. Do sum i maksimów prostsze są jednak sum() i max().

Składnia

#
Składnia
from functools import cache, lru_cache, partial, reduce, wraps

@lru_cache(maxsize=128)
def funkcja(argumenty):
    ...

nowa = partial(funkcja, *args, **kwargs)
wynik = reduce(funkcja, iterable, initial)

Najważniejsze funkcje

#
  • @lru_cache

    dekorator

    Zapamiętuje wyniki dla ostatnio użytych argumentów. Parametr maxsize (domyślnie 128) określa, ile wyników pamiętać, a None usuwa limit.
  • @cache

    dekorator

    Prostszy odpowiednik lru_cache(maxsize=None): pamięta wszystkie wyniki.
  • partial()

    funkcja

    Zwraca nową funkcję z częścią argumentów ustawionych na stałe: partial(func, *args, **kwargs).
  • reduce()

    funkcja

    Zwija kolekcję do jednej wartości: reduce(func, iterable, initial). Bez initial pusta kolekcja zgłasza TypeError.
  • @wraps

    dekorator

    Używany we własnych dekoratorach: kopiuje nazwę i docstring opakowanej funkcji do wrappera.
  • @total_ordering

    dekorator klasy

    Uzupełnia brakujące porównania na podstawie __eq__ i jednej z metod __lt__, __le__, __gt__ albo __ge__.
  • @cached_property

    dekorator metody

    Zamienia metodę w atrybut liczony raz, przy pierwszym odczycie.

Więcej przykładów

#
partial(): funkcja z ustawionymi argumentami
Python
from functools import partial

def award(exp, name, reason="zadanie"):
    return f"{name}: +{exp} EXP ({reason})"

small = partial(award, 5)
streak = partial(award, 20, reason="seria 7 dni")

print(small("Ania"))
print(streak("Kuba"))

from_binary = partial(int, base=2)
print(from_binary("1010"))
Wynikzapisany wynik, możesz go sprawdzić
Ania: +5 EXP (zadanie)
Kuba: +20 EXP (seria 7 dni)
10
reduce(): zwijanie kolekcji do jednej wartości
Python
from functools import reduce

scores = [72, 95, 64, 88]
print(reduce(lambda total, s: total + s, scores))
print(reduce(lambda best, s: s if s > best else best, scores))

updates = [{"level": 2}, {"points": 250}, {"level": 3}]
profile = reduce(lambda acc, change: {**acc, **change}, updates, {"name": "Ania"})
print(profile)
print(reduce(lambda a, b: a * b, [], 1))
Wynikzapisany wynik, możesz go sprawdzić
319
95
{'name': 'Ania', 'level': 3, 'points': 250}
1
total_ordering: komplet porównań
Python
from functools import total_ordering

@total_ordering
class Level:
    def __init__(self, number):
        self.number = number

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

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

print(Level(3) >= Level(2), Level(1) <= Level(1), Level(5) > Level(7))
Wynikzapisany wynik, możesz go sprawdzić
True True False

Dobre praktyki

#
  • Nie używaj @lru_cache dla funkcji z efektami ubocznymi, np. zapisem do pliku, losowaniem albo odczytem czasu. Dla zapamiętanych argumentów funkcja w ogóle się nie wykona.
  • Argumenty funkcji z pamięcią podręczną muszą być hashowalne. Listy zamień przed wywołaniem na krotki.
  • Zamiast reduce() do sumy, iloczynu czy maksimum użyj sum(), math.prod() i max(). Są czytelniejsze i szybsze.

Powiązane hasła

#

Widzisz błąd albo brakuje przykładu? Napisz do nas.