在** kwargs中使用OrderedDict

时间:2014-11-05 01:10:49

标签: python parameter-passing kwargs

是否可以将OrderedDict实例传递给使用**kwargs语法并保留排序的函数?

我想做的是:

def I_crave_order(**kwargs):
    for k, v in kwargs.items():
        print k, v

example = OrderedDict([('first', 1), ('second', 2), ('third', -1)])

I_crave_order(**example)
>> first 1
>> second 2
>> third -1

然而实际结果是:

>> second 2
>> third -1
>> first 1

即典型的随机字典排序。

我有其他用途,明确设置顺序是好的,所以我想保留**kwargs而不只是将OrderedDict作为常规参数传递

3 个答案:

答案 0 :(得分:28)

从Python 3.6开始,the keyword argument order is preserved。在3.6之前,由于OrderedDict变为dict,因此无法实现。


首先要注意的是,您在**example中传递的值不会自动成为**kwargs中的值。考虑这种情况,其中kwargs只包含example的一部分:

def f(a, **kwargs):
    pass
example = {'a': 1, 'b': 2}
f(**example)

或者这种情况下,它的值将超过示例中的值:

example = {'b': 2}
f(a=1, c=3, **example)

甚至根本没有重叠:

example = {'a': 1}
f(b=2, **example)

所以,你所要求的并没有多大意义。

但是,如果有某种方式可以指定您想要一个有序的**kwargs,那么无论哪里关键字来自 - 显式关键字args按照它们出现的顺序,然后是**exampleexample中出现的顺序排列的所有项目(如果exampledict,这可能是任意的,但如果它OrderedDict也可能有意义是OrderedDict

定义所有繁琐的细节,并保持性能可接受,结果证明比听起来更难。有关该想法的一些讨论,请参阅PEP 468和链接的线程。这次似乎已经停滞不前了,但如果有人拿起它并支持它(并为人们编写参考实现 - 这取决于可从C API访问的OrderedDict,但希望是在3.5+),我怀疑它最终会进入语言。


顺便说一句,请注意,如果 可能,它几乎肯定会在>>> d = OrderedDict(a=1, b=2, c=3) OrderedDict([('a', 1), ('c', 3), ('b', 2)]) 本身的构造函数中使用。但是,如果你尝试这样做,你所做的就是冻结一些任意顺序作为永久订单:

example

同时,你有什么选择?

嗯,显而易见的选择就是将def f(example): pass example = OrderedDict([('a', 1), ('b', 2)]) f(example) 作为普通参数传递而不是解压缩它:

*args

或者,当然,你可以使用{{1}}并将这些项目作为元组传递,但这通常是更加丑陋。

从PEP链接的线程中可能还有其他一些解决方法,但实际上,它们都不会比这更好。 (除了...... IIRC,李浩义提出了一个基于他的MacroPy的解决方案来传递保留关键字的参数,但我不记得细节。如果你愿意使用MacroPy解决方案一般都很棒MacroPy并编写不像Python那样读取的代码,但这并不总是合适的......)

答案 1 :(得分:16)

This is now the default in python 3.6

Python 3.6.0a4+ (default:d43f819caea7, Sep  8 2016, 13:05:34)
>>> def func(**kw): print(kw.keys())
...
>>> func(a=1, b=2, c=3, d=4, e=5)
dict_keys(['a', 'b', 'c', 'd', 'e'])   # expected order

如其他答案所述,之前无法做到这一点。

答案 2 :(得分:2)

当Python在签名中遇到**kwargs构造时,它期望kwargs成为"映射",这意味着两件事:(1)能够调用{ {1}}获取映射包含的密钥的可迭代次数,以及(2)可以为kwargs.keys()返回的可迭代中的每个密钥调用kwargs.__getitem__(key),并且得到的值是所需的一个与该密钥相关联。

在内部,Python将"转换"无论映射到字典中,都是这样的:

keys()

如果您认为**kwargs -> {key:kwargs[key] for key in kwargs.keys()} 已经是kwargs,这看起来有点傻 - 而且它会是,因为没有理由从一个dict构建完全等效的dict传入。

但是当kwargs不一定是dict时,重要的是将其内容放入合适的默认数据结构中,以便执行参数解包的代码始终知道它是什么&#39与...合作。

因此,可以混淆某种数据类型被解压缩的方式,但是由于为了一致的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

这种自定义的键值对传递(这里,dumbly总是返回42)是你能够修改**kwargs在Python中的工作方式的能力。

稍微更灵活地修改*args如何解包。有关详细信息,请参阅此问题:< Does argument unpacking use iteration or item-getting?>。