Як передати додаткові аргументи декоратору Python?


99

У мене є декоратор, як показано нижче.

def myDecorator(test_func):
    return callSomeWrapper(test_func)
def callSomeWrapper(test_func):
    return test_func
@myDecorator
def someFunc():
    print 'hello'

Я хочу вдосконалити цей декоратор, щоб прийняти ще один аргумент, як показано нижче

def myDecorator(test_func,logIt):
    if logIt:
        print "Calling Function: " + test_func.__name__
    return callSomeWrapper(test_func)
@myDecorator(False)
def someFunc():
    print 'Hello'

Але цей код дає помилку,

Помилка типу: myDecorator () приймає рівно 2 аргументи (1 задано)

Чому функція не передається автоматично? Як явно передати функцію функції декоратора?


3
balki: будь-ласка, уникайте використання логічного значення як аргументу, це не підхід gd і зменшує читабельність коду
Кіт Хо

8
@KitHo - це логічний прапор, тому використання логічного значення є правильним підходом.
AKX

2
@KitHo - що таке "gd"? Це добре"?
Роб Беднарк

Відповіді:


172

Оскільки ви викликаєте декоратор як функцію, йому потрібно повернути іншу функцію, яка є власне декоратором:

def my_decorator(param):
    def actual_decorator(func):
        print("Decorating function {}, with parameter {}".format(func.__name__, param))
        return function_wrapper(func)  # assume we defined a wrapper somewhere
    return actual_decorator

Зовнішня функція отримає будь-які аргументи, які ви передаєте явно, і повинна повернути внутрішню функцію. Внутрішній функції буде передано функцію для декорування та повернення зміненої функції.

Зазвичай ви хочете, щоб декоратор змінив поведінку функції, обернувши її у функцію обгортки. Ось приклад, який необов’язково додає реєстрацію при виклику функції:

def log_decorator(log_enabled):
    def actual_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if log_enabled:
                print("Calling Function: " + func.__name__)
            return func(*args, **kwargs)
        return wrapper
    return actual_decorator

Ці functools.wrapsкопії виклику річ , як ім'я та рядок документація для функції обгортки, щоб зробити його більш схожим на вихідну функцію.

Приклад використання:

>>> @log_decorator(True)
... def f(x):
...     return x+1
...
>>> f(4)
Calling Function: f
5

11
А використання functools.wrapsдоцільно - воно зберігає оригінальну назву, документ та ін. Оберненої функції.
AKX

@AKX: Дякую, я додав це до другого прикладу.
інтержой

1
Отже, в основному декоратор завжди бере лише один аргумент, який є функцією. Але декоратор може бути поверненим значенням функції, яка може приймати аргументи. Це правильно?
balki

2
@balki: Так, це правильно. Що бентежить, так це те, що багато людей також називатимуть зовнішню функцію ( myDecoratorтут) декоратором. Це зручно для користувача декоратора, але може заплутати, коли ви намагаєтеся написати його.
інтержой

1
Маленька деталь, яка мене збентежила: якщо log_decoratorви берете аргумент за замовчуванням, ви не можете використовувати @log_decorator, це має бути@log_decorator()
Стардіді

46

Просто для надання іншої точки зору: синтаксису

@expr
def func(...): #stuff

еквівалентно

def func(...): #stuff
func = expr(func)

Зокрема, це exprможе бути все, що вам подобається, якщо воно оцінюється як викличний. В Зокрема зокрема, exprможе бути декоратором заводу: ви даєте йому деякі параметри , і це дає вам декоратор. Тож, можливо, кращий спосіб зрозуміти свою ситуацію такий, як

dec = decorator_factory(*args)
@dec
def func(...):

який потім можна скоротити до

@decorator_factory(*args)
def func(...):

Звичайно, так як це виглядає , як decorator_factoryце декоратор, люди схильні називати його , щоб відобразити це. Що може заплутати, коли ви намагаєтесь слідувати рівням непрямості.


Дякую, це справді допомогло мені зрозуміти обгрунтування того, що відбувається.
Адітя Шрірам

25

Просто хочу додати якийсь корисний фокус, який дозволить зробити аргументи декоратора необов’язковими. Це також дозволить повторно використовувати декоратор і зменшити вкладеність

import functools

def myDecorator(test_func=None,logIt=None):
    if not test_func:
        return functools.partial(myDecorator, logIt=logIt)
    @functools.wraps(test_func)
    def f(*args, **kwargs):
        if logIt==1:
            print 'Logging level 1 for {}'.format(test_func.__name__)
        if logIt==2:
            print 'Logging level 2 for {}'.format(test_func.__name__)
        return test_func(*args, **kwargs)
    return f

#new decorator 
myDecorator_2 = myDecorator(logIt=2)

@myDecorator(logIt=2)
def pow2(i):
    return i**2

@myDecorator
def pow3(i):
    return i**3

@myDecorator_2
def pow4(i):
    return i**4

print pow2(2)
print pow3(2)
print pow4(2)

16

Просто ще один спосіб зробити декоратори. Я вважаю, що таким способом найпростіше обернути голову.

class NiceDecorator:
    def __init__(self, param_foo='a', param_bar='b'):
        self.param_foo = param_foo
        self.param_bar = param_bar

    def __call__(self, func):
        def my_logic(*args, **kwargs):
            # whatever logic your decorator is supposed to implement goes in here
            print('pre action baz')
            print(self.param_bar)
            # including the call to the decorated function (if you want to do that)
            result = func(*args, **kwargs)
            print('post action beep')
            return result

        return my_logic

# usage example from here on
@NiceDecorator(param_bar='baaar')
def example():
    print('example yay')


example()

Дякую! дивився на деякі "розумні" рішення "близько 30 хвилин, і це перше, що насправді має сенс.
canhazbits

0

Тепер, якщо ви хочете викликати функцію function1з декоратором, decorator_with_argі в цьому випадку і функція, і декоратор беруть аргументи,

def function1(a, b):
    print (a, b)

decorator_with_arg(10)(function1)(1, 2)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.