我正在编写一个装饰器,需要在调用它正在装饰的函数之前调用其他函数.修饰函数可能有位置参数,但装饰器将调用的函数只能接受关键字参数.有没有人有方便的方法将位置参数转换为关键字参数?
我知道我可以获得装饰函数的变量名列表:
>>> 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.
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().
注意 - 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.
嗯,这可能是矫枉过正的.我为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)
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'