from functools import wraps
def foo_register(method_name=None):
"""Does stuff."""
def decorator(method):
if method_name is None:
method.gw_method = method.__name__
else:
method.gw_method = method_name
@wraps(method)
def wrapper(*args, **kwargs):
method(*args, **kwargs)
return wrapper
return decorator
示例:以下内容使用my_function
修饰foo_register
,而不是将其设为decorator
。
@foo_register
def my_function():
print('hi...')
示例:以下按预期工作。
@foo_register('say_hi')
def my_function():
print('hi...')
如果我希望它在两个应用程序中正常工作(一个使用method.__name__
,一个传递名称),我必须检查foo_register
内部以查看第一个参数是否为装饰器,如果是这样,我必须:return decorator(method_name)
(而不是return decorator
)。这种“检查它是否可以调用”似乎非常hackish。有没有更好的方法来创建这样的多用途装饰器?
P.S。我已经知道我可以要求调用装饰器,但这不是一个“解决方案”。我希望API感觉自然。我的妻子喜欢装饰,我不想破坏它。
答案 0 :(得分:48)
我所知道的最干净的方法如下:
import functools
def decorator(original_function=None, optional_argument1=None, optional_argument2=None, ...):
def _decorate(function):
@functools.wraps(function)
def wrapped_function(*args, **kwargs):
...
return wrapped_function
if original_function:
return _decorate(original_function)
return _decorate
<强>解释强>
当调用装饰器时没有像这样的可选参数:
@decorator
def function ...
该函数作为第一个参数传递,而decorate按预期返回修饰函数。
如果使用一个或多个可选参数调用装饰器,如下所示:
@decorator(optional_argument1='some value')
def function ....
然后使用值为None的函数参数调用装饰器,因此按预期返回装饰的函数。
Python 3
请注意,上面的装饰器签名可以使用Python 3特定的*,
语法进行改进,以强制安全使用关键字参数。只需将最外层函数的签名替换为:
def decorator(original_function=None, *, optional_argument1=None, optional_argument2=None, ...):
答案 1 :(得分:36)
通过这里和其他地方的答案以及一堆反复试验的帮助,我发现实际上有一种更简单,更通用的方法可以使装饰器采用可选参数。它确实检查了它被调用的args,但没有任何其他方法可以做到。
关键是装饰你的装饰。
这是装饰器装饰器(此代码是通用的,任何需要可选arg装饰器的人都可以使用):
def optional_arg_decorator(fn):
def wrapped_decorator(*args):
if len(args) == 1 and callable(args[0]):
return fn(args[0])
else:
def real_decorator(decoratee):
return fn(decoratee, *args)
return real_decorator
return wrapped_decorator
使用它就像:
optional_arg_decorator
示例:
@optional_arg_decorator
def example_decorator_with_args(fn, optional_arg = 'Default Value'):
...
return fn
因此,对于您的情况,使用传入的方法名称保存函数的属性,或者__name__
如果无:
@optional_arg_decorator
def register_method(fn, method_name = None):
fn.gw_method = method_name or fn.__name__
return fn
现在你有一个可用的有或没有args 的装饰器:
@register_method('Custom Name')
def custom_name():
pass
@register_method
def default_name():
pass
assert custom_name.gw_method == 'Custom Name'
assert default_name.gw_method == 'default_name'
print 'Test passes :)'
答案 2 :(得分:26)
所以,这是我自己的答案(方法名称与上述不同,但概念相同):
from functools import wraps
def register_gw_method(method_or_name):
"""Cool!"""
def decorator(method):
if callable(method_or_name):
method.gw_method = method.__name__
else:
method.gw_method = method_or_name
@wraps(method)
def wrapper(*args, **kwargs):
method(*args, **kwargs)
return wrapper
if callable(method_or_name):
return decorator(method_or_name)
return decorator
示例用法(两个版本的工作方式相同):
@register_gw_method
def my_function():
print('hi...')
@register_gw_method('say_hi')
def my_function():
print('hi...')
答案 3 :(得分:10)
怎么样
from functools import wraps, partial
def foo_register(method=None, string=None):
if not callable(method):
return partial(foo_register, string=method)
method.gw_method = string or method.__name__
@wraps(method)
def wrapper(*args, **kwargs):
method(*args, **kwargs)
return wrapper
答案 4 :(得分:7)
以下是我对@NickC's answer的改编,其中包含以下增强功能:
import functools
def optional_arg_decorator(fn):
@functools.wraps(fn)
def wrapped_decorator(*args, **kwargs):
is_bound_method = hasattr(args[0], fn.__name__) if args else False
if is_bound_method:
klass = args[0]
args = args[1:]
# If no arguments were passed...
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
if is_bound_method:
return fn(klass, args[0])
else:
return fn(args[0])
else:
def real_decorator(decoratee):
if is_bound_method:
return fn(klass, decoratee, *args, **kwargs)
else:
return fn(decoratee, *args, **kwargs)
return real_decorator
return wrapped_decorator
答案 5 :(得分:4)
现在这个旧线程无论如何都回到顶部,lemme只是扔掉了一些装饰器:
def magical_decorator(decorator):
@wraps(decorator)
def inner(*args, **kw):
if len(args) == 1 and not kw and callable(args[0]):
return decorator()(args[0])
else:
return decorator(*args, **kw)
return inner
现在你的神奇装饰师只需一行就可以了!
@magical_decorator
def foo_register(...):
# bla bla
顺便说一句,这适用于任何装饰者。它只会导致@foo
表现得尽可能接近@foo()
。
答案 6 :(得分:3)
用于装饰装饰器定义的通用装饰器,表示装饰装饰器接受默认参数,如果没有明确给出则设置默认参数。
from functools import wraps
def default_arguments(*default_args, **default_kwargs):
def _dwrapper(decorator):
@wraps(decorator)
def _fwrapper(*args, **kwargs):
if callable(args[0]) and len(args) == 1 and not kwargs:
return decorator(*default_args, **default_kwargs)(args[0])
return decorator(*args, **kwargs)
return _fwrapper
return _dwrapper
它可以以任何一种方式使用。
from functools import lru_cache # memoization decorator from Python 3
# apply decorator to decorator post definition
lru_cache = (default_arguments(maxsize=100)) (lru_cache)
# could also be:
# @default_arguments(maxsize=100)
# class lru_cache(object):
# def __init__(self, maxsize):
# ...
# def __call__(self, wrapped_function):
# ...
@lru_cache # this works
def fibonacci(n):
...
@lru_cache(200) # this also works
def fibonacci(n):
...
答案 7 :(得分:1)
如果你想在多个装饰器上使用这个功能,你可以使用装饰器的装饰器来逃避代码样板:
from functools import wraps
import inspect
def decorator_defaults(**defined_defaults):
def decorator(f):
args_names = inspect.getargspec(f)[0]
def wrapper(*new_args, **new_kwargs):
defaults = dict(defined_defaults, **new_kwargs)
if len(new_args) == 0:
return f(**defaults)
elif len(new_args) == 1 and callable(new_args[0]):
return f(**defaults)(new_args[0])
else:
too_many_args = False
if len(new_args) > len(args_names):
too_many_args = True
else:
for i in range(len(new_args)):
arg = new_args[i]
arg_name = args_names[i]
defaults[arg_name] = arg
if len(defaults) > len(args_names):
too_many_args = True
if not too_many_args:
final_defaults = []
for name in args_names:
final_defaults.append(defaults[name])
return f(*final_defaults)
if too_many_args:
raise TypeError("{0}() takes {1} argument(s) "
"but {2} were given".
format(f.__name__,
len(args_names),
len(defaults)))
return wrapper
return decorator
@decorator_defaults(start_val="-=[", end_val="]=-")
def my_text_decorator(start_val, end_val):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
return "".join([f.__name__, ' ', start_val,
f(*args, **kwargs), end_val])
return wrapper
return decorator
@decorator_defaults(end_val="]=-")
def my_text_decorator2(start_val, end_val):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
return "".join([f.__name__, ' ', start_val,
f(*args, **kwargs), end_val])
return wrapper
return decorator
@my_text_decorator
def func1a(value):
return value
@my_text_decorator()
def func2a(value):
return value
@my_text_decorator2("-=[")
def func2b(value):
return value
@my_text_decorator(end_val=" ...")
def func3a(value):
return value
@my_text_decorator2("-=[", end_val=" ...")
def func3b(value):
return value
@my_text_decorator("|> ", " <|")
def func4a(value):
return value
@my_text_decorator2("|> ", " <|")
def func4b(value):
return value
@my_text_decorator(end_val=" ...", start_val="|> ")
def func5a(value):
return value
@my_text_decorator2("|> ", end_val=" ...")
def func5b(value):
return value
print(func1a('My sample text')) # func1a -=[My sample text]=-
print(func2a('My sample text')) # func2a -=[My sample text]=-
print(func2b('My sample text')) # func2b -=[My sample text]=-
print(func3a('My sample text')) # func3a -=[My sample text ...
print(func3b('My sample text')) # func3b -=[My sample text ...
print(func4a('My sample text')) # func4a |> My sample text <|
print(func4b('My sample text')) # func4b |> My sample text <|
print(func5a('My sample text')) # func5a |> My sample text ...
print(func5b('My sample text')) # func5b |> My sample text ...
注意:它有一个缺点,你不能将1个参数作为函数传递给装饰器。
注意2:如果您有关于如何改进此装饰器的提示/说明,您可以在代码审核时发表评论:https://codereview.stackexchange.com/questions/78829/python-decorator-for-optional-arguments-decorator
答案 8 :(得分:0)
这是另一个变体,它非常简洁,不使用functools:
def decorator(*args, **kwargs):
def inner_decorator(fn, foo=23, bar=42, abc=None):
'''Always passed <fn>, the function to decorate.
# Do whatever decorating is required.
...
if len(args)==1 and len(kwargs)==0 and callable(args[0]):
return inner_decorator(args[0])
else:
return lambda fn: inner_decorator(fn, *args, **kwargs)
根据是否只能使用一个参数调用inner_decorator
,可以执行@decorator
,@decorator()
,@decorator(24)
等。
这可以推广到'装饰器装饰者':
def make_inner_decorator(inner_decorator):
def decorator(*args, **kwargs):
if len(args)==1 and len(kwargs)==0 and callable(args[0]):
return inner_decorator(args[0])
else:
return lambda fn: inner_decorator(fn, *args, **kwargs)
return decorator
@make_inner_decorator
def my_decorator(fn, a=34, b='foo'):
...
@my_decorator
def foo(): ...
@my_decorator()
def foo(): ...
@my_decorator(42)
def foo(): ...
答案 9 :(得分:0)
如果可选参数是可调用的,那么这是另一个解决方案:
def test_equal(func=None, optional_value=None):
if func is not None and optional_value is not None:
# prevent user to set func parameter manually
raise ValueError("Don't set 'func' parameter manually")
if optional_value is None:
optional_value = 10 # The default value (if needed)
def inner(function):
def func_wrapper(*args, **kwargs):
# do something
return function(*args, **kwargs) == optional_value
return func_wrapper
if not func:
return inner
return inner(func)
这样两种语法都可以工作:
@test_equal
def does_return_10():
return 10
@test_equal(optional_value=20)
def does_return_20():
return 20
# does_return_10() return True
# does_return_20() return True
答案 10 :(得分:0)
这是我为python3编写的解决方案。它与其他方法有不同的方法,因为它定义了一个可调用的类而不是一个函数。
#define NUM_EVENTS 11
typedef enum events {
ls = 0,
rs = 1,
ws = 2,
lo = 3,
ro = 4,
lc = 5,
rc = 6,
gru = 7,
grl = 8,
gll = 9,
glu = 10
} events;
你仍然必须明确地调用装饰器
class flexible_decorator:
def __init__(self, arg="This is default"):
self.arg = arg
def __call__(self, func):
def wrapper(*args, **kwargs):
print("Calling decorated function. arg '%s'" % self.arg)
func(*args, **kwargs)
return wrapper
答案 11 :(得分:0)
我已经制作了一个简单的包解决问题
主分公司
pip install git+https://github.com/ferrine/biwrap
最新发布的
pip install biwrap
有些包装器可能包含可选参数,我们通常希望避免@wrapper()
次调用,而是使用@wrapper
。
这适用于简单包装
import biwrap
@biwrap.biwrap
def hiwrap(fn, hi=True):
def new(*args, **kwargs):
if hi:
print('hi')
else:
print('bye')
return fn(*args, **kwargs)
return new
定义的包装器可以双向使用
@hiwrap
def fn(n):
print(n)
fn(1)
#> hi
#> 1
@hiwrap(hi=False)
def fn(n):
print(n)
fn(1)
#> bye
#> 1
biwrap
也适用于绑定方法
class O:
@hiwrap(hi=False)
def fn(self, n):
print(n)
O().fn(1)
#> bye
#> 1
也支持类方法/属性
class O:
def __init__(self, n):
self.n = n
@classmethod
@hiwrap
def fn(cls, n):
print(n)
@property
@hiwrap(hi=False)
def num(self):
return self.n
o = O(2)
o.fn(1)
#> hi
#> 1
print(o.num)
#> bye
#> 2
呼叫等功能也可以
def fn(n):
print(n)
fn = hiwrap(fn, hi=False)
fn(1)
#> bye
#> 1
答案 12 :(得分:0)
类似于使用可调用类
检查参数类型和长度的解决方案class decor(object):
def __init__(self, *args, **kwargs):
self.decor_args = args
self.decor_kwargs = kwargs
def __call__(self, *call_args, **call_kwargs):
if callable(self.decor_args[0]) and len(self.decor_args) == 1:
func = self.decor_args[0]
return self.__non_param__call__(func, call_args, call_kwargs)
else:
func = call_args[0]
return self.__param__call__(func)
def __non_param__call__(self, func, call_args, call_kwargs):
print "No args"
return func(*call_args, **call_kwargs)
def __param__call__(self, func):
def wrapper(*args, **kwargs):
print "With Args"
return func(*args, **kwargs)
return wrapper
@decor(a)
def test1(a):
print 'test' + a
@decor
def test2(b):
print 'test' + b
答案 13 :(得分:0)
这个问题让我非常恼火,最终写了一个库来解决它:decopatch。
它支持两种开发样式:嵌套(如python装饰工厂中的样式)和 flat (少一层嵌套)。这是您的示例在平面模式下实现的方式:
from decopatch import function_decorator, DECORATED
from makefun import wraps
@function_decorator
def foo_register(method_name=None, method=DECORATED):
if method_name is None:
method.gw_method = method.__name__
else:
method.gw_method = method_name
# create a signature-preserving wrapper
@wraps(method)
def wrapper(*args, **kwargs):
method(*args, **kwargs)
return wrapper
请注意,我在这里使用makefun.wraps而不是functools.wraps
,以便完全保留签名(如果参数无效,则根本不调用包装器)。
decopatch
支持另一种开发样式,我称之为 double-flat ,该样式专门用于创建像这样的签名保留功能包装器。您的示例将这样实现:
from decopatch import function_decorator, WRAPPED, F_ARGS, F_KWARGS
@function_decorator
def foo_register(method_name=None,
method=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS):
# this is directly the wrapper
if method_name is None:
method.gw_method = method.__name__
else:
method.gw_method = method_name
method(*f_args, **f_kwargs)
请注意,采用这种样式,您的所有代码都在对method
的调用中执行。这可能是不希望的-您可能希望仅在装饰时执行一次操作-为此,以前的样式会更好。
您可以检查两种样式是否都可以使用:
@foo_register
def my_function():
print('hi...')
@foo_register('say_hi')
def my_function():
print('hi...')
请检查documentation以获得详细信息。