functools
— Higher-order functions and operations on callable objects¶
Вихідний код: Lib/functools.py
Модуль functools
призначений для функцій вищого порядку: функцій, які діють або повертають інші функції. Загалом, будь-який викликуваний об’єкт можна розглядати як функцію для цілей цього модуля.
Модуль functools
визначає такі функції:
- @functools.cache(user_function)¶
Простий легкий необмежений кеш функцій. Іноді називається «memoize».
Returns the same as
lru_cache(maxsize=None)
, creating a thin wrapper around a dictionary lookup for the function arguments. Because it never needs to evict old values, this is smaller and faster thanlru_cache()
with a size limit.Наприклад:
@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 stackingproperty()
on top oflru_cache()
. See Як кешувати виклики методів? for more details on how this differs fromcached_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)
andf(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 newdict
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 Як кешувати виклики методів?An LRU (least recently used) cache works best when the most recent calls are the best predictors of upcoming calls (for example, the most popular articles on a news server tend to change each day). The cache’s size limit assures that the cache does not grow without bound on long-running processes such as web servers.
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])¶
Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. For example,
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
calculates((((1+2)+3)+4)+5)
. The left argument, x, is the accumulated value and the right argument, y, is the update value from the iterable. If the optional initializer is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. If initializer is not given and iterable contains only one item, the first item is returned.Приблизно еквівалентно:
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
andtyping.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) ...
For code that dispatches on a collections type (e.g.,
list
), but wants to typehint the items of the collection (e.g.,list[int]
), the dispatch type should be passed explicitly to the decorator itself with the typehint going into the function definition:>>> @fun.register(list) ... def _(arg: list[int], verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem)
Примітка
At runtime the function will dispatch on an instance of a list regardless of the type contained within the list i.e.
[1,2,3]
will be dispatched the same as["foo", "bar", "baz"]
. The annotation provided in this example is for static type checkers only and has no runtime impact.Щоб увімкнути реєстрацію 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 supportstypes.UnionType
andtyping.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)¶
Update a wrapper function to look like the wrapped function. The optional arguments are tuples to specify which attributes of the original function are assigned directly to the matching attributes on the wrapper function and which attributes of the wrapper function are updated with the corresponding attributes from the original function. The default values for these arguments are the module level constants
WRAPPER_ASSIGNMENTS
(which assigns to the wrapper function’s__module__
,__name__
,__qualname__
,__annotations__
,__type_params__
, and__doc__
, the documentation string) andWRAPPER_UPDATES
(which updates the wrapper function’s__dict__
, i.e. the instance dictionary).Щоб дозволити доступ до оригінальної функції для самоаналізу та інших цілей (наприклад, обхід декоратора кешування, такого як
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 anAttributeError
.Змінено в версії 3.4: Атрибут
__wrapped__
тепер завжди посилається на обгорнуту функцію, навіть якщо ця функція визначила атрибут__wrapped__
. (див. bpo-17482)Змінено в версії 3.12: The
__type_params__
attribute is now copied by default.
- @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
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 function.__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.