【问题标题】:Default Argument decorator python默认参数装饰器python
【发布时间】:2017-08-25 02:06:40
【问题描述】:

Python 3.6

我正在尝试创建一个装饰器,它会自动将参数的字符串分配为默认值。

如:

def example(one='one', two='two', three='three'):
    pass

相当于:

@DefaultArguments
def example(one, two, three):
    pass

这是我的尝试(行不通……还没有……)DefaultArguments

from inspect import Parameter, Signature, signature


class DefaultArguments(object):

    @staticmethod
    def default_signature(signature):
        def default(param):
            if param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
                return param.replace(default=param.name)
            else:
                return param
        return Signature([default(param) for param in signature.parameters.values()])

    def __init__(self, func):
        self.func = func
        self.sig = self.default_signature(signature(func))

    def __call__(self, *args, **kwargs):
        arguments = self.sig.bind(*args, **kwargs)
        return self.func(arguments)

静态方法default_signature 为函数创建所需的签名,但我很难将新签名分配给函数。我正在尝试使用签名。bind 我已经阅读了docs,但我遗漏了一些东西。

编辑

结合 Ashwini Chaudhary 的回答:

from inspect import Parameter, Signature, signature

class DefaultArguments(object):

    @staticmethod
    def default_signature(signature):
        def default(param):
            if param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
                return param.replace(default=param.name)
            else:
                return param
        return Signature([default(param) for param in signature.parameters.values()])

    def __init__(self, func):
        self.func = func
        self.sig = self.default_signature(signature(func))
        print(self.sig)

    def __call__(self, *args, **kwargs):
        ba = self.sig.bind(*args, **kwargs)
        ba.apply_defaults()
        return self.func(*ba.args, **ba.kwargs)

【问题讨论】:

标签: python decorator signature


【解决方案1】:

将参数和关键字与签名绑定后,您需要在 BoundArguments 实例上调用 apply_defaults 以设置缺少参数的默认值。

还将使用BoundArgumentsargskwargs 属性调用函数。

def __call__(self, *args, **kwargs):
    ba = self.sig.bind(*args, **kwargs)
    ba.apply_defaults()
    return self.func(*ba.args, **ba.kwargs)

演示:

>>> @DefaultArguments
... def example(one, two, three):
...         print(one, two, three)
...

>>> example()
one two three
>>> example('spam')
spam two three
>>> example(one='spam', three='eggs')
spam two eggs

您的代码的功能版本,它也更新了装饰函数的签名:

from functools import wraps
from inspect import Parameter, Signature, signature


def default_arguments(func):

    def default(param):
        if param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
            param = param.replace(default=param.name)
        return param

    sig = Signature([default(param) for param in signature(func).parameters.values()])

    @wraps(func)
    def wrapper(*args, **kwargs):
        ba = sig.bind(*args, **kwargs)
        ba.apply_defaults()
        return func(*ba.args, **ba.kwargs)

    wrapper.__signature__ = sig
    return wrapper

演示:

>>> from inspect import getfullargspec    
>>> @default_arguments
... def example(one, two, three):
...         print(one, two, three)
...

>>> getfullargspec(example)
FullArgSpec(
    args=['one', 'two', 'three'],
    varargs=None,
    varkw=None,
    defaults=('one', 'two', 'three'),
    kwonlyargs=[], kwonlydefaults=None, annotations={}
)

【讨论】:

  • 感谢功能版。很酷,我会用它。
【解决方案2】:

这似乎有效:

import inspect

def default_args(func):
    argspec = inspect.getfullargspec(func)

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        unpassed_positional_args = argspec.args[len(args):]
        kwargs.update((a, a) for a in unpassed_positional_args if a not in kwargs)
        return func(*args, **kwargs)

    return wrapper

它依赖于你可以在 python 中通过关键字传递位置参数的事实。例如如果你有一个功能:

def foo(a, b):
    ...

您完全有权将其称为:

foo(b=1, a=2)

我的解决方案会计算出您传递了多少位置参数,并使用它来确定哪些位置参数没有传递。然后,我将这些位置参数名称添加到 kwargs 字典中。

这里很酷的是,如果有人在 python2.x 中需要这个,他们只需要将 getfullargspec 更改为 getargspec 就可以了。


关于速度的说明:

将我的解决方案与 Ashwini 的出色解释进行比较表明,简单的装饰器比处理 Signature 对象快大约 10 倍:

@default_args
def foo(a, b, c):
    pass

@DefaultArguments
def bar(a, b, c):
    pass

@default_arguments
def qux(a, b, c):
    pass

import timeit
print(timeit.timeit('foo()', 'from __main__ import foo'))  # 1.72s
print(timeit.timeit('bar()', 'from __main__ import bar'))  # 17.4s
print(timeit.timeit('qux()', 'from __main__ import qux'))  # 17.6

他的解决方案实际上更新了函数的__signature__(这真的很好)。原则上,您可以采用Signature 创建逻辑并将其添加到我的解决方案中以更新__signature__,但保留argspec 样式逻辑以进行实际计算...

【讨论】:

  • @JamesSchinner -- Ashwini 的测试用例提醒我注意一个小错误 -- 基本上我需要确保我没有添加默认值,如果它们已经存在于 kwargs 中。
  • 这个超级干净。如果我没有对我的代码投入感情的话。我会用这个。我选择了 Ashwini's,因为它解释了我之前的理解有什么问题。
  • 是的——我对此没意见。事实上,我同意你给他打勾——他是你问题的一个很好的答案。在很多方面,它都比我的要好,因为它更多地解释了您所询问的 API。 (我认为这很好地证明了为什么 StackOverflow 允许 multiple 答案:-)。
  • 似乎“绿色勾号”只能应用于一个。
猜你喜欢
  • 2016-09-27
  • 2015-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-10
  • 2011-11-21
  • 1970-01-01
  • 2015-04-06
相关资源
最近更新 更多