Как стать автором
Обновить

Комментарии 14

НЛО прилетело и опубликовало эту надпись здесь

Вообще-то нет. Описанный по ссылке антипаттерн возникает, если полагать, что массив/словарь инициализируется заново в каждом вызове функции:


A programmer wrote the append function below under the assumption that the append function would return a new list every time that the function is called without the second argument. In reality this is not what happens.

А описанный здесь приём основывается как раз на том, что значение будет общим для всех вызовов.

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

Насчёт неочевидности кэширования и отсутствия контроля за памятью согласен, в теории такой кэш может без предупреждения разрастись на несколько гиг. Метод хорош в более-менее недолго работающих программах (в моём случае научные расчёты, которым надо отработать максимум три-четыре раза по сутки каждый за исследование), но оставлять такую функцию бесконечно крутиться на сервере не стоит.


Насчёт интерфейса не согласен, потому что публичный интерфейс функции никак не отличается от некэшируемого аналога. Новые аттрибуты не появляются, старые не удаляются. У любой функции есть __defaults__, у любой функции в него можно залезть. Разве что у некэшируемой меньше вероятность найти там что-то интересное, но и тут эксплицитное обращение к function.__defaults__[0][query_value] не нормальный интерфейс, а хак вокруг хака.

Насчёт интерфейса не согласен, потому что публичный интерфейс функции никак не отличается от некэшируемого аналога


Как же не отличается, если вводится дополнительная переменная cache, который ещё и можно инвалидировать извне функции?
В случае с базой (как в статье) можно использовать 2 функции: одна принимает полный набор параметров (некоторые из которых нехэшируемые), а затем вызывает другую функцию с меньшим набором параметров(все хэшируемые), и она уже использует @lru_cache
И всё это — вместо того, чтобы улучшить архитектуру.
В реальных проектах данные обычно меняются во времени, т.е. нужно еще как минимум задавать таймаут на рефреш кеша, поэтому реализовывать предложенным методом станет накладно в каждой функции.
В случае с моей базой не меняются, пока я не накачу апдейт (каковые выходят раз в несколько месяцев). Но в общем случае согласен.
Я бы сказал, что это заслуженно малоизвестный приём. Меняется интерфейс функции, да и код с декоратором гораздо чище.
@functools.lru_cache. Декоратор из модуля functools, который запоминает последние вызовы функции. Надёжно и просто, но использует в качестве ключей все параметры функции, а значит, требует их хэшируемости и не может заметить, что два формально разных значения параметра эквивалентны.

А у вас разве как-то иначе? Вы тоже используете значения как ключи словаря, что требует, чтобы они были hashable.


if a not in cache: 
    cache[a] = a*a

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории