functools — Higher-order functions and operations on callable objects

Вихідний код: Lib/functools.py


Модуль functools призначений для функцій вищого порядку: функцій, які діють або повертають інші функції. Загалом, будь-який викликуваний об’єкт можна розглядати як функцію для цілей цього модуля.

Модуль functools визначає такі функції:

@functools.cache(user_function)

Простий легкий необмежений кеш функцій. Іноді називається «memoize».

Повертає те саме, що lru_cache(maxsize=None), створюючи тонку обгортку навколо пошуку в словнику для аргументів функції. Оскільки йому ніколи не потрібно вилучати старі значення, він менший і швидший, ніж lru_cache() з обмеженням розміру.

Наприклад:

@cache
def factorial(n):
    return n * factorial(n-1) if n else 1

>>> factorial(10)      # no previously cached result, makes 11 recursive calls
3628800
>>> factorial(5)       # just looks up cached value result
120
>>> factorial(12)      # makes two new recursive calls, the other 10 are cached
479001600

The cache is threadsafe so that the wrapped function can be used in multiple threads. This means that the underlying data structure will remain coherent during concurrent updates.

It is possible for the wrapped function to be called more than once if another thread makes an additional call before the initial call has been completed and cached.

Added in version 3.9.

@functools.cached_property(func)

Перетворення методу класу на властивість, значення якого обчислюється один раз, а потім кешується як звичайний атрибут протягом життя екземпляра. Подібно до property(), з додаванням кешування. Корисно для дорогих обчислених властивостей екземплярів, які в іншому випадку є фактично незмінними.

Приклад:

class DataSet:

    def __init__(self, sequence_of_numbers):
        self._data = tuple(sequence_of_numbers)

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

Механіка cached_property() дещо відрізняється від property(). Звичайна властивість блокує запис атрибута, якщо не визначено установщик. На відміну від цього, cached_property дозволяє запис.

Декоратор cached_property працює лише під час пошуку й лише тоді, коли атрибут із такою ж назвою не існує. Коли він виконується, cached_property записує в атрибут з таким же ім’ям. Наступні атрибути читання та запису мають пріоритет над методом cached_property, і він працює як звичайний атрибут.

Кешоване значення можна очистити, видаливши атрибут. Це дозволяє знову запустити метод cached_property.

The cached_property does not prevent a possible race condition in multi-threaded usage. The getter function could run more than once on the same instance, with the latest run setting the cached value. If the cached property is idempotent or otherwise not harmful to run more than once on an instance, this is fine. If synchronization is needed, implement the necessary locking inside the decorated getter function or around the cached property access.

Зауважте, що цей декоратор заважає роботі словників спільного ключа PEP 412. Це означає, що словники примірників можуть займати більше місця, ніж зазвичай.

Крім того, цей декоратор вимагає, щоб атрибут __dict__ для кожного екземпляра був змінним відображенням. Це означає, що він не працюватиме з деякими типами, такими як метакласи (оскільки атрибути __dict__ в екземплярах типу є проксі-серверами лише для читання для простору імен класу), і ті, які вказують __slots__ без включення __dict__ як один із визначених слотів (оскільки такі класи взагалі не надають атрибут __dict__).

If a mutable mapping is not available or if space-efficient key sharing is desired, an effect similar to cached_property() can also be achieved by stacking property() on top of lru_cache(). See Як кешувати виклики методів? for more details on how this differs from cached_property().

Added in version 3.8.

Змінено в версії 3.12: Prior to Python 3.12, cached_property included an undocumented lock to ensure that in multi-threaded usage the getter function was guaranteed to run only once per instance. However, the lock was per-property, not per-instance, which could result in unacceptably high lock contention. In Python 3.12+ this locking is removed.

functools.cmp_to_key(func)

