[英]Python convert args to kwargs
我正在編寫一個裝飾器,它需要在調用它正在裝飾的函數之前調用其他函數。 裝飾函數可能有位置參數,但裝飾器將調用的函數只能接受關鍵字參數。 有沒有人有將位置參數轉換為關鍵字參數的方便方法?
我知道我可以獲得修飾函數的變量名稱列表:
>>> def a(one, two=2):
... pass
>>> a.func_code.co_varnames
('one', 'two')
但是我不知道如何分辨什么是位置傳遞的,什么是關鍵字。
我的裝飾器看起來像這樣:
class mydec(object):
def __init__(self, f, *args, **kwargs):
self.f = f
def __call__(self, *args, **kwargs):
hozer(**kwargs)
self.f(*args, **kwargs)
除了比較 kwargs 和 co_varnames 之外,還有什么方法可以向 kwargs 添加任何不在那里的東西,並希望得到最好的結果?
任何按位置傳遞的 arg 都將傳遞給 *args。 任何作為關鍵字傳遞的 arg 都將傳遞給 **kwargs。 如果您有位置參數值和名稱,則可以執行以下操作:
kwargs.update(dict(zip(myfunc.func_code.co_varnames, args)))
將它們全部轉換為關鍵字參數。
如果您使用 Python >= 2.7 inspect.getcallargs()
為您開箱即用。 您只需將裝飾函數作為第一個參數傳遞給它,然后將其余參數完全按照您計划調用它的方式傳遞。 例子:
>>> def f(p1, p2, k1=None, k2=None, **kwargs):
... pass
>>> from inspect import getcallargs
我打算做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'}}
如果您知道裝飾函數不會使用**kwargs
,那么該鍵將不會出現在 dict 中,您就完成了(我假設沒有*args
,因為這會打破一切都具有的要求一個名字)。 如果你確實有**kwargs
,就像我在這個例子中那樣,並且想要將它們與其余的命名參數一起包含,則需要多一行:
>>> call_args.update(call_args.pop('kwargs'))
>>> call_args
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'extra': 'kx1'}
更新:對於 Python >= 3.3,請參閱inspect.Signature.bind()
和相關的inspect.signature
函數以了解類似於(但比) inspect.getcallargs()
。
注意 - co_varnames 將包括局部變量和關鍵字。 這可能無關緊要,因為 zip 會截斷較短的序列,但如果您傳遞了錯誤數量的 args,則可能會導致混淆錯誤消息。
您可以使用func_code.co_varnames[:func_code.co_argcount]
避免這種情況,但最好使用檢查模塊。 IE:
import inspect
argnames, varargs, kwargs, defaults = inspect.getargspec(func)
您可能還想處理函數定義**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
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
鑒於此修飾函數,以下調用將返回適當的答案:
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
但是請注意,如果您開始嵌套裝飾器,事情會變得很奇怪,因為裝飾函數現在不再需要 a 和 b,而是需要 args 和 kwargs:
@decorator
@decorator
def thing(a,b):
return a+b
這里thing(1,2)
將打印{'args': 1, 'kwargs': 2}
和TypeError: thing() got an unexpected keyword argument 'args'
錯誤TypeError: thing() got an unexpected keyword argument 'args'
這是使用inspect.signature
(適用於Python 3.3+)解決此問題的較新方法。 我先舉一個可以自己運行/測試的例子,然后展示如何用它修改原始代碼。
這是一個測試函數,它總結了給它的任何 args/kwargs; 至少需要一個參數 ( a
) 並且有一個帶有默認值 ( b
) 的僅關鍵字參數,只是為了測試函數簽名的不同方面。
def silly_sum(a, *args, b=1, **kwargs):
return a + b + sum(args) + sum(kwargs.values())
現在讓我們為silly_sum
制作一個包裝器,它可以以與silly_sum
相同的方式silly_sum
(除了我們將要silly_sum
一個例外),但它只會將 kwargs 傳遞給包裝后的silly_sum
。
def wrapper(f):
sig = inspect.signature(f)
def wrapped(*args, **kwargs):
bound_args = sig.bind(*args, **kwargs)
bound_args.apply_defaults()
print(bound_args) # just for testing
all_kwargs = bound_args.arguments
assert len(all_kwargs.pop("args", [])) == 0
all_kwargs.update(all_kwargs.pop("kwargs"))
return f(**all_kwargs)
return wrapped
sig.bind
返回一個BoundArguments
對象,但這不會考慮默認值,除非您顯式調用apply_defaults
。 如果沒有給出*args
/ **kwargs
,這樣做也會為 args 生成一個空元組,並為 kwargs 生成一個空字典。
sum_wrapped = wrapper(silly_sum)
sum_wrapped(1, c=9, d=11)
# prints <BoundArguments (a=1, args=(), b=1, kwargs={'c': 9, 'd': 11})>
# returns 22
然后我們只需獲取參數字典並添加任何**kwargs
。使用此包裝器的例外是*args
不能傳遞給函數。 這是因為這些沒有名稱,所以我們不能將它們轉換為 kwargs。 如果將它們作為名為 args 的 kwarg 傳遞是可以接受的,那么可以改為這樣做。
以下是如何將其應用於原始代碼:
import inspect
class mydec(object):
def __init__(self, f, *args, **kwargs):
self.f = f
self._f_sig = inspect.signature(f)
def __call__(self, *args, **kwargs):
bound_args = self._f_sig.bind(*args, **kwargs)
bound_args.apply_defaults()
all_kwargs = bound_args.arguments
assert len(all_kwargs.pop("args"), []) == 0
all_kwargs.update(all_kwargs.pop("kwargs"))
hozer(**all_kwargs)
self.f(*args, **kwargs)
@mikenerone 提出的(最佳)解決方案是原始海報問題的解決方案:
import inspect
from functools import wraps
class mydec(object):
def __init__(self, f, *args, **kwargs):
self.f = f
def __call__(self, *args, **kwargs):
call_args = inspect.getcallargs(self.f, *args, **kwargs)
hozer(**call_args)
return self.f(*args, **kwargs)
def hozer(**kwargs):
print('hozer got kwds:', kwargs)
def myadd(i, j=0):
return i + j
o = mydec(myadd)
assert o(1,2) == 3
assert o(1) == 1
assert o(1, j=2) == 3
hozer got kwds: {'i': 1, 'j': 2}
hozer got kwds: {'i': 1, 'j': 0}
hozer got kwds: {'i': 1, 'j': 2}
這是一個通用裝飾器,它將 Python 函數的所有參數轉換並合並為kwargs
並僅使用這些 kwargs 調用包裝函數。
def call_via_kwargs(f):
@wraps(f)
def wrapper(*args, **kwds):
call_args = inspect.getcallargs(f, *args, **kwds)
print('kwargs:', call_args)
return f(**call_args)
return wrapper
@call_via_kwargs
def adder(i, j=0):
return i + j
assert adder(1) == 1
assert adder(i=1) == 1
assert adder(1, j=2) == 3
kwargs: {'i': 1, 'j': 0}
kwargs: {'i': 1, 'j': 0}
kwargs: {'i': 1, 'j': 2}
這些解決方案正確處理默認參數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.