Рубрики
Без рубрики

Развертывающие декораторы, часть 2

Вторая половина серии на Python Decorators – с более продвинутыми темами. Помечено Python, Pythonic, Functional.

Последнее сообщение, я писал о основах декораторов в Python. Для тех из вас, что пропустил это, вот основные моменты.

  1. Декораторы размещаются перед определениями функций и служат для обертывания или добавления дополнительных функций для функций без скрытия одной цели данной функции.
  2. Они используются так:
@custom_decorator
def generic_example_function():
    # ...
    pass
  1. При определении функции декоратора он должен принять функцию в качестве ввода и вывода новой/разной/измененной/завернутой функции.
def custom_decorator(func):
    # *args, **kwargs allow your decorated function to handle
    # the inputs it is supposed to without problems

    def modified_function(*args, **kwargs):
        # Do some extra stuff
        # ...
        return func(*args, **kwargs) # Call the input function as it
                                    # was originally called and return that

    return modified_function

Хорошо. Это о покрытии это. Давайте доберемся до хороших вещей! Я собираюсь покрыть проходящие аргументы в декораторы (a a a la flasks @ app.route ('/') ), декораторы укладки и декораторы на основе классов.

Вы можете пройти аргументы в декоратор! Это становится немного сложнее, хотя. Помните, как базовая функция декоратора занимает функцию, определяет новую функцию и возвращает это? Если у вас есть аргументы, вы на самом деле должны генерировать декоратор на лету, поэтому вы должны определить функцию, которая возвращает функцию декоратора, которая возвращает фактическую функцию, которую вы заботитесь о. Oy vey. Go Go Go Gadget Code Пример!

from time import sleep

def delay(seconds): # The outermost function handles the decorator's arguments

    def delay_decorator(func): # It defines a decorator function, like we are used to

        def inner(*args, **kwargs): # The decorator function defines the modified function
            # Because we do things this way, the inner function
            # gets access to the arguments supplied to the decorator initially
            sleep(seconds)
            return func(*args, **kwargs)

        return inner  # Decorator function returns the modified function

    return delay_decorator # Finally, the outer function returns the custom decorator

@delay(5)
def sneeze(times):
    return "Achoo! " * times

>>> sneeze(3)
(wait 5 seconds)
"Achoo! Achoo! Achoo!"

Опять же, это может сначала выглядеть запутано. Вы можете подумать об этом таким образом: внешняя функция, задержка В этом случае ведет себя так, будто он называется прямо при добавлении декоратора. Как только переводчик читает @delay (5) , он запускает функцию задержки и заменяет @delay Декоратор с модифицированным возвращенным декоратором. Во время выполнения, когда мы называем чихать Похоже, похоже чихать обернут в Delay_Decorator с секунды Отказ Таким образом, фактическая функция, которая называется Внутренний , что это чихать завернут в 5 секундную спальную функцию. Все еще запутался? Я тоже немного. Может быть, просто спать на нем и вернуться.

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

def pop(func):

    def inner(*args, **kwargs):
        print("Pop!")
        return func(*args, **kwargs)

    return inner

def lock(func):

    def inner(*args, **kwargs):
        print("Lock!")
        return func(*args, **kwargs)

    return inner

@pop
@lock
def drop(it):
    print("Drop it!")
    return it[:-2]

>>> drop("This example is obnoxious, isn't it")
Pop!
Lock!
Drop it
"This example is obnoxious, isn't "

Как видите, вы можете обернуть функцию, которая уже завернута. В математике (и, на самом деле, в программировании), они назовел бы это Функциональная композиция Отказ Так же как f o g (x) (g (x)) , укладка @pop на @ замок на падение производит поп (замок (падение ())). Хьюи будет так гордиться.

… Без аргументов

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

class MySuperCoolDecorator:
    def __init__(self, func):
        print("Initializing decorator class")
        self.func = func
        func()

    def __call__(self):
        print("Calling decorator call method")
        self.func()

@MySuperCoolDecorator
def simple_function():
    print("Inside the simple function")

print("Decoration complete!")

simple_function()

Какие выходы:

Initializing decorator class
Inside the simple function
Decoration complete!
Calling decorator call method
Inside the simple function

… С аргументами

Классовые декораторы делают аргументы декоратора намного проще, но они ведут себя по-разному сверху. Я скажу это снова.

ПРЕДУПРЕЖДЕНИЕ: Классовые декораторы ведут себя по-разному в зависимости от того, имеют ли они аргументы.

Я не уверен, почему. Кто-то, кто умнее меня, должен объяснить это. В любом случае, когда аргументы предоставляются декоратору, происходят три вещи.

  1. Аргументы декоратора передаются на __init__ функция.
  2. Сама функция передается на __call__ функция.
  3. __call__ Функция вызывается только один раз, и он немедленно вызывается, аналогично функциональным декораторам.

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

class PreloadedCache:
    # This method is called as soon as the decorator is attached to a function.
    def __init__(self, preloads={}):
        """Expects a dictionary of preloaded {input: output} pairs.
        I know it only works for one input, but I'm keeping it simple."""
        if preloads is None:
            self.cache = {}
        else:
            self.cache = preloads

    def __call__(self, func):
        # This method is called when a function is passed to the decorator
        def inner(n):
            if n in self.cache:
                return self.cache[n]
            else:
                result = func(n)
                self.cache[n] = result
                return result
        return inner

@PreloadedCache({1: 1, 2: 1, 4: 3, 8: 21}) # First __init__, then __call__
def fibonacci(n):
    """Returns the nth fibonacci number"""
    if n in (1, 2):
        return 1
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

# At runtime, the 'inner' function above will actually be called!
# fibonacci(8) never actually gets called, because it's already in the cache!

Довольно круто верно? Я представляю, что эта версия создания декоратора, по крайней мере, для меня, наиболее интуитивно понятна.

Бонус в том, что в Python, поскольку функции являются объектами, вы можете добавлять их атрибуты. Таким образом, если вы измените __call__ Способ выше, чтобы добавить следующее:

    def __call__(self, func):
        # ... Everything except the last line
        inner.cache = self.cache # Attach a reference to the cache!!!
        return inner

>>> fibonacci(10)
55
>>> fibonacci.cache
{1: 1, 2: 1, 4: 3, 8: 21, 3: 2, 5: 5, 6: 8, 7: 13, 9: 34, 10: 55}

В любом случае, я знаю, что это много. Эта тема является одним из более запутанных темами Python для меня, но она действительно может сделать для Slick API, если вы делаете библиотеку. Просто посмотри на Колба, веб-каркас или Нажмите, CLI Framework Отказ Оба написаны той же командой, на самом деле! На самом деле, Я написал краткий пост о щелчке Данное назад, если вы заинтересованы.

В любом случае в любом случае, если у вас есть какие-либо вопросы о декораторах (или что-то еще в этом отношении), не стесняйтесь спрашивать меня! Я всегда рад помочь (даже если я обычно в конечном итоге, делая энергичный гудящий, прежде чем я могу полностью ответить на большинство вопросов). То же самое идет, если вы сможете объяснить что-то лучше, чем у меня или есть дополнительный вход. 😁

Первоначально опубликовано на мой блог Отказ Редактировать: Изменено @delay_function к @задерживать , был неверным раньше. Спасибо @radimsuckr Действительно Редактировать еще раз: изменить ФУНК к Self.func В начальном примере класса. @radimsuckr держать меня честно.

Оригинал: “https://dev.to/rpalo/unwrapping-decorators-part-2”