Python 使用可选参数制作装饰器
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3888158/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Making decorators with optional arguments
提问by orokusaki
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
Example: The following decorates my_functionwith foo_registerinstead of ever making it to decorator.
示例:以下装饰my_function用foo_register而不是使它成为decorator.
@foo_register
def my_function():
print('hi...')
Example: The following works as expected.
示例:以下按预期工作。
@foo_register('say_hi')
def my_function():
print('hi...')
If I want it to work correctly in both applications (one using method.__name__and one passing the name in), I have to check inside of foo_registerto see if the first argument is a decorator, and if so, I have to: return decorator(method_name)(instead of return decorator). This sort of "check to see if it's a callable" seems very hackish. Is there a nicer way to create a multi-use decorator like this?
如果我希望它在两个应用程序中都能正常工作(一个使用method.__name__和一个传入名称),我必须检查内部foo_register以查看第一个参数是否是装饰器,如果是,我必须:(return decorator(method_name)而不是return decorator)。这种“检查它是否是可调用的”似乎很hackish。有没有更好的方法来创建这样的多用途装饰器?
P.S. I already know that I can require the decorator to be called, but that's not a "solution". I want the API to feel natural. My wife loves decorating, and I don't want to ruin that.
PS 我已经知道我可以要求调用装饰器,但这不是“解决方案”。我希望 API 感觉自然。我妻子喜欢装饰,我不想破坏它。
采纳答案by orokusaki
Glenn - I had to do it then. I guess I'm glad that there is not a "magic" way to do it. I hate those.
格伦 - 我当时必须这样做。我想我很高兴没有“神奇”的方法来做到这一点。我讨厌那些。
So, here's my own answer (method names different than above, but same concept):
所以,这是我自己的答案(方法名称与上面不同,但概念相同):
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
Example usage (both versions work the same):
示例用法(两个版本的工作方式相同):
@register_gw_method
def my_function():
print('hi...')
@register_gw_method('say_hi')
def my_function():
print('hi...')
回答by Oscar
How about
怎么样
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
回答by Niklas B.
Now that this old thread is back at the top anyway, lemme just throw in some Decorator-ception:
既然这个旧线程无论如何都回到了顶部,那么让我加入一些装饰器概念:
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
Now your magical decorator is just a single line away!
现在你的魔法装饰器就在一行之外!
@magical_decorator
def foo_register(...):
# bla bla
By the way, this works for any decorator. It just causes @footo behave (as close as possibly) like @foo().
顺便说一下,这适用于任何装饰器。它只会导致@foo行为(尽可能接近)像@foo().
回答by K3---rnc
A generic decorator for decorating decorator definitions, expressing that decorated decorator accepts default arguments, which are set if none are explicitly given.
用于装饰装饰器定义的通用装饰器,表示装饰的装饰器接受默认参数,如果没有明确给出,则设置这些参数。
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
It can be used in either of ways.
它可以以任何一种方式使用。
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):
...
回答by Nicole
Through the help of the answers here and elsewhere and a bunch of trial and error I've found that there is actually a far easier and generic way to make decorators take optional arguments. It does check the args it was called with but there isn't any other way to do it.
通过这里和其他地方的答案的帮助以及一堆反复试验,我发现实际上有一种更简单和通用的方法来使装饰器采用可选参数。它确实检查了它被调用的参数,但没有任何其他方法可以做到这一点。
The key is to decorate your decorator.
关键是装饰你的装饰器。
Generic decorator decorator code
通用装饰器装饰器代码
Here is the decorator decorator (this code is generic and can be used by anyone who needs an optional arg decorator):
这是装饰器装饰器(此代码是通用的,任何需要可选 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
Usage
用法
Using it is as easy as:
使用它就像:
- Create a decorator like normal.
- After the first target function argument, add your optional arguments.
- Decorate the decorator with
optional_arg_decorator
- 像平常一样创建装饰器。
- 在第一个目标函数参数之后,添加您的可选参数。
- 装饰装饰器
optional_arg_decorator
Example:
例子:
@optional_arg_decorator
def example_decorator_with_args(fn, optional_arg = 'Default Value'):
...
return fn
Test cases
测试用例
For your use case:
对于您的用例:
So for your case, to save an attribute on the function with the passed-in method name or the __name__if None:
因此,对于您的情况,要使用传入的方法名称或__name__if None在函数上保存属性:
@optional_arg_decorator
def register_method(fn, method_name = None):
fn.gw_method = method_name or fn.__name__
return fn
Add decorated methods
添加修饰方法
Now you have a decorator that is usable with or without args:
现在你有一个可以使用或不使用 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 :)'
回答by Patbenavente
The cleanest way I know of for doing this is the following:
我所知道的最干净的方法如下:
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
Explanation
解释
When the decorator is called with no optional arguments like this:
当没有像这样的可选参数调用装饰器时:
@decorator
def function ...
The function is passed as the first argument and decorate returns the decorated function, as expected.
该函数作为第一个参数传递,decorate 按预期返回装饰后的函数。
If the decorator is called with one or more optional arguments like this:
如果使用一个或多个可选参数调用装饰器,如下所示:
@decorator(optional_argument1='some value')
def function ....
Then decorator is called with the function argument with value None, so a function that decorates is returned, as expected.
然后使用值为 None 的函数参数调用装饰器,因此按预期返回装饰的函数。
Python 3
蟒蛇 3
Note that the decorator signature above may be improved with Python 3-specific *,syntax to enforce safe use of keyword arguments. Simply replace the signature of the outermost function with:
请注意,上面的装饰器签名可能会使用 Python 3 特定的*,语法进行改进,以强制安全使用关键字参数。只需将最外层函数的签名替换为:
def decorator(original_function=None, *, optional_argument1=None, optional_argument2=None, ...):
回答by Karolis ?arapnickis
If you want this functionality on multiple decorators you can evade the code boilerplate with a decorator for a decorator:
如果您希望在多个装饰器上使用此功能,您可以使用装饰器的装饰器来逃避代码样板:
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 ...
Note: it has the drawback where you can't pass 1 argument as function to decorator.
注意:它有一个缺点,你不能将 1 个参数作为函数传递给装饰器。
Note2: if you have tips/notes on how to improve this decorator, you could comment at code review: https://codereview.stackexchange.com/questions/78829/python-decorator-for-optional-arguments-decorator
注意2:如果您有关于如何改进此装饰器的提示/注释,您可以在代码中发表评论:https: //codereview.stackexchange.com/questions/78829/python-decorator-for-optional-arguments-decorator
回答by Ryne Everett
Enhanced Generic Decorator Decorator Code
增强的通用装饰器装饰器代码
Here's my adaption of @Nicole's answerwith the following enhancements:
这是我对@Nicole 的回答的改编,并进行了以下改进:
- optional kwargs may be passed to the decorated decorator
- the decorated decorator may be a bound method
- 可选的 kwargs 可以传递给装饰的装饰器
- 装饰的装饰器可能是一个绑定方法
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
回答by Julian Smith
Here's another variation, which is fairly concise and doesn't use functools:
这是另一个变体,它相当简洁并且不使用 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)
Depending on whether inner_decoratorcan be called with only one parameter, one can then do @decorator, @decorator(), @decorator(24)etc.
根据是否inner_decorator可以只用一个参数调用,可以再做@decorator,@decorator(),@decorator(24)等。
This can be generalised to a 'decorator decorator':
这可以概括为“装饰器装饰器”:
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(): ...
回答by MajorTom
Here is an other solution that work also if the optional argument is a callable:
如果可选参数是可调用的,则这是另一种解决方案:
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)
This way both syntax will work:
这样两种语法都可以工作:
@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

