回答

收藏

如何在一个表达式中合并两个字典(取字典的并集)?

技术问答 技术问答 306 人阅读 | 0 人回复 | 2023-09-11

我有两个 Python 字典,我想写一个单一的表达式来返回这两个字典,合并(即合并)。update()方法将是我需要的,如果它返回其结果而不是修改字典。
- o- `/ L* Z1 v7 I5 W8 v7 V

    : I0 M% K1 l2 K8 U4 Q0 x' `: X
  • >>> x = {'a': 1,'b': 2}>>> y = {'b': 10,'c': 11}>>> z = x.update(y)>>> print(z)None>>> x{'a': 1,'b': 10,'c code]我怎样才能得到最终合并的字典?z,而不是x?, [3 o& w3 V  i8 c
  • (应该特别清楚最后一次胜利的冲突处理dict.update()也是我在寻找的。
    $ z) C* d/ b* I. o
  •                                                                
    : k4 `! L$ T$ ^4 V: A
  •     解决方案:                                                               % S2 R5 s/ }# @: S$ v* C. \
  •                                                                 如何在单个表达式中合并两个 Python 字典?对于字典xand y,z成为浅层合并的字典,其中值y取代 值x。
    1 ^' t) V2 e, @3 g1 `7 @6 Y
  • & v. {0 w* T9 J/ L4 O
  • 在Python 3.9.0或更高(释放2020年10月17日)EP-在这里讨论、实现、提供最简单的方法:py  z = x | y          # NOTE: 3.9  ONLY
    5 A+ h7 W& q: p
  • , G9 V( [% c* C( n& n  ]1 f- a
  • 在 Python 3.5 或更高版本:py  z = {**x,**y}
      b$ d4 ?/ N/ i& ^

  • $ u! ~" y- l7 M8 j5 S& {) o3 A) }' x
  • 在 Python 2(或 3.在4 或更低版本中编写函数:py  def merge_two_dicts(x,y):      z = x.copy()   # start with keys and values of x      z.update(y)    # modifies z with keys and values of y      return z
    2 x; O- o5 f4 @5 B7 f7 E) w4 ^" l
  • 现在:
    ! k- l- F; H7 S; h
  • py  z = merge_two_dicts(x,y)- H9 W5 S1 |5 r2 C/ p* o$ x# J
  • 解释假设你有两个字典,你想在不改变原始字典的情况下将它们合并到一个新字典中:+ l7 E  O9 Z3 `( t2 I3 d8 F
  • [code]x = {'a': 1,'b': 2}y = {'b': 3,'ccode]期待的结果是得到一个新的字典 ( z),第二值,第二字典值覆盖第一字典值。5 E0 }5 c0 b7 D( B+ a' l
  • [code]>>> z{'a': 1,'b': 3,'ccode]在PEP 448 Python 3.在5 中使用的新语法是[code]z = {**x,**y}$ E2 J2 _' x. Y5 t
它确实是一种单一的表达。! _! H* H. x# w6 ^
请注意,我们也可以与文本符号合并:! R% a$ ]2 j: K. R0 q, S
    z = {**x,'foo': 1,'bar': 2,**y}( {, c$ D7 b7 Y' I* @
现在:* Z5 I2 d# A8 I% R
    6 X  t- j7 @# T
  • >>> z{'a': 1,'b': 3,'foo': 1,'bar': 2,'ccode]它现在显示为 3.5 PEP 478的发布时间表已经实现,现在已经进入Python 3.在5 新功能文档中。1 [: R; W( s4 F+ e5 [5 n
  • 然而,许多组织仍在使用 Python 2,您可能希望以后以兼容的方式执行此操作。Python 2 和 Python 3.0-3.4 中可用的经典 Pythonic 方法是将其作为两个过程执行:[code]z = x.copy()z.update(y) # which returns None since it mutates z
    5 K- q/ ?9 J  s6 B
在这两种方法中,y排名第二,其值将被替换x的值,因此b我们的最终结果将指向3。
) S9 G9 q! d# c/ O$ g还没有在 Python 3.5 上,但我想要一个单一的表达式假如你还没用 Python 3.5 或者需要编写后兼容的代码,你想要在单个表达式中使用,那么性能最好的方法是将其放入函数中:
; x) _: Z! @, }- V7 s' ^" I
    def merge_two_dicts(x,y):    """Given two dictionaries,merge them into a new dict as a shallow copy."""    z = x.copy()    z.update(y)    return z
    3 V5 i2 y  [) C" c: ~4 z" ^3 B" D' E
然后你有一个表达式:
' x, C" V9 l* v: ]
    z = merge_two_dicts(x,y)
    / M4 S% C7 ~, }# D
您还可以创建一个函数来合并任何数量的字典,从零到非常大的数字:4 z: I- H+ x8 n. U% ?% a3 ?
    def merge_dicts(*dict_args):    """    Given any number of dictionaries,shallow copy and merge into a new dict,   precedence goes to key-value pairs in latter dictionaries.    """    result = {}    for dictionary in dict_args:        result.update(dictionary)    return result& z) F  p% O4 e7 \% H1 e
该函数适用于所有字典 Python 2 和 3a到g:
3 v% K0 A) a& W( h; A9 |/ U
    z = merge_dicts(a,b,c,d,e,f,g)
    # }9 C, n, \8 }
和键值对g优先于字典ato f,依此类推。. [2 O" h! I; U- v- h/ d% Q
批评其他答案不要使用你在以前的答案中看到的内容:+ V5 B; W7 M" a: d' b1 h* }
    z = dict(x.items()   y.items())
    6 Z3 v8 }  u! V2 H7 D- C
在 Python 2 在内存中,你是每个 dict 创建两个列表,在内存中创建第三个列表,其长度等于前两个列表的长度,然后丢弃所有三个列表创建 dict。在 Python 3 中,这将失败,因为你要两个dict_items将对象添加在一起,而不是两个列表 -
# i; D. q' K6 p5 ]& ~# }( h$ g3 `
    >>> c = dict(a.items()   b.items())Traceback (most recent call last):  File "",line 1,in TypeError: unsupported operand type(s) for  : 'dict_items' and 'dict_items'
    ( a1 X* s3 \2 F5 l! @# X- F
例如,您必须创建它们的显式列表z = dict(list(x.items()  list(y.items())). 这是浪费资源和计算能力。
# U6 F- U+ a- B2 x. g. N类似地,当值是不可散列的对象(如列表),items()在 Python 3(viewitems()在 Python 2.7 中)并集也会失败。即使你的值可以散列,因为集合在语义上是无序的,所以行为在优先级上是不定义的。所以不要这样做:
- w  l6 ]5 Y% @, Z( e: }
    >>> c = dict(a.items() | b.items())- [2 p% e: Q, J& z" H
这个例子展示了当值不能散列时会发生什么:+ S/ S* O( o8 [0 s: m# w
    >>> x = {'a>>> y = {'b>>> dict(x.items() | y.items())Traceback (most recent call last):  File "",line 1,in TypeError: unhashable type: 'list'
    , J. C) s8 u. j
这是一个y应该有优先示例,但是x  由于集合的任意顺序而保留from 的值:6 e6 I7 v0 M& K$ t- H: l4 `7 h

    & q& n1 N2 S" ^2 M# {6 @
  • >>> x = {'a': 2}>>> y = {'a': 1}>>> dict(x.items() | y.items()){'a code]另一个你不应该用的黑客:[code]z = dict(x,**y)) U; ]% O/ ?5 b2 N; w$ \) r1 ?- e8 t
这使用dict构造函数非常快,内存效率高(甚至比我们的两个步骤多一点),但除非你确切知道这里发生了什么(也就是说,第二个 dict 作为关键字参数传递给 dict 构造函数),很难阅读,不是预期用法,所以不是 Pythonic。: ~* v# y" _0 A8 i8 t% w
这是在 django修复用法示例。- ]2 J/ K1 N! V& `0 z# u
字典旨在使用可散列键(例如frozensets 或元组),但是当键不是字符串时此方法在 Python 3 失败。% M0 m& \. r. p
    >>> c = dict(a,**b)Traceback (most recent call last):  File "",line 1,in TypeError: keyword arguments must be strings
    % s4 w5 [2 ~7 H
该语言的创造者 Guido van Rossum 写道:
9 c8 w2 J' {" ]9 c( P7 W6 o我可以声明 dict({},{1:3}) 是非法的,因为毕竟是对的    滥用机制。* Z& Q3 ?9 K* e3 t6 p

! N. z) m3 \) q% J; F# q显然 dict(x,**y) 作为 call x.update(y) and return x”的“cool hack四处走动。就我个人而言,我认为它比酷更卑鄙。1 E2 S8 b: T6 `' s- C% J
根据我的理解(以及语言创造者的理解),预期使用dict(**y)以可读性为目的创建字典,例如:7 H0 g7 c6 a/ ^( ?/ Y1 d6 V
    dict(a=1,b=10,c=11)
    ( E. j9 [) b% |
代替
7 E% D" ~( O  A9 N
    - z6 W6 K* ~, Z8 o! }) R
  • {'a': 1,'b': 10,'c code]回复评论不管 Guido 怎么说,dict(x,**y)它都符合 dict 规范,顺便说一句。适用于 Python 2 和 3。事实上,这只适用于字符串键,而不是 dict 的缺点。在这个地方使用     运算符不是滥用机制,事实上,    是为了传递字典作为关键字而设计的。$ P5 `! t+ j& z% c3 T5 H
  • 同样,当键不是字符串时,它也不适用于 3。隐式调用合同是命名空间使用普通字典,用户只能传输字符串形式的关键字参数。所有其他可调用对象都被迫执行。dict在 Python 2 打破了这个致性:[code]>>> foo(**{('a','b'): None})Traceback (most recent call last):  File "",line 1,in TypeError: foo() keywords must be strings>>> dict(**{('a','b'): None}){('a','b'): None}
    8 q: i: p1 ]0 Z" G) _0 |+ l
考虑到 Python 其他实现(PyPy、Jython、IronPython),这种不一致是很糟糕的。因此它在 Python 3 已经修复,因为这种用法可能是一个突破性的变化。; N& @+ ~1 m% f* _6 m4 N
我告诉你,故意编写只适用于语言版本或某些任意约束的代码是恶意的。( z+ k0 M, I/ Q0 v, W* M1 z' M4 ?
更多评论:
( [- m. |$ X8 k, @( edict(x.items()   y.items() 还是 Python 2 中最可读的解决方案。可读性很重要。
- `; y# z$ `5 V& B1 g5 u3 B我的回答:merge_two_dicts(x,y)事实上,如果我们真的关心可读性,对我来说似乎更清楚。而且它不向前兼容,因为 Python 2 越来越被弃用。
1 Z6 l# k8 h/ ?4 N6 P{**x,**y}嵌套字典似乎没有处理。嵌套键的内容只是被覆盖,而不是合并 […] 我最终被这些答案所困扰,我很惊讶没有人提到它。在我对合并一词的解释中,这些答案描述了用另一个字典更新一个字典,而不是合并。/ {2 \2 j, U. k7 G
是的,我必须让你回到这个问题,它要求正确两个字典进行浅层合并,第一个值被第二个值覆盖 - 在单个表达式中。
% w' E. E) j! y假设有两个字典,一个可能会递归地将它们合并到一个函数中,但是您应该注意不要修改来自任一来源的字典,避免这种情况的最可靠方法是在赋值时进行复制。由于键必须是可散列的,因此通常是不可变的,复制它们是没有意义的:
+ R1 K9 a' q! Z: J# N9 g' o  B
    from copy import deepcopydef dict_of_dicts_merge(x,y):    z = {}    overlapping_keys = x.keys() & y.keys()    for key in overlapping_keys:        z[key] = dict_of_dicts_merge(x[key],y[key])    for key in x.keys() - overlapping_keys:        z[key] = deepcopy(x[key])    for key in y.keys() - overlapping_keys:        z[key] = deepcopy(y[key])    return z
    * g6 Z) P7 |% V2 O7 ?& y# s; B* Z
用法:5 D! H8 k! x" S7 o7 H' l3 t- w) c

    9 G+ L9 _/ K( ?, o5 B
  • >>> x = {'a{1:{}b >>> y = {'b{c >>> dict_of_dicts_merge(x,y){'b ac code]其他类型的事故远远超出了这个问题。
    ( C- ?" L  ^: n$ g! G# x
  • 性能差但正确Ad-hoc这些方法的性能较低,但它们会提供正确的行为。少得多比高性能copy和update或新的拆包,因为他们通过在更高的抽象水平的每个键-但是他们做的尊重优先顺序(后者字典优先), o+ b1 d! R3 F" ?* y
  • 您还可以在字典理解中手动链接字典:[code]{k: v for d in dicts for k,v in d.items()} # iteritems in Python 2.7
    ; f% P% {# A7 }, E+ l( X) R
或者在 Python 2.也许早在 2.4 引入生成器表达式时:
0 }) {9 ^6 @% g
    dict((k,v) for d in dicts for k,v in d.items()) # iteritems in Python 2- W2 y# u, \7 }1 z% S2 j% @9 G
itertools.chain 将迭代器以正确的顺序链接到键确:
: `6 K7 w) y: N8 d: l' b4 h
    from itertools import chainz = dict(chain(x.items(),y.items())) # iteritems in Python 22 L! p4 I) m) l( O
性能分析我只会分析已知行为的正确用法。(自包含,可以自己复制粘贴。9 u+ z6 ?/ H3 E* h( h
    : R+ a  f# c  F& |) G
  • from timeit import repeatfrom itertools import chainx = dict.fromkeys('abcdefg')y = dict.fromkeys('efghijk')def merge_two_dicts(x,y):    z = x.copy()    z.update(y)    return zmin(repeat(lambda: {**x,**y}))min(repeat(lambda: merge_two_dicts(x,y)))min(repeat(lambda: {k: v for d in (x,y) for k,v in d.items()}))min(repeat(lambda: dict(chain(x.items(),y.items()))))min(repeat(lambda: dict(item for d in (x,y) for item in d.items())code]在 Python 3.8.1 中,NixOS:[code]>>> min(repeat(lambda: {**x,**y}))1.0804965235292912>>> min(repeat(lambda: merge_two_dicts(x,y)))1.636518670246005>>> min(repeat(lambda: {k: v for d in (x,y) for k,v in d.items()}))3.1779992282390594>>> min(repeat(lambda: dict(chain(x.items(),y.items())))2.740647904574871>>> min(repeat(lambda: dict(item for d in (x,y) for item in d.items())))4.266070580109954$ uname -aLinux nixos 4.19.113 #1-NixOS SMP Wed Mar 25 07:06:15 UTC 2020 x86_64 GNU/Linux
    . X' O- d  `9 P# |( J3 y
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则