【问题标题】:Dict merge in a dict comprehension字典理解中的字典合并
【发布时间】:2016-10-01 18:13:02
【问题描述】:

在 python 3.5 中,我们可以通过使用 double-splat unpacking 来合并 dicts

>>> d1 = {1: 'one', 2: 'two'}
>>> d2 = {3: 'three'}
>>> {**d1, **d2}
{1: 'one', 2: 'two', 3: 'three'}

酷。不过,它似乎不能推广到动态用例:

>>> ds = [d1, d2]
>>> {**d for d in ds}
SyntaxError: dict unpacking cannot be used in dict comprehension

相反,我们必须使用reduce(lambda x,y: {**x, **y}, ds, {}),这看起来更丑陋。为什么解析器不允许使用“一种明显的方法”,而该表达式似乎没有任何歧义?

【问题讨论】:

  • 您也不能在任何其他 *- 或 **- 解包上下文中执行此操作。即,你不能做some_function(*x for x in list_lists)。拆包星不是真正的运算符,不能出现在表达式中。
  • {k: v for d in [d1, d2] for k, v in d.items()} 将替代您的reduce(),尽管“丑陋”仍然。
  • 我相信另一种选择是dict(ChainMap(d2, d1)),我个人不喜欢它,因为到底谁知道ChainMap 是什么?
  • 其实,ChainMap(*ds) 本身似乎就足够了!很好,您应该将其添加为答案。
  • 叹息....{**d for d in ds} 会很不错的。

标签: python dictionary syntax-error python-3.5 dict-comprehension


【解决方案1】:

基于this solution 并由@ilgia-everilä 提到,但使其与Py2 兼容并且仍然避免中间结构。将其封装在一个函数中使其使用起来非常具有可读性。

def merge_dicts(*dicts, **extra):
    """
    >>> merge_dicts(dict(a=1, b=1), dict(b=2, c=2), dict(c=3, d=3), d=4, e=4)
    {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 4}
    """
    return dict((
        (k,v)
        for d in dicts
        for k,v in d.items()
    ), **extra)

【讨论】:

    【解决方案2】:

    您可以使用itertools.chainitertools.chain.from_iterable

    import itertools
    
    ds = [{'a': 1, 'b': 2}, {'c': 30, 'b': 40}]
    
    merged_d = dict(itertools.chain(*(d.items() for d in ds)))
    print(merged_d)  # {'a': 1, 'b': 40, 'c': 30}
    

    【讨论】:

      【解决方案3】:

      这不完全是您问题的答案,但我会考虑使用 ChainMap 作为一种惯用且优雅的方式来执行您的建议(在线合并字典):

      >>> from collections import ChainMap
      >>> d1 = {1: 'one', 2: 'two'}
      >>> d2 = {3: 'three'}
      >>> ds = [d1, d2]
      >>> dict(ChainMap(*ds))
      {1: 'one', 2: 'two', 3: 'three'}
      

      虽然这不是一个特别透明的解决方案,因为许多程序员可能不知道ChainMap 的工作原理。请注意(正如@AnttiHaapala 指出的那样)“使用第一个发现”,因此,根据您的意图,您可能需要先调用reversed,然后再将dicts 传递给ChainMap

      >>> d2 = {3: 'three', 2: 'LOL'}
      >>> ds = [d1, d2]
      >>> dict(ChainMap(*ds))
      {1: 'one', 2: 'two', 3: 'three'}
      
      >>> dict(ChainMap(*reversed(ds)))
      {1: 'one', 2: 'LOL', 3: 'three'}
      

      【讨论】:

        【解决方案4】:

        你可以定义这个函数:

        from collections import ChainMap
        def mergeDicts(l):
            return dict(ChainMap(*reversed(list(l))))
        

        然后你可以像这样使用它:

        >>> d1 = {1: 'one', 2: 'two'}
        >>> d2 = {3: 'three'}
        >>> ds = [d1, d2]
        >>> mergeDicts(ds)
        {1: 'one', 2: 'two', 3: 'three'}
        

        【讨论】:

          【解决方案5】:

          对我来说,显而易见的方法是:

          d_out = {}
          for d in ds:
              d_out.update(d)
          

          这很快并且可能非常高效。我不知道我可以为python开发人员说话,但我不知道你期望的版本更容易阅读。例如,由于缺少:,您的理解在我看来更像是集合理解。 FWIW,我认为没有任何技术原因(例如解析器歧义)他们无法添加这种形式的理解解包。

          显然,these forms were proposed,但没有足够普遍的支持来保证实施它们(目前)。

          【讨论】:

          • 通过使其成为表达式而不是语句,增加了函数式样式的可能用例。如果 for 循环总是更好,则根本没有理由添加合并表达式 - 我的问题更多是关于为什么故意将其限制为已知的预定数量的操作数?
          • @wim -- 如果您真的想以函数式方法执行此操作,只需将其包装在一个函数中即可:-)。正如相关 PEP 中所讨论的,有意限制它的原因是社区中对于使用什么语法没有足够强烈的共识。也许将来某个时候会重新审视它,但现在,它被省略了,以便每个人都同意的部分可以安排实施。
          • 有趣的是,理解语法是 part of the implementation 并被主动删除。
          • @norok2 我仍然看不出任何令人信服的理由来说明它被删除的原因(?)
          猜你喜欢
          • 2016-03-14
          • 2021-07-24
          • 1970-01-01
          • 1970-01-01
          • 2019-05-16
          • 1970-01-01
          • 2017-08-03
          • 2011-03-14
          • 1970-01-01
          相关资源
          最近更新 更多