【问题标题】:How to write a decorator to set the first argument?如何编写装饰器来设置第一个参数?
【发布时间】:2015-10-22 03:26:54
【问题描述】:

我编写了一个名为apply_first 的装饰器,它设置了装饰函数的第一个参数。不幸的是,这个装饰器的签名错误。有什么办法吗?我通常使用decorator来保留签名,但这次我想更改它。

def apply_first(x):
    def decorate(f):
        def g(*args):
            return f(*((x,) + args))
        return g
    return decorate

@apply_first(5)
def add(x,y):
    return x+y

print(add(3))
# prints 8

【问题讨论】:

  • 你是什么意思“错误的签名”?您期望生成的签名是什么(如果不是*args,为什么不)?
  • 我没想到它与*args 有任何不同,但我希望它是(y)。正如我在帖子中提到的,decorator 模块通常会为您处理这个问题。
  • 啊,恐怕你做不到。请注意,您所写的或多或少是functools.partial,而functools.wraps 保留了从3.4 开始的签名。
  • 或者也许你可以,虽然它并不整洁或容易 - 参见例如stackoverflow.com/a/1409496/3001761
  • 也许可以在 python 3.4 中使用inspect.Signature.bind 来做到这一点。

标签: python decorator python-decorators


【解决方案1】:

更好的解决方案:

我最终编写了一个装饰器来修复装饰器,这些装饰器会装饰掉第一个参数

import decorator
import sys

def wrapper_string(pre_decor):
    argspec = decorator.getfullargspec(pre_decor)
    if len(argspec.args) == 0:
        raise TypeError("Couldn't find a first argument to decorate away.")
    allargs = list(argspec.args[1:])
    allshortargs = list(argspec.args[1:])

    if argspec.varargs:
        allargs.append('*' + argspec.varargs)
        allshortargs.append('*' + argspec.varargs)
    elif argspec.kwonlyargs:
        allargs.append('*')  # single star syntax
    for a in argspec.kwonlyargs:
        allargs.append('%s=None' % a)
        allshortargs.append('%s=%s' % (a, a))
    if argspec.varkw:
        allargs.append('**' + argspec.varkw)
        allshortargs.append('**' + argspec.varkw)

    head = "def " + pre_decor.__name__ + "(" + ', '.join(allargs) + "):"
    body = "    return _decorator_(_func_)("+ ', '.join(allshortargs) +")"
    return head + "\n" + body

def update_signature(pre_decor, post_decor, **kw):
    "Update the signature of post_decor with the data in pre_decor"
    post_decor.__name__ = pre_decor.__name__
    post_decor.__doc__ = getattr(pre_decor, '__doc__', None)
    post_decor.__dict__ = getattr(pre_decor, '__dict__', {})
    argspec = decorator.getfullargspec(pre_decor)
    if len(argspec.args) == len(argspec.defaults):
        pos = 1
    else:
        pos = 0
    post_decor.__defaults__ = getattr(pre_decor, '__defaults__', ())[pos:]
    post_decor.__kwdefaults__ = getattr(pre_decor, '__kwdefaults__', None)
    #post_decor.__annotations__ = getattr(pre_decor, '__annotations__', None)
    try:
        frame = sys._getframe(3)
    except AttributeError:  # for IronPython and similar implementations
        callermodule = '?'
    else:
        callermodule = frame.f_globals.get('__name__', '?')
    post_decor.__module__ = getattr(pre_decor, '__module__', callermodule)
    post_decor.__dict__.update(kw)

@decorator.decorator
def decorate_first_arg(dec, pre_decor):
    evaldict = pre_decor.__globals__.copy()
    evaldict['_decorator_'] = dec
    evaldict['_func_'] = pre_decor
    wrapper = compile(wrapper_string(pre_decor), "<string>", "single")
    exec(wrapper, evaldict)
    post_decor = evaldict[pre_decor.__name__]
    update(pre_decor, post_decor)
    return post_decor

几乎所有代码都是从 Michele Simionato 的 decorator 模块 (GitHub) 复制和适当修改的。

有了这个,上面的例子可以固定为:

@decorate_first_arg
def apply_first(x):
    def decorate(f):
        def g(*args):
            return f(*((x,) + args))
        return g
    return decorate

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-17
    • 2019-02-11
    • 2015-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-23
    相关资源
    最近更新 更多