Перетворіть функцію порівняння старого стилю на key function. Використовується з інструментами, які приймають ключові функції (такі як sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby()). Ця функція в основному використовується як інструмент переходу для програм, які перетворюються з Python 2, який підтримує використання функцій порівняння.

A comparison function is any callable that accepts two arguments, compares them, and returns a negative number for less-than, zero for equality, or a positive number for greater-than. A key function is a callable that accepts one argument and returns another value to be used as the sort key.

Приклад:

sorted(iterable, key=cmp_to_key(locale.strcoll))  # locale-aware sort order

Приклади сортування та короткий посібник із сортування див. Sorting Techniques.

Added in version 3.2.

@functools.lru_cache(user_function)
@functools.lru_cache(maxsize=128, typed=False)

Декоратор для обгортання функції викликом мемоізації, який зберігає до maxsize останніх викликів. Це може заощадити час, коли дорога функція або функція, пов’язана з вводом/виводом, періодично викликається з однаковими аргументами.

The cache is threadsafe so that the wrapped function can be used in multiple threads. This means that the underlying data structure will remain coherent during concurrent updates.

It is possible for the wrapped function to be called more than once if another thread makes an additional call before the initial call has been completed and cached.

Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable.

Distinct argument patterns may be considered to be distinct calls with separate cache entries. For example, f(a=1, b=2) and f(b=2, a=1) differ in their keyword argument order and may have two separate cache entries.

Якщо вказано user_function, вона має бути викликаною. Це дозволяє застосувати декоратор lru_cache безпосередньо до функції користувача, залишаючи значення maxsize за замовчуванням 128:

@lru_cache
def count_vowels(sentence):
    return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')

Якщо для параметра maxsize встановлено значення None, функція LRU вимкнена, і кеш може збільшуватися без обмежень.

Якщо typed має значення true, аргументи функції різних типів кешуватимуться окремо. Якщо typed має значення false, реалізація зазвичай розглядатиме їх як еквівалентні виклики та кешуватиме лише один результат. (Деякі типи, такі як str і int, можуть кешуватися окремо, навіть якщо typed має значення false.)

Зауважте, що специфічність типу застосовується лише до безпосередніх аргументів функції, а не до їх вмісту. Скалярні аргументи Decimal(42) і Fraction(42) розглядаються як окремі виклики з різними результатами. Навпаки, аргументи кортежу ('answer', Decimal(42)) і ('answer', Fraction(42)) розглядаються як еквівалентні.

The wrapped function is instrumented with a cache_parameters() function that returns a new dict showing the values for maxsize and typed. This is for information purposes only. Mutating the values has no effect.

Щоб допомогти виміряти ефективність кешу та налаштувати параметр maxsize, обгорнута функція обладнана функцією cache_info(), яка повертає named tuple, що показує влучення, промахи, maxsize і currsize.

Декоратор також надає функцію cache_clear() для очищення або анулювання кешу.

Оригінальна базова функція доступна через атрибут __wrapped__. Це корисно для самоаналізу, для обходу кешу або для перезагортання функції в інший кеш.

Кеш зберігає посилання на аргументи та значення, що повертаються, доки вони не вичерпаються з кешу або поки кеш не буде очищено.

If a method is cached, the self instance argument is included in the cache. See Як кешувати виклики методів?

Кеш LRU (найменше використовуваний) працює найкраще, коли останні дзвінки є найкращим прогнозом майбутніх дзвінків (наприклад, найпопулярніші статті на сервері новин, як правило, змінюються щодня). Обмеження розміру кешу гарантує, що кеш не буде безмежно зростати довготривалими процесами, такими як веб-сервери.

In general, the LRU cache should only be used when you want to reuse previously computed values. Accordingly, it doesn’t make sense to cache functions with side-effects, functions that need to create distinct mutable objects on each call (such as generators and async functions), or impure functions such as time() or random().

Приклад кешу LRU для статичного веб-контенту:

