# `functools` — Higher-order functions and operations on callable objects

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

The `functools` module is for higher-order functions: functions that act on or return other functions. In general, any callable object can be treated as a function for the purposes of this module.

The `functools` module defines the following functions:

@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 than `lru_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)    # no new calls, just returns the cached result
      120
      >>> factorial(12)   # two new recursive calls, factorial(10) is 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.

  Call-once behavior is not guaranteed because locks are not held during the function call. Potentially another call with the same arguments could occur while the first call is still running.

  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(word):
          return sum(word.count(vowel) for vowel in 'AEIOUaeiou')

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

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

  Note, type specificity applies only to the function’s immediate arguments rather than their contents. The scalar arguments, `Decimal(42)` and `Fraction(42)` are treated as distinct calls with distinct results. In contrast, the tuple arguments `('answer', Decimal(42))` and `('answer', Fraction(42))` are treated as equivalent.

  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.

  To help measure the effectiveness of the cache and tune the maxsize parameter, the wrapped function is instrumented with a `cache_info()` function that returns a named tuple showing hits, misses, maxsize and currsize.

  The decorator also provides a `cache_clear()` function for clearing or invalidating the cache.

  Оригінальна базова функція доступна через атрибут `__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

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

  The class must define one of `__lt__()`, `__le__()`, `__gt__()`, or `__ge__()`. In addition, the class should supply an `__eq__()` method.

  Наприклад:

      @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.Placeholder

  A singleton object used as a sentinel to reserve a place for positional arguments when calling `partial()` and `partialmethod()`.

  Added in version 3.14.

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

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

      def partial(func, /, *args, **keywords):
          def newfunc(*more_args, **more_keywords):
              return func(*args, *more_args, **(keywords | more_keywords))
          newfunc.func = func
          newfunc.args = args
          newfunc.keywords = keywords
          return newfunc

  The `partial()` function is used for partial function application which «freezes» some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature. For example, `partial()` can be used to create a callable that behaves like the `int()` function where the base argument defaults to `2`:

      >>> basetwo = partial(int, base=2)
      >>> basetwo.__doc__ = 'Convert base 2 string to an int.'
      >>> basetwo('10010')
      18

  If `Placeholder` sentinels are present in args, they will be filled first when `partial()` is called. This makes it possible to pre-fill any positional argument with a call to `partial()`; without `Placeholder`, only the chosen number of leading positional arguments can be pre-filled.

  If any `Placeholder` sentinels are present, all must be filled at call time:

      >>> say_to_world = partial(print, Placeholder, Placeholder, "world!")
      >>> say_to_world('Hello', 'dear')
      Hello dear world!

  Calling `say_to_world('Hello')` raises a `TypeError`, because only one positional argument is provided, but there are two placeholders that must be filled in.

  If `partial()` is applied to an existing `partial()` object, `Placeholder` sentinels of the input object are filled in with new positional arguments. A placeholder can be retained by inserting a new `Placeholder` sentinel to the place held by a previous `Placeholder`:

      >>> from functools import partial, Placeholder as _
      >>> remove = partial(str.replace, _, _, '')
      >>> message = 'Hello, dear dear world!'
      >>> remove(message, ' dear')
      'Hello, world!'
      >>> remove_dear = partial(remove, _, ' dear')
      >>> remove_dear(message)
      'Hello, world!'
      >>> remove_first_dear = partial(remove_dear, _, 1)
      >>> remove_first_dear(message)
      'Hello, dear world!'

  `Placeholder` cannot be passed to `partial()` as a keyword argument.

  Змінено в версії 3.14: Added support for `Placeholder` in positional arguments.

class functools.partialmethod(func, /, *args, **keywords)

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

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

  When func is a descriptor (such as a normal Python function, `classmethod()`, `staticmethod()`, `abstractmethod()` or another instance of `partialmethod`), calls to `__get__` are delegated to the underlying descriptor, and an appropriate partial object returned as the result.

  Коли 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, /[, initial])

  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 initial 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 initial is not given and iterable contains only one item, the first item is returned.

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

      initial_missing = object()

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

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

  Змінено в версії 3.14: initial is now supported as a keyword argument.

@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)

  To add overloaded implementations to the function, use the `register()` attribute of the generic function, which can be used as a decorator. For functions annotated with types, the decorator will infer the type of the first argument automatically:

      >>> @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)

  `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)
      ...

  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.

  To enable registering lambdas and pre-existing functions, the `register()` attribute can also be used in a functional form:

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

  The `register()` attribute returns the undecorated function. This enables decorator stacking, `pickling`, and the creation of unit tests for each variant independently:

      >>> @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: The `register()` attribute now supports using type annotations.

  Змінено в версії 3.11: The `register()` attribute now supports `typing.Union` as a type annotation.

class functools.singledispatchmethod(func)

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

  To define a generic method, decorate it with the `@singledispatchmethod` decorator. When defining a method using `@singledispatchmethod`, note that the dispatch happens on the type of the first non-self or non-cls argument:

      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` supports nesting with other decorators such as `@classmethod`. Note that to allow for `dispatcher.register`, `singledispatchmethod` must be the outer most decorator. Here is the `Negator` class with the `neg` methods bound to the class, rather than an instance of the class:

      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

  The same pattern can be used for other similar decorators: `@staticmethod`, `@~abc.abstractmethod`, and others.

  Added in version 3.8.

  Змінено в версії 3.15: Added support of non-descriptor callables.

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) and `WRAPPER_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 an `AttributeError`.

  Змінено в версії 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'

  Without the use of this decorator factory, the name of the example function would have been `'wrapper'`, and the docstring of the original `example()` would have been lost.

## `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.