Напишите декоратор cached который будет кэшировать результат вызова функции

от admin

Напишите декоратор cached который будет кэшировать результат вызова функции

Создайте декоратор makeCaching(f) , который берет функцию f и возвращает обёртку, которая кеширует её результаты.

В этой задаче функция f имеет только один аргумент, и он является числом.

  1. При первом вызове обёртки с определённым значением аргумента – она вызывает f и запоминает её результат.
  2. При втором и последующих вызовах с тем же значением аргумента – возвращается сохранённое значение результата.

Должно работать так:

Запоминать результаты вызова функции будем в замыкании, в объекте cache: < ключ:значение >.

Обратите внимание: проверка на наличие уже подсчитанного значения выглядит так: if (x in cache) . Менее универсально можно проверить так: if (cache[x]) , это если мы точно знаем, что cache[x] никогда не будет false , 0 и т.п.

Задание из Яндекс Практикума по Python, Декораторы

"Задание: Напишите функцию-декоратор, которая сохранит (закеширует) значение декорируемой функции. Если декорируемая функция будет вызвана повторно с теми же параметрами — декоратор должен вернуть сохранённый результат, не выполняя функцию.

Подсказка: Создайте словарь и при каждом вызове декоратора сохраняйте в нём аргументы задекорированной функции. При каждом вызове проверяйте, не было ли уже аналогичного вызова. Если был — верните результат прошлого вызова, если не было — верните результат декорируемой функции и одновременно сохраните этот результат в словарь. Ключом для каждой записи словаря может быть аргумент декорируемой функции."

Собственно, код. Написать требуется функцию cache_args, но как ни бьюсь — она работает неверно.

Изучаем продвинутые возможности Python, часть 2: замыкания, декораторы, модуль functools

В первой части серии публикаций о продвинутых возможностях Python мы познакомились с итераторами, генераторами и модулем itertools. В сегодняшней публикации речь пойдёт о замыканиях, декораторах и модуле functools.

Декораторы

Декоратор — паттерн проектирования, при использовании которого класс или функция изменяет или дополняет функциональность другого класса или функции без использования наследования или прямого изменения исходного кода. В Python декораторы представляют собой функции или любые вызываемые объекты, которые принимают на вход набор необязательных аргументов и функцию или класс и возвращают функцию или класс. Их можно использовать для реализации паттерна проектирования декоратора или для решения других задач. Декораторы классов появились в Python 2.6.

Кстати, если вы не знакомы с замыканиями Python, прежде чем читать дальше ознакомьтесь с дополнением о замыканиях в конце этой статьи. Концепцию декораторов сложно понять, если вы не знакомы с замыканиями.

В Python декораторы применяются к функции или классу с помощью символа @ . В качестве первого примера давайте используем простой декоратор, который регистрирует вызовы функций. В этом примере декоратор принимает формат времени в качестве аргумента и печатает лог перед и после выполнения декорированной функции с временем исполнения. Это может быть кстати, когда вы сравниваете эффективность разных реализаций алгоритма или разных алгоритмов.

Посмотрите на пример использования. Здесь функции add1 и add2 оформлены с помощью logged , а также дан пример вывода. Заметьте, что формат времени хранится в замыкании возвращаемых функций с декоратором. Поэтому понимание замыканий необходимо для понимания декораторов Python.

Также обратите внимание, как имя возвращаемой функции заменяется именем оригинальной функции в случае, если оно используется позже. Python не делает этого по умолчанию.

Если вы достаточно внимательны, то заметите, что мы заботимся, чтобы у возвращаемой функции был правильно указан __name__ , но не заботимся о __doc__ или __module__ . Поэтому если у функции add есть строка документации, она потеряется. Как можно этого избежать? Мы могли бы справиться с проблемой так же, как при обработке __name__ . Но выполнять такие операции с каждым декоратором утомительно. Поэтому в модуле functools есть декоратор wraps , который срабатывает именно в таком сценарии. Использование декоратора внутри другого декоратора может показаться странным. Но если вы думаете о декораторах как о функциях, которые принимают функции в качестве параметров и возвращают функции, всё становится на места. Декоратор wraps используется в следующих примерах вместо ручной обработки __name__ и других подобных атрибутов.

Следующий пример немного сложнее. Давайте напишем декоратор, который кэширует результат вызова функции в течение указанного в секундах времени. Код ожидает, что переданные в функцию аргументы — хэшируемые объекты (hashable objects), потому что мы используем кортеж с аргументами args в качестве первого параметра и замороженный набор элементов в kwargs в качестве второго параметра, который выступает ключом кэша. У каждой функции будет уникальный кэш dict , который хранится в замыкании функции.