@lru_cache(maxsize=32)
def get_pep(num):
    'Retrieve text of a Python Enhancement Proposal'
    resource = f'https://peps.python.org/pep-{num:04d}'
    try:
        with urllib.request.urlopen(resource) as s:
            return s.read()
    except urllib.error.HTTPError:
        return 'Not Found'

>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
...     pep = get_pep(n)
...     print(n, len(pep))

>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)

Приклад ефективного обчислення чисел Фібоначчі з використанням кешу для реалізації техніки динамічного програмування:

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

>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

Added in version 3.2.

Змінено в версії 3.3: Додано параметр введений.

Змінено в версії 3.8: Додано опцію user_function.

Змінено в версії 3.9: Added the function cache_parameters()

@functools.total_ordering

Враховуючи клас, що визначає один або більше методів розширеного впорядкування порівняння, цей декоратор класу забезпечує решту. Це спрощує завдання, пов’язані з визначенням усіх можливих операцій розширеного порівняння:

Клас має визначати одне з __lt__(), __le__(), __gt__() або __ge__(). Крім того, клас повинен надати метод __eq__().

Наприклад:

@total_ordering
class Student:
    def _is_valid_operand(self, other):
        return (hasattr(other, "lastname") and
                hasattr(other, "firstname"))
    def __eq__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

Примітка

Незважаючи на те, що цей декоратор дозволяє легко створювати повністю впорядковані типи, що добре ведуть себе, це ціна відбувається за рахунок повільнішого виконання та більш складних трасувань стека для похідних методів порівняння. Якщо порівняльний аналіз продуктивності вказує на те, що це вузьке місце для даної програми, впровадження всіх шести розширених методів порівняння натомість, ймовірно, забезпечить легке підвищення швидкості.

Примітка

Цей декоратор не намагається перевизначити методи, які були оголошені в класі або його суперкласах. Це означає, що якщо суперклас визначає оператор порівняння, total_ordering не реалізує його знову, навіть якщо вихідний метод є абстрактним.

Added in version 3.2.

Змінено в версії 3.4: Returning NotImplemented from the underlying comparison function for unrecognised types is now supported.

functools.partial(func, /, *args, **keywords)

Повертає новий частковий об’єкт, який під час виклику поводитиметься як func, що викликається з позиційними аргументами args і ключовими аргументами keywords. Якщо до виклику надається більше аргументів, вони додаються до args. Якщо надаються додаткові ключові аргументи, вони розширюють і замінюють ключові слова. Приблизно еквівалентно:

def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

partial() використовується для застосування часткової функції, яка «заморожує» деяку частину аргументів функції та/або ключових слів, у результаті чого створюється новий об’єкт зі спрощеною сигнатурою. Наприклад, partial() можна використовувати для створення виклику, який поводиться як функція int(), де аргумент base за замовчуванням дорівнює двом:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
class functools.partialmethod(func, /, *args, **keywords)

Повертає новий дескриптор partialmethod, який поводиться як partial, за винятком того, що він призначений для використання як визначення методу, а не для безпосереднього виклику.

func має бути descriptor або викликаним (об’єкти, які, як і звичайні функції, обробляються як дескриптори).

Коли func є дескриптором (наприклад, звичайною функцією Python, classmethod(), staticmethod(), abstractmethod() або іншим екземпляром partialmethod), викликається __get__ делегуються базовому дескриптору, а в результаті повертається відповідний частковий об’єкт.

Коли func є недескрипторним викликом, відповідний пов’язаний метод створюється динамічно. Це поводиться як звичайна функція Python, коли використовується як метод: аргумент self буде вставлено як перший позиційний аргумент, навіть перед args і keywords, наданими конструктору partialmethod.

Приклад:

>>> class Cell:
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True

Added in version 3.4.

functools.reduce(function, iterable[, initializer])

