Я знаю, що це питання давнє, але деякі коментарі є новими, і хоча всі життєздатні рішення по суті однакові, більшість з них не дуже чисті чи легкі для читання.
Як сказано у відповіді thobe, єдиним способом вирішити обидва випадки є перевірка обох сценаріїв. Найпростіший спосіб - це просто перевірити, чи є один аргумент, і це callabe (ПРИМІТКА: додаткові перевірки будуть необхідні, якщо ваш декоратор бере лише 1 аргумент, і це є об’єктом, що викликається):
def decorator(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
else:
У першому випадку ви робите те, що робить будь-який звичайний декоратор, повертаєте модифіковану або загорнуту версію переданої функції.
У другому випадку ви повертаєте "новий" декоратор, який так чи інакше використовує інформацію, передану з * args, ** kwargs.
Це добре, і все, але виписувати це для кожного декоратора, який ви робите, може бути досить неприємним і не таким чистим. Натомість було б непогано мати можливість автоматично модифікувати наші декоратори без необхідності переписувати їх ... але для цього вони потрібні!
Використовуючи наступний декоратор, ми можемо дезократувати наших декораторів, щоб їх можна було використовувати з аргументами або без них:
def doublewrap(f):
'''
a decorator decorator, allowing the decorator to be used as:
@decorator(with, arguments, and=kwargs)
or
@decorator
'''
@wraps(f)
def new_dec(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
return f(args[0])
else:
return lambda realf: f(realf, *args, **kwargs)
return new_dec
Тепер ми можемо прикрасити наших декораторів @doublewrap, і вони будуть працювати з аргументами та без них, з одним застереженням:
Я зазначав вище, але тут слід повторити, перевірка цього декоратора робить припущення щодо аргументів, які декоратор може отримати (а саме, що він не може отримати жодного аргументу, що викликається). Оскільки зараз ми робимо його застосовним до будь-якого генератора, його потрібно пам’ятати або модифікувати, якщо це буде суперечити.
Наступне демонструє його використання:
def test_doublewrap():
from util import doublewrap
from functools import wraps
@doublewrap
def mult(f, factor=2):
'''multiply a function's return value'''
@wraps(f)
def wrap(*args, **kwargs):
return factor*f(*args,**kwargs)
return wrap
@mult
def f(x, y):
return x + y
@mult(3)
def f2(x, y):
return x*y
@mult(factor=5)
def f3(x, y):
return x - y
assert f(2,3) == 10
assert f2(2,5) == 30
assert f3(8,1) == 5*7
@redirect_output
надзвичайно малоінформативний. Я вважаю, що це погана ідея. Використовуйте першу форму і значно спростіть своє життя.