Вот как это используется. Мы применяем декоратор к наивному и неэффективному калькулятору чисел Фибоначчи. Декоратор кэша эффективно применяет к коду паттерн мемоизации. Обратите внимание, что в замыкании fib находятся кэш dict , ссылка на исходную функцию fib , значение аргумента logged , а также значение аргумента timeout . dump_closure описывается в конце статьи после раздела о замыканиях.

Декораторы класса

В предыдущем разделе мы рассмотрели декораторы функций и некоторые необычные способы их применения. Теперь давайте рассмотрим декораторы классов. В данном случае декоратор принимает на вход класс (объект с типом type в Python) и возвращает модифицированный класс.

Первый пример — простая математика. Дано частично упорядоченное множество P. Мы определяем Pd как дуальность P, исключительно если P(x,y)⟺Pd(y,x). Другими словами, речь идёт об обратном порядке. Как можно реализовать это с помощью Python? Предположим, класс определяет порядок с помощью методов __lt__ , __le__ и так далее. Тогда мы можем написать декоратор класса, который заменяет каждую функцию её дуальностью.

Вот как это можно применить к str , чтобы создать новый класс rstr , в котором используется обратный лексикографический порядок.

Давайте посмотрим на более сложный пример. Предположим, мы хотим применить декоратор logged из предыдущего примера ко всем методам в классе. Это можно сделать вручную: просто добавить декоратор в каждый метод. Также можно автоматизировать процесс с помощью декоратора класса. Прежде чем сделать это, автор улучшил декоратор logged из предыдущего раздела. Теперь в нём используется атрибут wraps из модуля functools вместо ручной работы с __name__ . Также здесь в возвращаемую функцию добавлен атрибут _logged_decorator . Его значение True , он применяется, чтобы избежать двойного декорирования функции. Это удобно, когда мы применяем декоратор к классам, которые должны наследовать методы от других классов. Наконец, добавлен аргумент name_prefix , который делает возможной кастомизацию сообщений лога.

Теперь можно написать декоратор класса.

Вот как он будет использоваться. Обратите внимание, как здесь обрабатываются переопределённые методы и наследование.

Наш первый пример декораторов класса должен был изменять порядок методов класса. Похожий декоратор, но более полезный, может принимать один из __lt__ , __le__ , __gt__ или __ge__ и __eq__ , и реализовывать остальные для полного упорядочивания класса. Это именно то, что делает декоратор functools.total_ordering . Подробности в документации.

Несколько примеров из Flask

Рассмотрим несколько интересных примеров использования декораторов в Flask.

Читать:
Java file outside of source root intellij idea что делать

Представьте, что хотите, чтобы некоторые функции выводили предупреждающие сообщения, если они вызываются при определённых обстоятельствах в режиме отладки. Вместо того, чтобы вручную добавлять код в начало каждой функции, можно использовать декоратор. Это то, что делает декоратор, который можно найти в файле app.py Flask.

Более интересный пример — декоратор Flask route , который определяется в классе Flask . Заметьте, что декоратор может быть методом класса. В этом случае в качестве первого параметра используется self . Полный код смотрите в файле app.py. Обратите внимание, декоратор просто регистрирует декорированную функцию как обработчик URL с помощью вызова функции add_url_rule .

Дополнительное чтение

Много информации о декораторах вы найдёте на официальной вики-странице Python. Также можно посмотреть замечательное видео Дэвида Безли о метапрограммировании в Python 3.

Приложение: замыкания

Замыкание — это комбинация функции и множества ссылок на переменные в области видимости функции. Последнее иногда называют ссылочной средой. Замыкание позволяет выполнять функцию за пределами области видимости. В Python ссылочная среда хранится в виде набора ячеек. Доступ к ним можно получить с помощью атрибутов func_closure или __closure__ . В Python 3 используется только __closure__ .

Важно понимать, что речь идёт просто о ссылках, а не о глубоких копиях объектов. Конечно, неважно, являются ли объекты неизменяемыми, но для изменяемых объектов, например, списков, это важно. Это иллюстрирует пример ниже. Обратите внимание, у функций также есть __globals__ , где хранится глобальное ссылочное окружение, для которого была определена функция. Посмотрите на простой пример:

Ещё один пример, более сложный. Убедитесь, что понимаете, почему код работает именно так.

Наконец, вот пример метода dump_closure , который использовался выше.

Адаптированный перевод статьи A Study of Python’s More Advanced Features Part II: Closures, Decorators and functools by Sahand Saba. Мнение автора оригинальной публикации может не совпадать с мнением администрации «Хекслета».

6 Python декораторов, которые значительно упростят ваш код

Лучшая функция Python, которая применяет эту философию из «дзен Python», — это декоратор.

Декораторы могут помочь вам писать меньше кода для реализации сложной логики и повторно использовать его повсюду.

Более того, существует множество замечательных встроенных декораторов Python, которые значительно облегчают нам жизнь, поскольку мы можем просто использовать одну строчку кода для добавления сложных функций к существующим функциям или классам.

