Я знаю, что этот вопрос старый, но некоторые комментарии являются новыми, и хотя все жизнеспособные решения по сути одинаковы, большинство из них не очень чисты или не легко читаются.
Как и в ответе Тобе, единственный способ справиться с обоими случаями - проверить оба сценария. Самый простой способ - просто проверить, существует ли один аргумент и является ли он callabe (ПРИМЕЧАНИЕ: дополнительные проверки будут необходимы, если ваш декоратор принимает только 1 аргумент и это может быть вызываемый объект):
def decorator(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
# called as @decorator
else:
# called as @decorator(*args, **kwargs)
В первом случае вы делаете то, что делает любой обычный декоратор, возвращаете модифицированную или упакованную версию переданной функции.
Во втором случае вы возвращаете «новый» декоратор, который каким-то образом использует информацию, переданную с помощью * 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]):
# actual decorated function
return f(args[0])
else:
# decorator arguments
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
# try normal
@mult
def f(x, y):
return x + y
# try args
@mult(3)
def f2(x, y):
return x*y
# try kwargs
@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