当Python在签名中遇到
**kwargs
结构时,它期望
kwargs
是一个"映射",这意味着两件事:(1)能够调用
kwargs.keys()
获得映射包含的键的可迭代对象;(2)
kwargs.__getitem__(key)
可以为由
keys()
返回的每个键调用,并且生成的值是与该键相关联的所需值。
在内部,Python会将任何映射"转换"为字典,就像这样:
**kwargs -> {key:kwargs[key] for key in kwargs.keys()}
如果您认为
kwargs
已经是
dict
,那么这看起来可能有点傻 - 因为没有理由从传入的
dict
构造一个完全等效的
dict
。但是当
kwargs
不一定是
dict
时,将其内容带入合适的默认数据结构就很重要了,以便执行参数解包的代码始终知道自己正在处理什么。
所以,您可以干预特定数据类型的解包方式,但由于为了实现统一的参数解包协议而转换为
dict
,所以无法保证解包参数的顺序(因为
dict
不会跟踪元素添加的顺序)。如果Python语言将
**kwargs
转换为
OrderedDict
而不是
dict
(这意味着关键字参数的键的顺序将是它们被遍历的顺序),那么通过传递
OrderedDict
或一些其他数据结构使得
keys()
保持某种排序,您可以期望某些参数的顺序排列。只是因为实现中选择了
dict
作为标准,而不是其他类型的映射结构,所以出现了这种怪癖。
以下是一个可以“解包”的类的愚蠢示例,但它始终将所有解包的值视为42(尽管它们并不真正如此)。
class MyOrderedDict(object):
def __init__(self, odict):
self._odict = odict
def __repr__(self):
return self._odict.__repr__()
def __getitem__(self, item):
return 42
def __setitem__(self, item, value):
self._odict[item] = value
def keys(self):
return self._odict.keys()
然后定义一个函数来打印解包后的内容:
def foo(**kwargs):
for k, v in kwargs.iteritems():
print k, v
创建一个变量并尝试运行它:
In [257]: import collections; od = collections.OrderedDict()
In [258]: od['a'] = 1; od['b'] = 2; od['c'] = 3;
In [259]: md = MyOrderedDict(od)
In [260]: print md
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
In [261]: md.keys()
Out[261]: ['a', 'b', 'c']
In [262]: foo(**md)
a 42
c 42
b 42
这里的定制键值对传递(在这里,始终返回 42)是您在 Python 中调整如何使用 **kwargs 的能力的极限。
更多关于如何调整 *args 获取解包的灵活性。有关更多信息,请参见此问题:Does argument unpacking use iteration or item-getting?。