当 Python 在签名中遇到 **kwargs 构造时,它期望 kwargs 是一个“映射”,这意味着两件事:(1)能够调用 kwargs.keys() 以获取密钥的可迭代包含在映射中,以及 (2) 可以为 keys() 返回的迭代中的每个键调用 kwargs.__getitem__(key),并且结果值是与该键关联的所需值。
在内部,Python 会将映射的任何内容“转换”为字典,有点像这样:
**kwargs -> {key:kwargs[key] for key in kwargs.keys()}
如果你认为kwargs 已经是dict,这看起来有点傻——而且确实如此,因为没有理由从传入的dict 构造一个完全等效的dict。
但是当kwargs 不一定是dict 时,重要的是要将其内容放入合适的默认数据结构中,以便执行参数解包的代码始终知道它在处理什么。
所以,您可以弄乱某种数据类型的解包方式,但是由于为了一致的 arg-unpacking-protocol 转换为 dict,它恰好发生了无法保证参数解包的顺序(因为dict 不跟踪元素添加的顺序)。如果 Python 语言将 **kwargs 降低为 OrderedDict 而不是 dict(这意味着键作为关键字 args 的顺序将是它们被遍历的顺序),那么通过传递 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)是您修改**kwargs 在 Python 中的工作方式的能力范围。
在修改 *args 的解包方式方面有更大的灵活性。有关更多信息,请参阅此问题:Does argument unpacking use iteration or item-getting?>。