Русский
Русский
English
Статистика
Реклама

Не практичный pythonпишем декоратор в однустроку

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

Дисклеймер

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

Пролог

Идея написать декоратор в 4 строки меня изначально никак не трогала. Было просто интересно написать декоратор. Но в процессе спортивный интерес взял верх. Изначально все начиналось с простого кэширующего декоратора.

data = {}  # словарь для сохарения кэшируемых данныхdef decor_cache(func):      def wrapper(*args):        # создаем ключ из назвнии функции которую          # кэшируем и аргументов которые передаем        key = f"{func.__name__}{args}"         # проверяем кэшировли дунную функцию с аргументами         if args in data:            return data.get(key)        else:            # если кэшируем впервые            response = func(args) # запускаем функцию с аргументами             data[key] = response # кэшируем результат             return response      return wrapper

Сейчас задача из 18 строк кода, 11 если удалить пробелы и комментарии, сделать 4 строки. Первое что приходит на ум, записать конструкцию ifelse в одну строчку.

data = {}  # словарь для сохарения кэшируемых данныхdef decor_cache(func):      def wrapper(*args):        # создаем ключ из назвнии функции которую          # кэшируем и аргументов которые передаем        key = f"{func.__name__}{args}"        if not args in data            # если кэшируем впервые            response = func(args) # запускаем функцию с аргументами             data[key] = response # кэшируем результат         return data.get(key) if args in data else response         return wrapper

Теперь у нас 15 строк кода против 18, и появился ещё один if, что создает дополнительную вычислительную нагрузку, но сегодня мы собрались не для улучшению performance. Давайте добавим в этот мир энтропии и немного copy-paste и упростим переменную key.

data = {}  # словарь для сохарения кэшируемых данныхdef decor_cache(func):      def wrapper(*args):        if not args in data            # если кэшируем впервые            response = func(args) # запускаем функцию с аргументами             data[f"{func.__name__}{args}"] = response # кэшируем результат         return data.get(f"{func.__name__}{args}") if args in data else response         return wrapper

Теперь мы имеем 12 строк, без пробелов и комментариев 8 строк. Нам пока этого не достаточно, цель 4 строчки, и надо упростить ещё. Мы помним что декораторэто функция которая должна возвращать callable объект (функцию). Функцией может быть и lambda! Значит мы можем упростить и функцию wrapper и заменить её на lambdaанонимную функцию. И возвращать из функции "декоратора", анонимную функцию.

data = {}  # словарь для сохарения кэшируемых данныхdef decor_cache(func):  cache = labda *args: data.get(f"{func.__name__}{args}") if args in data else data[f"{func.__name__}{args}"] = func(args)     return labda *args: cache(*args) if cache(*args) else data.get(f"{func.__name__}{args}")

Цель достигнута! Декоратор в 4 строки, чистого кодаполучился. Как можно увидеть одной lambda функцией не обошлось, пришлось создать две lambda функцию. Первая lambda делает две вещи: если объект уже был закешировав возвращаем ранее закешировнанное значение и кэширует объект если он не был ранее кэширован но в таком случае мы нечего не возвращаем.

Первая lambda функция теперь у нас не анонимная её пришлось записать в переменную чтобы к ней можно было обращаться по имени переменной. Вторая lambda делает примерно тоже самое что и первая только обращалась к первой lambda по имени, кеширует и получает значения из кеша, в случае если первая lambda не вернула значение, значит объект новый и он был закэширован и значит вторая lambda вернет входящие значение.

Конструкция получилась громоздкой, но все равно читаемая, более или менеепри условии если вы легко читаете lambda выражения. Много условностей, но это ещё не ад но мы к нему приближаемся. После всего проделанного пути все ещё кажется что можно ещё сократить количество строк кода. Мы уже вошли во вкус. Например те-же две lambda выражения можно совместить в одно выражение. Давайте объеденим две lambda функции в одну.

Для этого нам надо пойти на некоторое ухищрение, использовать тернарный оператор or. Тернарный оператор принимает два значения, справа и слева относительно себя, и пытается получить логический ответ True или False. Как оператор сравнения. Для того чтобы вычислить конструкцию слева и справа интерпретатор python выполнит код справа и слева. Слева у нас конструкция memory.update({f"{func.name}_{args[0]}": func(args[0])}) данное выражение вернет нам None метод update всегда будет возвращать нам None тернарный оператор воспримит этого как False и не будет это выводить, но главное что он выполнит этот код и мы обновим переменную memory. Справа у нас конструкция получения элемента по индексу из tupla, выражение простое и всегда будет давать результат, если в tuple будет запрашиваемый индекс.

data = {}  # словарь для сохарения кэшируемых данныхdef decor_cache(func):    return lambda *args: memory.get(f"{func.__name__}_{args[0]}") if f"{func.__name__}_{args[0]}" in memory else (lambda: memory.update({f"{func.__name__}_{args[0]}": func(args[0])}) or args[0])()

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

data = {}  # словарь для сохарения кэшируемых данныхdecor_cache = lambda func: lambda *args: memory.get(f"{func.__name__}_{args[0]}") if f"{func.__name__}_{args[0]}" in memory else (lambda: memory.update({f"{func.__name__}_{args[0]}": func(args[0])}) or args[0])()

Нарушая все паттерны, нам удалось создать кэширующий декоратор, почти в одну строку. Почти, потому что формально у нас есть строка объявления переменной data. Это мне не давало покоя... примерно 10 минут, пока не вспомнил что в python есть функция globals().

Функцияglobals()возвращает словарь с глобальной таблицей символов, определённых в модуле. По сути выдает словарь глобальных переменных (ключимя переменной, значениессылка на объект). Так мы получаем возможность создавать переменные в одно выражение, одной строкой. Давайте тогда для создания переменной с пустым словарем, будем использовать следующую конструкцию:

globals().update({memory: {}})

И для получения значения переменной конструкцию с get:

globals().get(memory)

После чего мы применили это на наш декоратор, и теперь действительно все получилось уместить в одну строку.

decor_cache = lambda func: lambda *args: globals().get("memory").get(f"{func.__name__}_{args[0]}") if f"{func.__name__}_{args[0]}" in globals().get("memory") else (lambda : globals().get("memory").update({f"{func.__name__}_{args[0]}": func(args[0])}) or args[0])()

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

Итоги

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

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

Источник: habr.com
К списку статей
Опубликовано: 14.06.2021 16:14:45
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Python

Функциональное программирование

Python3

Lambda

Первые шаги

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru