Python将args转换为kwargs

jko*_*ker 18 python decorator

我正在编写一个装饰器,需要在调用它正在装饰的函数之前调用其他函数.修饰函数可能有位置参数,但装饰器将调用的函数只能接受关键字参数.有没有人有方便的方法将位置参数转换为关键字参数?

我知道我可以获得装饰函数的变量名列表:

>>> def a(one, two=2):
...    pass

>>> a.func_code.co_varnames
('one', 'two')
Run Code Online (Sandbox Code Playgroud)

但我无法弄清楚如何判断位置传递的内容以及关键字是什么.

我的装饰师看起来像这样:

class mydec(object):
    def __init__(self, f, *args, **kwargs):
        self.f = f

    def __call__(self, *args, **kwargs):
        hozer(**kwargs)
        self.f(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

除了比较kwargs和co_varnames之外,还有其他方法,在kwargs中添加任何不在那里的东西,并希望最好的吗?

Nad*_*mli 17

任何以位置方式传递的arg都将传递给*args.任何作为关键字传递的arg都将传递给**kwargs.如果你有位置args值和名称,那么你可以这样做:

kwargs.update(dict(zip(myfunc.func_code.co_varnames, args)))
Run Code Online (Sandbox Code Playgroud)

将它们全部转换为关键字args.

  • 事实上,`kwargs.update(zip(myfunc.func_code.co_varnames, args))` 就足够了。`dict.update` 也处理 2D 迭代。 (4认同)
  • 我不知道现在是否每个人都在使用“inspect”,但这在python3中不起作用。Copilot 给了我这个,它可以工作`kwargs.update(zip(fn.__code__.co_varnames[1:], args))`。顺便说一句,我的“fn.__code__.co_varnames”的第一个元素是“self” (2认同)

Cry*_*ops 12

如果您使用的是Python> = 2.7 inspect.getcallargs(),则可以为您提供开箱即用的功能.您只需将装饰函数作为第一个参数传递给它,然后将其余参数传递给您打算调用它.例:

>>> def f(p1, p2, k1=None, k2=None, **kwargs):
...     pass
>>> from inspect import getcallargs
Run Code Online (Sandbox Code Playgroud)

我打算这样做f('p1', 'p2', 'p3', k2='k2', extra='kx1')(注意k1在位置上被传递为p3),所以......

>>> call_args = getcallargs(f, 'p1', 'p2', 'p3', k2='k2', extra='kx1')
>>> call_args
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'kwargs': {'extra': 'kx1'}}
Run Code Online (Sandbox Code Playgroud)

如果您知道装饰函数将不会使用**kwargs,那么该键将不会出现在dict中,并且您已经完成了(我假设没有*args,因为这会破坏一切都有名称的要求).如果你确实**kwargs,就像我在这个例子中那样,并希望将它们包含在其余的命名参数中,那么还需要一行:

>>> call_args.update(call_args.pop('kwargs'))
>>> call_args
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'extra': 'kx1'}
Run Code Online (Sandbox Code Playgroud)

更新:用于Python> = 3.3,看到inspect.Signature.bind()和相关的inspect.signature功能为类似于(但比更强大的)功能inspect.getcallargs().


Bri*_*ian 6

注意 - co_varnames将包含局部变量和关键字.这可能无关紧要,因为zip会截断较短的序列,但如果传递错误的args数量,可能会导致混淆错误消息.

你可以避免这种情况func_code.co_varnames[:func_code.co_argcount],但更好的方法是使用inspect模块.即:

import inspect
argnames, varargs, kwargs, defaults = inspect.getargspec(func)
Run Code Online (Sandbox Code Playgroud)

您可能还想处理函数定义的情况**kwargs*args(即使只是在与装饰器一起使用时引发异常).如果设置了这些,则第二个和第三个结果getargspec将返回其变量名称,否则它们将为None.


Cha*_*iam 5

嗯,这可能是矫枉过正的.我为dectools包(在PyPi上)编写了它,所以你可以在那里获得更新.它返回字典,同时考虑位置,关键字和默认参数.包中有一个测试套件(test_dict_as_called.py):

 def _dict_as_called(function, args, kwargs):
""" return a dict of all the args and kwargs as the keywords they would
be received in a real function call.  It does not call function.
"""

names, args_name, kwargs_name, defaults = inspect.getargspec(function)

# assign basic args
params = {}
if args_name:
    basic_arg_count = len(names)
    params.update(zip(names[:], args))  # zip stops at shorter sequence
    params[args_name] = args[basic_arg_count:]
else:
    params.update(zip(names, args))    

# assign kwargs given
if kwargs_name:
    params[kwargs_name] = {}
    for kw, value in kwargs.iteritems():
        if kw in names:
            params[kw] = value
        else:
            params[kwargs_name][kw] = value
else:
    params.update(kwargs)

# assign defaults
if defaults:
    for pos, value in enumerate(defaults):
        if names[-len(defaults) + pos] not in params:
            params[names[-len(defaults) + pos]] = value

# check we did it correctly.  Each param and only params are set
assert set(params.iterkeys()) == (set(names)|set([args_name])|set([kwargs_name])
                                  )-set([None])

return params
Run Code Online (Sandbox Code Playgroud)


JnB*_*ymn 5

Nadia 的答案是正确的,但我觉得该答案的工作演示很有用。

def decorator(func):
    def wrapped_func(*args, **kwargs):
        kwargs.update(zip(func.__code__.co_varnames, args))
        print(kwargs)
        return func(**kwargs)
    return wrapped_func

@decorator
def thing(a,b):
    return a+b
Run Code Online (Sandbox Code Playgroud)

鉴于此修饰函数,以下调用将返回适当的答案:

thing(1, 2)  # prints {'a': 1, 'b': 2}  returns 3
thing(1, b=2)  # prints {'b': 2, 'a': 1}  returns 3
thing(a=1, b=2)  # prints {'a': 1, 'b': 2}  returns 3
Run Code Online (Sandbox Code Playgroud)

但是请注意,如果您开始嵌套装饰器,事情就会变得很奇怪,因为装饰函数现在不再需要 a 和 b,而是需要 args 和 kwargs:

@decorator
@decorator
def thing(a,b):
    return a+b
Run Code Online (Sandbox Code Playgroud)

这里thing(1,2)将打印{'args': 1, 'kwargs': 2}和错误TypeError: thing() got an unexpected keyword argument 'args'