Застосуйте функцію двох аргументів сукупно до елементів iterable, зліва направо, щоб зменшити iterable до одного значення. Наприклад, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) обчислює ((((1+2)+3)+4)+5). Лівий аргумент, x, — це накопичене значення, а правий аргумент, y, — це значення оновлення з iterable. Якщо необов’язковий ініціалізатор присутній, він розміщується перед елементами об’єкта ітерації в обчисленні та служить за замовчуванням, коли об’єкт ітерації порожній. Якщо initializer не вказано, а iterable містить лише один елемент, повертається перший елемент.

Приблизно еквівалентно:

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

Перегляньте itertools.accumulate() для ітератора, який видає всі проміжні значення.

@functools.singledispatch

Перетворення функції на single-dispatch generic function.

Щоб визначити загальну функцію, прикрасьте її за допомогою декоратора @singledispatch. Визначаючи функцію за допомогою @singledispatch, зауважте, що відправлення відбувається за типом першого аргументу:

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)

Щоб додати перевантажені реалізації до функції, використовуйте атрибут register() загальної функції, який можна використовувати як декоратор. Для функцій, анотованих типами, декоратор автоматично визначить тип першого аргументу:

>>> @fun.register
... def _(arg: int, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: list, verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

types.UnionType and typing.Union can also be used:

>>> @fun.register
... def _(arg: int | float, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> from typing import Union
>>> @fun.register
... def _(arg: Union[list, set], verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)
...

Для коду, який не використовує анотації типу, відповідний аргумент типу можна явно передати самому декоратору:

>>> @fun.register(complex)
... def _(arg, verbose=False):
...     if verbose:
...         print("Better than complicated.", end=" ")
...     print(arg.real, arg.imag)
...

Щоб увімкнути реєстрацію lambdas і вже існуючих функцій, атрибут register() також можна використовувати у функціональній формі:

>>> def nothing(arg, verbose=False):
...     print("Nothing.")
...
>>> fun.register(type(None), nothing)

Атрибут register() повертає недекоровану функцію. Це дозволяє стекувати декоратор, піклінг і створювати модульні тести для кожного варіанту незалежно:

>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print("Half of your number:", end=" ")
...     print(arg / 2)
...
>>> fun_num is fun
False

Під час виклику загальна функція надсилає тип першого аргументу:

>>> fun("Hello, world.")
Hello, world.
>>> fun("test.", verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615

Якщо немає зареєстрованої реалізації для певного типу, його порядок вирішення методів використовується для пошуку більш загальної реалізації. Оригінальна функція, прикрашена @singledispatch, зареєстрована для базового типу object, що означає, що вона використовується, якщо не знайдено кращої реалізації.

Якщо реалізацію зареєстровано в abstract base class, віртуальні підкласи базового класу будуть відправлені до цієї реалізації:

>>> from collections.abc import Mapping
>>> @fun.register
... def _(arg: Mapping, verbose=False):
...     if verbose:
...         print("Keys & Values")
...     for key, value in arg.items():
...         print(key, "=>", value)
...
>>> fun({"a": "b"})
a => b

Щоб перевірити, яку реалізацію вибере загальна функція для заданого типу, використовуйте атрибут dispatch():

>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)    # note: default implementation
<function fun at 0x103fe0000>

Щоб отримати доступ до всіх зареєстрованих реалізацій, використовуйте атрибут registry лише для читання:

>>> fun.registry.keys()
dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
          <class 'decimal.Decimal'>, <class 'list'>,
          <class 'float'>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>

Added in version 3.4.

Змінено в версії 3.7: Атрибут register() тепер підтримує використання анотацій типу.

Змінено в версії 3.11: The register() attribute now supports types.UnionType and typing.Union as type annotations.

class functools.singledispatchmethod(func)

Перетворення методу на single-dispatch generic function.

Щоб визначити загальний метод, прикрасьте його декоратором @singledispatchmethod. Визначаючи функцію за допомогою @singledispatchmethod, зауважте, що відправлення відбувається за типом першого не*self* або не*cls* аргументу:

class Negator:
    @singledispatchmethod
    def neg(self, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(self, arg: int):
        return -arg

    @neg.register
    def _(self, arg: bool):
        return not arg

@singledispatchmethod підтримує вкладення з іншими декораторами, такими як @classmethod. Зауважте, що для того, щоб дозволити dispatcher.register, singledispatchmethod має бути зовнішнім декоратором. Ось клас Negator з методами neg, прив’язаними до класу, а не екземпляр класу:

class Negator:
    @singledispatchmethod
    @classmethod
    def neg(cls, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    @classmethod
    def _(cls, arg: int):
        return -arg

    @neg.register
    @classmethod
    def _(cls, arg: bool):
        return not arg

Той самий шаблон можна використовувати для інших подібних декораторів: @staticmethod, @abstractmethod та інших.

Added in version 3.8.

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

Оновіть функцію wrapper, щоб виглядати як функція wrapper. Необов’язкові аргументи — це кортежі, які вказують, які атрибути вихідної функції призначаються безпосередньо відповідним атрибутам у функції-огортці та які атрибути функції-огортки оновлюються відповідними атрибутами з вихідної функції. Значеннями за замовчуванням для цих аргументів є константи рівня модуля WRAPPER_ASSIGNMENTS (які призначають функції-оболонки __module__, __name__, __qualname__, __annotations__ і __doc__, рядок документації) і WRAPPER_UPDATES (який оновлює __dict__ функції-огортки, тобто словник екземпляра).

Щоб дозволити доступ до оригінальної функції для самоаналізу та інших цілей (наприклад, обхід декоратора кешування, такого як lru_cache()), ця функція автоматично додає атрибут __wrapped__ до оболонки, яка посилається на функцію, яку обгортають.

Основним призначенням цієї функції є функції decorator, які обертають декоровану функцію та повертають оболонку. Якщо функція-оболонка не оновлена, метадані повернутої функції відображатимуть визначення оболонки, а не початкове визначення функції, що зазвичай не дуже корисно.

update_wrapper() можна використовувати з іншими викликами, ніж функції. Будь-які атрибути з іменами assigned або updated, яких немає в об’єкті, що обгортається, ігноруються (тобто ця функція не намагатиметься встановити їх у функції обгортки). AttributeError все ще виникає, якщо в самій функції-обгортки відсутні будь-які атрибути, названі в updated.

Змінено в версії 3.2: The __wrapped__ attribute is now automatically added. The __annotations__ attribute is now copied by default. Missing attributes no longer trigger an AttributeError.

Змінено в версії 3.4: Атрибут __wrapped__ тепер завжди посилається на обгорнуту функцію, навіть якщо ця функція визначила атрибут __wrapped__. (див. bpo-17482)

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

Це зручна функція для виклику update_wrapper() як декоратора функції під час визначення функції-огортки. Це еквівалентно partial(update_wrapper, wrapped=wrapped, assigned=призначено, updated=updated). Наприклад:

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print('Calling decorated function')
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

Без використання цієї фабрики декораторів назва функції прикладу була б 'wrapper, а рядок документації оригінального example() було б втрачено.

partial Об’єкти

partial об’єкти – це викликані об’єкти, створені partial(). Вони мають три атрибути лише для читання:

partial.func

Викликаний об’єкт або функція. Виклики об’єкта partial будуть перенаправлені до func з новими аргументами та ключовими словами.

partial.args

Крайні ліві позиційні аргументи, які будуть додані до позиційних аргументів, наданих до виклику об’єкта partial.

partial.keywords

Ключові аргументи, які будуть надані під час виклику об’єкта partial.

partial objects are like function objects in that they are callable, weak referenceable, and can have attributes. There are some important differences. For instance, the __name__ and __doc__ attributes are not created automatically. Also, partial objects defined in classes behave like static methods and do not transform into bound methods during instance attribute look-up.