Болтать не буду. Давайте посмотрим на отобранные мной 6 декораторов, которые покажут вам, насколько элегантен Python.

1. @lru_cache: Ускоряем программы кэшированием

Самый простой способ ускорить работу функций Python с помощью трюков кэширования — использовать декоратор @lru_cache.

Этот декоратор можно использовать для кэширования результатов функции, так что последующие вызовы функции с теми же аргументами не будут выполняться снова.

Это особенно полезно для функций, которые требуют больших вычислительных затрат или часто вызываются с одними и теми же аргументами.

Рассмотрим интуитивно понятный пример:

Приведенная выше программа вычисляет N-ое число Фибоначчи с помощью функции Python. Это занимает много времени, поскольку при вычислении fibonacci(30) многие предыдущие числа Фибоначчи будут вычисляться много раз в процессе рекурсии.

Теперь давайте ускорим этот процесс с помощью декоратора @lru_cache:

Как видно из приведенного выше кода, после использования декоратора @lru_cache мы можем получить тот же результат за 0,00002990 секунды, что намного быстрее, чем предыдущие 0,18129450 секунды.

Декоратор @lru_cache имеет параметр maxsize, который определяет максимальное количество результатов для хранения в кэше. Когда кэш заполнен и необходимо сохранить новый результат, наименее использованный результат вытесняется из кэша, чтобы освободить место для нового. Это называется стратегией наименее использованного результата (LRU).

По умолчанию maxsize установлен на 128. Если оно установлено в None, как в нашем примере, функции LRU отключены, и кэш может расти без ограничений.

2. @total_ordering: Добавляем недостающие методы сравнения

Декоратор @total_ordering из модуля functools используется для генерации недостающих методов сравнения для класса Python на основе тех, которые определены.

Как видно из приведенного выше кода, в классе Student нет определений для методов ge, gt и le. Однако благодаря декоратору @total_ordering результаты наших сравнений между различными экземплярами будут правильными.

Преимущества этого декоратора очевидны:

Он может сделать ваш код чище и сэкономить ваше время. Поскольку вам не нужно писать все методы сравнения.

Некоторые старые классы могут не определять достаточно методов сравнения. Безопаснее добавить к нему декоратор @total_ordering для дальнейшего использования.

3. @contextmanager: Кастомный менеджер контекстов

В Python есть механизм менеджмента контекста, который поможет вам правильно управлять ресурсами.

В основном нам нужно просто использовать операторы with:

Как показано в приведенном выше коде, мы можем открыть файл с помощью оператора with, чтобы он был закрыт автоматически после записи. Нам не нужно явно вызывать функцию f.close(), чтобы закрыть файл.

Иногда нам нужно определить индивидуальный менеджер контекста для каких-то особых требований. В этом случае декоратор @contextmanager — наш друг.

Например, следующий код реализует простой настраиваемый контекстный менеджер, который может выводить соответствующую информацию при открытии или закрытии файла.

4. @property: Настраиваем геттеры и сеттеры для классов

Геттеры и сеттеры — важные понятия в объектно-ориентированном программировании (ООП).

Для каждой переменной экземпляра класса метод getter возвращает ее значение, а метод setter устанавливает или обновляет ее значение. Учитывая это, геттеры и сеттеры также известны как аксессоры и мутаторы, соответственно.

Они используются для защиты данных от прямого и неожиданного доступа или изменения.

Различные языки ООП имеют разные механизмы для определения геттеров и сеттеров. В Python мы можем просто использовать декоратор @property.

Как видно из приведенного выше примера, переменная score не может быть установлена как 999, что является бессмысленным числом. Потому что мы ограничили ее допустимый диапазон внутри функции сеттера с помощью декоратора @property.

Без сомнения, добавление этого сеттера может успешно избежать неожиданных ошибок или результатов.

5. @cached_property: Кешируем результат функции как атрибут

В Python 3.8 в модуле functools появился новый мощный декоратор — @cached_property. Он может превратить метод класса в свойство, значение которого вычисляется один раз, а затем кэшируется как обычный атрибут на протяжении всего существования экземпляра.

В приведенном выше коде мы оптимизировали метод area через свойство @cached_property. Таким образом, нет повторных вычислений для circle.area одного и того же неизменного экземпляра.

6. @atexit.register: Объявляем функцию которая вызывается при выходе из программы

Декоратор @register из модуля atexit может позволить нам выполнить функцию при завершении работы интерпретатора Python.

Этот декоратор очень полезен для выполнения финальных задач, таких как освобождение ресурсов или просто прощание! ��

На выходе получаем

Еще больше примеров использования Python и Machine Learning в современных сервисах можно посмотреть в моем телеграм канале. Я пишу про разработку, ML, стартапы и релокацию в UK для IT специалистов.

Похожие статьи