Что эквивалентно декораторам с аргументами без синтаксического сахара? - PullRequest
5 голосов
/ 11 апреля 2020

Я изучаю декораторы и наткнулся на пример, где декоратор взял аргумент. Это немного смутило меня, потому что я узнал, что ( примечание: примеры из этого вопроса в основном из этой статьи ):

def my_decorator(func):
  def inner(*args, **kwargs):
    print('Before function runs')
    func(*args, **kwargs)
    print('After function ran')
  return inner

@my_decorator
def foo(thing_to_print):
  print(thing_to_print)

foo('Hello')
# Returns:
# Before function runs
# Hello
# After function ran

было эквивалентно

foo = my_wrapper(foo)

Так что мне не имеет смысла, как что-то может принимать аргумент, чтобы лучше объяснить, вот пример декоратора, который принимает аргумент:

def repeat(num_times):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                value = func(*args, **kwargs)
            return value
        return wrapper_repeat
    return decorator_repeat

@repeat(num_times=4)
def greet(name):
    print(f"Hello {name}")

greet('Bob')
# Returns:
# Hello Bob
# Hello Bob
# Hello Bob
# Hello Bob

Поэтому, когда я вижу это, я думаю:

greet = repeat(greet, num_times=4)

Я знаю, что это не может быть правильным, потому что num_times - единственный аргумент, который должен быть пройденным. Так что же является правильным эквивалентом @repeat(num_times=4) без "@ -symbol-syntax"? Спасибо!

Ответы [ 4 ]

2 голосов
/ 11 апреля 2020

В этом случае это будет:

greet = repeat(num_times=4)(greet)

Это объясняет два уровня вложенности в пределах repeat (вам нужно вызывать функцию «дважды», вы могли бы сказать). repeat(num_times=4) возвращает декоратор, затем этот декоратор оборачивается вокруг greet.

2 голосов
/ 11 апреля 2020

Эта статья та же самая, что рассказала мне все, что я знаю о декораторах! Это великолепно. Что касается синтаксиса не @ символа:

Вы можете представить себе фактическую функцию декоратора decorator_repeat(func), функция в repeat(num_times=4).

@repeat(num_times=4) возвращает декоратор, который по существу @decorator_repeat, за исключением @decorator_repeat, теперь имеет доступ к переменной num_times.

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

2 голосов
/ 11 апреля 2020

Из вашего примера кода, repeat возвращает определение decorator_repeat.
Таким образом, вы можете вызвать функцию decorator_repeat и передать функцию greet следующим образом:

def greet(name):
    print(f"Hello {name}")

greet = repeat(num_times=4)(greet)

greet('Bob')
# Hello Bob
# Hello Bob
# Hello Bob
# Hello Bob
0 голосов
/ 11 апреля 2020

будет лучше, если вы используете

from typing import Callable

def func(*args, **kwargs):
    pass

def repeat(times: int, func: Callable, *args, **kwargs):
    for __ in range(times):
        value = func(*args, **kwargs)
    return value

# usage: repear(4, func, ...)

, тогда вы можете сделать это другой формой, чтобы repeat возвратил функцию:

def repeat(times: int, func: Callable) -> Callable:
    # the function that you really have to have:
    def inner(*args, **kwargs):
        for __ in times:
            value = func(*args, **kwargs)
        return valur
    # return the function that you really want
    return inner

# usage: repeat(4, func)(...)

Хорошо, но теперь вы думаю, декоратор будет лучше:

def repeat(func: Callable) -> Callable:
    # the function that you really have to have:
    def inner(*args, **kwargs):
        for __ in times:
            value = func(*args, **kwargs)
        return valur
    # return the function that you really want
    return inner

теперь вы можете использовать его как:

times = 4
@repeat
def func(*args, **kwargs):
    pass

но это ужасно, как насчет того, чтобы сделать это сильной функцией? Хорошая идея.

def outer_repeat(times: int):
    return repeat

теперь вы можете работать так:

@outer_repeat(times = 4)
def func(*args, **kwargs):
    pass

, поэтому окончательное решение будет выглядеть так:

def out_repeat(times: int):
    def repeat(func: Callable):
        # the function that you want to have
        @functools.wraps(func)
        def inner(*args, **kwargs):
            for __ in range(times):
                value = func(*args, **kwargs)
            return value
        # return the function that you wanna
        return inner
    return repeat
...