【问题标题】:Using an OrderedDict in **kwargs在 **kwargs 中使用 OrderedDict
【发布时间】:2014-11-05 01:10:48
【问题描述】:

是否可以将 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 作为常规参数传递

【问题讨论】:

  • 感谢您的详细回答。我最终做的是允许一个可选的第一个参数(一个有序的字典),如果提供的话,它将优先于 **kwargs 使用。很棒的信息,不过

标签: python parameter-passing keyword-argument


【解决方案1】:

从 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,不管关键字来自哪里——显式关键字参数按照它们出现的顺序,然后是**example 的所有项目按照它们在example 中出现的顺序排列(如果exampledict,这可能是任意的,但如果它是OrderedDict,也可能是有意义的)。

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


顺便提一下,如果这个可能的,它几乎肯定会在OrderedDict 本身的构造函数中使用。但是,如果您尝试这样做,您所做的只是将一些任意订单冻结为永久订单:

>>> 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 并将项目作为元组传递,但这通常更难看。

从 PEP 链接的线程中可能还有其他一些解决方法,但实际上,没有一个会比这更好。 (除了……IIRC,李浩毅根据他的MacroPy 提出了一个解决方案来传递订单保留关键字参数,但我不记得细节了。如果您愿意使用 MacroPy 和编写的代码读起来不像 Python,但这并不总是合适的……)

【讨论】:

  • +1 了解详情,以及我最喜欢的代码审查回复So, what you're asking for doesn't really make sense.。谢谢你。
  • 感谢您提供的良好链接。 pep 很有趣,因为它说 **kwargs 保证是一个插入顺序保留映射,但是下一个 pep 说 dict 的新实现是顺序保留,但这应该被视为一个实现特性。在 3.7 上,当我检查 **kwargs 值的类型时,它是一个字典。所以我猜 kwargs pep 的当前实现是懒惰地依赖于当前的 dict 实现来保持顺序。
  • @MB。从 Python 3.7 开始,保持顺序的 dict 已成为一种语言特性,而不是实现细节。这是一个有争议的变化,它确实让我感到惊讶,但它确实发生了。
【解决方案2】:

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

如其他答案所述,之前不可能这样做。

【讨论】:

    【解决方案3】:

    当 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 的顺序将是它们被遍历的顺序),那么通过传递 OrderedDictkeys() 尊重某种排序的其他数据结构,您可以期望参数有一定的排序。选择 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?>。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-12
      • 2023-04-01
      • 1970-01-01
      • 2018-08-15
      • 1970-01-01
      相关资源
      最近更新 更多