用可选参数制作装饰器

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 

例如:下面的代码使用foo_register来装饰my_function ,而不是使它成为decorator

 @foo_register def my_function(): print('hi...') 

示例:以下按预期工作。

 @foo_register('say_hi') def my_function(): print('hi...') 

如果我希望它在两个应用程序(一个使用method.__name__名称method.__name__和一个传入名称)中正确工作,我必须检查foo_register内部以查看第一个参数是否是装饰器,如果是,则必须: return decorator(method_name) (而不是return decorator )。 这种“检查是否可以调用”似乎非常黑客。 有没有更好的方法来创build一个这样的多用途装饰器?

PS我已经知道,我可以要求装饰被称为,但这不是一个“解决scheme”。 我希望API感觉自然。 我的妻子喜欢装饰,我不想毁了。

通过在这里和其他地方的答案和一堆试验和错误的帮助,我发现实际上有一个更容易和通用的方法来使装饰器采取可选参数。 它确实检查了被调用的参数,但是没有其他的方法可以做到这一点。

关键是要装饰你的装饰

generics装饰器装饰器代码

这里是装饰器装饰器(这个代码是通用的,可以被任何需要可选的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 

用法

使用它是一样简单的:

  1. 像正常一样创build一个装饰器。
  2. 在第一个目标函数参数之后,添加您的可选参数。
  3. 使用optional_arg_decorator装饰装饰器

例:

 @optional_arg_decorator def example_decorator_with_args(fn, optional_arg = 'Default Value'): ... return fn 

testing用例

对于你的用例:

所以对于你的情况,要保存一个属性的函数与传入的方法名或__name__如果None

 @optional_arg_decorator def register_method(fn, method_name = None): fn.gw_method = method_name or fn.__name__ return fn 

添加装饰的方法

现在你有一个装饰器,可以使用或不使用参数

 @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 :)' 

我知道的最干净的方式是这样的:

 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 ... 

该函数作为第一个parameter passing并装饰返回装饰函数,如预期的那样。

如果装饰器被调用一个或多个可选参数,如下所示:

 @decorator(optional_argument1='some value') def function .... 

然后装饰器被调用与值None的函数参数,所以装饰的函数返回,如预期。

格伦 – 我必须这样做。 我想我很高兴没有一种“魔术”的方式来做到这一点。 我讨厌那些。

所以,这里是我自己的答案(方法名称与上面不同,但是相同的概念):

 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...') 

怎么样

 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 

增强的通用装饰器装饰者代码

这是我的适应@ NickC的答案与以下增强function:

  • 可选的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 

现在这个老的线程已经回到顶端了,让我们引入一些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 

现在你的神奇装饰只是一条线!

 @magical_decorator def foo_register(...): # bla bla 

顺便说一句,这适用于任何装饰者。 它只是使@foo@foo()那样performance(尽可能地接近@foo()

用于装饰装饰器定义的通用装饰器,表示装饰的装饰器接受默认参数,如果没有明确给定,则设置默认参数。

 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): ... 

如果你想在多个装饰器上使用这个function,你可以使用装饰器的装饰器逃避代码样板:

 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

这是另一个变化,这是相当简洁的,不使用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(): ... 

如果可选参数是可调用的,这里还有一个解决scheme:

 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 

这是我为python3编写的解决scheme。 它具有与其他方法不同的方法,因为它定义了一个可调用的类而不是一个函数。

 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 

您仍然必须显式调用装饰器

 @flexible_decorator() def f(foo): print(foo) @flexible_decorator(arg="This is not default") def g(bar): print(bar)