使用可选参数创建装饰器

oro*_*aki 56 python decorator wrapper

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
Run Code Online (Sandbox Code Playgroud)

例如:下面的装饰my_functionfoo_register的,而不是它曾经做对decorator.

@foo_register
def my_function():
    print('hi...')
Run Code Online (Sandbox Code Playgroud)

示例:以下按预期工作.

@foo_register('say_hi')
def my_function():
    print('hi...')
Run Code Online (Sandbox Code Playgroud)

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

PS我已经知道我可以要求装饰器被调用,但这不是一个"解决方案".我希望API感觉自然.我的妻子喜欢装饰,我不想破坏它.

ben*_*nte 51

我知道这样做最干净的方法如下:

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
Run Code Online (Sandbox Code Playgroud)

说明

调用装饰器时没有像这样的可选参数:

@decorator
def function ...
Run Code Online (Sandbox Code Playgroud)

函数作为第一个参数传递,decorate按预期返回修饰函数.

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

@decorator(optional_argument1='some value')
def function ....
Run Code Online (Sandbox Code Playgroud)

然后使用值为None的函数参数调用decorator,因此返回一个装饰函数,如预期的那样.

Python 3

请注意,上面的装饰器签名可以使用Python 3特定的*,语法进行改进,以强制安全使用关键字参数.只需将最外层函数的签名替换为:

def decorator(original_function=None, *, optional_argument1=None, optional_argument2=None, ...):
Run Code Online (Sandbox Code Playgroud)

  • 如果一个人接受关键字args的要求而不是位置args(我这样做),这是迄今为止最好的答案. (6认同)
  • 我同意这应该是答案。干净整洁。 (2认同)
  • 您可以通过在函数定义中添加`*`来强制仅使用关键字args。例如,def装饰器(original_function = None,*,argument1 = None,argument2 = None,...): (2认同)
  • @ForeverWintr我已经将您对Python 3的精明建议纳入了原始答案。我们甚至可以通过[聪明的Python 2.7 hack](/sf/answers/3099907401/)进一步扩展它,但是...很难理解这一点。Python 2.7正当之无愧地风靡一时。 (2认同)

Nic*_*ole 38

通过这里和其他地方的答案以及一堆反复试验的帮助,我发现实际上有一种更容易和通用的方法使装饰器采用可选参数.它确实检查了它被调用的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
Run Code Online (Sandbox Code Playgroud)

用法

使用它就像:

  1. 像普通人一样创建装饰器.
  2. 在第一个目标函数参数之后,添加可选参数.
  3. 用装饰装饰 optional_arg_decorator

例:

@optional_arg_decorator
def example_decorator_with_args(fn, optional_arg = 'Default Value'):
    ...
    return fn
Run Code Online (Sandbox Code Playgroud)

测试用例

对于您的用例:

因此,对于你的情况,以节省属性与传入的方法名称的功能或__name__如果:

@optional_arg_decorator
def register_method(fn, method_name = None):
    fn.gw_method = method_name or fn.__name__
    return fn
Run Code Online (Sandbox Code Playgroud)

添加装饰方法

现在你有一个可以使用或不使用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 :)'
Run Code Online (Sandbox Code Playgroud)

  • 如果您的装饰器收到一个类作为参数,则此解决方案将无法正常工作,因为在这两种情况下`callable(args [0])`都会返回`True`。装饰函数时和调用函数时。 (2认同)

oro*_*aki 28

格伦 - 我必须这样做.我想我很高兴没有"神奇"的方法来做到这一点.我讨厌那些.

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

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
Run Code Online (Sandbox Code Playgroud)

示例用法(两个版本的工作方式相同):

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

@register_gw_method('say_hi')
def my_function():
    print('hi...')
Run Code Online (Sandbox Code Playgroud)

  • @orokusaki:我曾经发现它们真的令人困惑,但是习惯于传递和返回函数的想法,主要是通过做很多事情,这有助于使它们只是让人感到困惑. (3认同)
  • FWIW,装饰器的整个概念非常神奇。不像幸运符那样神奇,但仍然神奇。我认为为了让妻子“真正”高兴,在这种情况下应该有一个装饰器装饰器,使装饰器在没有调用时使用默认参数。当然,如果它实际上传递了一个可调用函数,那么这是行不通的。 (2认同)
  • @intuited - 当我编写一个装饰器时,我觉得我正在从"Inception"观看面包车/桥梁场景.我同意他们维护起来并不是很有趣,但他们肯定有助于使库更加用户友好(即丑陋的实现细节). (2认同)

小智 11

怎么样

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
Run Code Online (Sandbox Code Playgroud)

  • stackoverflow上没有死线程这样的东西.如果对于原始提问者来说,最好的答案来得太晚了:运气不好.但是对于其他人后来通过搜索发现这一点,如果您的答案很有价值,那么回答它总是很有价值的. (14认同)

Ryn*_*ett 7

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

以下是我对@ NickC答案的改编,其中包括以下增强功能:

  • 可选的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
Run Code Online (Sandbox Code Playgroud)