回答

收藏

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

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

我有两个 Python 字典,我想写一个单一的表达式来返回这两个字典,合并(即合并)。update()方法将是我需要的,如果它返回其结果而不是修改字典。
* K0 l# K# r, O3 [/ f6 h8 \( Q) W
    # h+ g+ e6 E" R" X! X( j
  • >>> 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?  _- ^7 W4 n! j) M& N6 h& ^( x! ]# i
  • (应该特别清楚最后一次胜利的冲突处理dict.update()也是我在寻找的。
    6 o" @5 }& Q2 I( n6 ?/ E
  •                                                                
    . _* Y% [5 {' M
  •     解决方案:                                                               
    ( ?, [; @, {+ f; f
  •                                                                 如何在单个表达式中合并两个 Python 字典?对于字典xand y,z成为浅层合并的字典,其中值y取代 值x。5 R- K3 c( H$ k0 L
  • , e! E' P6 ~6 F6 h9 M* t& O) A; F8 ]
  • 在Python 3.9.0或更高(释放2020年10月17日)EP-在这里讨论、实现、提供最简单的方法:py  z = x | y          # NOTE: 3.9  ONLY
    4 Z) a; H+ C9 Q( F9 ^1 i
  • ! d# O1 ~+ a, T- N9 r
  • 在 Python 3.5 或更高版本:py  z = {**x,**y}' b- `9 J4 S8 S5 J* T( w

  • ' f# Q" v% F+ K! s2 {: w
  • 在 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# n4 q4 F% s# K+ B8 Q# S
  • 现在:
    , o' e3 Z8 N* o6 @2 v, C" ?
  • py  z = merge_two_dicts(x,y)9 p4 z5 |& ~( f- G$ l
  • 解释假设你有两个字典,你想在不改变原始字典的情况下将它们合并到一个新字典中:7 F# Z+ a& f2 {2 E# w1 T
  • [code]x = {'a': 1,'b': 2}y = {'b': 3,'ccode]期待的结果是得到一个新的字典 ( z),第二值,第二字典值覆盖第一字典值。9 z6 m" j. E6 S4 w5 f/ c2 w- }  l
  • [code]>>> z{'a': 1,'b': 3,'ccode]在PEP 448 Python 3.在5 中使用的新语法是[code]z = {**x,**y}; z- ^2 }- e6 i8 h4 y2 A% i
它确实是一种单一的表达。
' e$ u' b0 x: K5 ^& s+ F请注意,我们也可以与文本符号合并:
7 ?" a8 W/ q8 E0 c8 [6 Z8 ?5 J& ?
    z = {**x,'foo': 1,'bar': 2,**y}: D$ e# Q1 T- M! x; l2 F& c
现在:
# K" _2 f: o; e- I( v+ `

    ' J. N" y# W4 Q$ C/ T
  • >>> z{'a': 1,'b': 3,'foo': 1,'bar': 2,'ccode]它现在显示为 3.5 PEP 478的发布时间表已经实现,现在已经进入Python 3.在5 新功能文档中。: H# v; Y3 X" I& Y& A+ d" a! B
  • 然而,许多组织仍在使用 Python 2,您可能希望以后以兼容的方式执行此操作。Python 2 和 Python 3.0-3.4 中可用的经典 Pythonic 方法是将其作为两个过程执行:[code]z = x.copy()z.update(y) # which returns None since it mutates z
    3 u, l+ v0 C, v* p* `' h
在这两种方法中,y排名第二,其值将被替换x的值,因此b我们的最终结果将指向3。
2 W- @' X' [" P1 S7 W0 l还没有在 Python 3.5 上,但我想要一个单一的表达式假如你还没用 Python 3.5 或者需要编写后兼容的代码,你想要在单个表达式中使用,那么性能最好的方法是将其放入函数中:
" l' _2 q# Q" O5 H3 X
    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
    ) p: p, T% y, n
然后你有一个表达式:
  g! q3 u* ]5 Q- @1 i' }7 K; I7 `7 z0 @
    z = merge_two_dicts(x,y)
    : G' b+ K, V+ @* G9 n& m$ N+ A
您还可以创建一个函数来合并任何数量的字典,从零到非常大的数字:) P; i; D0 @4 n" D
    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
    2 W$ z) R$ w" p) o) {* R( y
该函数适用于所有字典 Python 2 和 3a到g:4 N) s3 R, Q: y. c5 w$ f! s$ S$ @. t
    z = merge_dicts(a,b,c,d,e,f,g)
    + }8 `; H* a- Q$ T  u7 h( d2 B4 s
和键值对g优先于字典ato f,依此类推。
, P% y+ ?/ e) x& W1 Z# L批评其他答案不要使用你在以前的答案中看到的内容:7 x4 `5 ?/ @' o
    z = dict(x.items()   y.items())
    1 V4 o) _! ?  E; E! U8 n
在 Python 2 在内存中,你是每个 dict 创建两个列表,在内存中创建第三个列表,其长度等于前两个列表的长度,然后丢弃所有三个列表创建 dict。在 Python 3 中,这将失败,因为你要两个dict_items将对象添加在一起,而不是两个列表 -
/ h8 Z; B1 i1 M' l( \! q, A' F* ~
    >>> 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'  c& t, U" G4 h/ {6 r* W& z
例如,您必须创建它们的显式列表z = dict(list(x.items()  list(y.items())). 这是浪费资源和计算能力。2 m1 p( ]; l) J! Y8 W" K; N
类似地,当值是不可散列的对象(如列表),items()在 Python 3(viewitems()在 Python 2.7 中)并集也会失败。即使你的值可以散列,因为集合在语义上是无序的,所以行为在优先级上是不定义的。所以不要这样做:, f, h& t8 R  W& Z& T
    >>> c = dict(a.items() | b.items())
    7 U' _7 b" _7 h/ C* x# i4 T2 q% t
这个例子展示了当值不能散列时会发生什么:: ^* H% U: p2 a8 u
    >>> x = {'a>>> y = {'b>>> dict(x.items() | y.items())Traceback (most recent call last):  File "",line 1,in TypeError: unhashable type: 'list'
    * Y- r# R" c+ w& Z- `6 P
这是一个y应该有优先示例,但是x  由于集合的任意顺序而保留from 的值:" x# O8 m2 t- m% @& ]
    ) z: \/ Y2 B5 {/ \
  • >>> x = {'a': 2}>>> y = {'a': 1}>>> dict(x.items() | y.items()){'a code]另一个你不应该用的黑客:[code]z = dict(x,**y)
    " q6 C  L8 K0 G% ^6 x/ F
这使用dict构造函数非常快,内存效率高(甚至比我们的两个步骤多一点),但除非你确切知道这里发生了什么(也就是说,第二个 dict 作为关键字参数传递给 dict 构造函数),很难阅读,不是预期用法,所以不是 Pythonic。  U! u1 ?; |  n  q0 q/ B& r5 u; r
这是在 django修复用法示例。
$ j( R/ Q1 G) K5 y! P* b; s+ i字典旨在使用可散列键(例如frozensets 或元组),但是当键不是字符串时此方法在 Python 3 失败。
1 I0 _, I) Y+ s
    >>> c = dict(a,**b)Traceback (most recent call last):  File "",line 1,in TypeError: keyword arguments must be strings
    " R5 \$ K' O3 S* c0 g
该语言的创造者 Guido van Rossum 写道:
! Q& X4 ]- `. i1 O2 ?" z我可以声明 dict({},{1:3}) 是非法的,因为毕竟是对的    滥用机制。2 g5 a; S: o/ p& j+ J
8 Y! @5 @7 f6 Z/ R* r
显然 dict(x,**y) 作为 call x.update(y) and return x”的“cool hack四处走动。就我个人而言,我认为它比酷更卑鄙。, l3 D  N9 @( X8 A0 \1 \
根据我的理解(以及语言创造者的理解),预期使用dict(**y)以可读性为目的创建字典,例如:. [( E) V' z: Z# s% P1 v
    dict(a=1,b=10,c=11); u  j2 w) K( B% r7 ?' x
代替) ?8 g. N( e: ^6 I+ A$ E
    ( Q( N; `5 L+ @' L
  • {'a': 1,'b': 10,'c code]回复评论不管 Guido 怎么说,dict(x,**y)它都符合 dict 规范,顺便说一句。适用于 Python 2 和 3。事实上,这只适用于字符串键,而不是 dict 的缺点。在这个地方使用     运算符不是滥用机制,事实上,    是为了传递字典作为关键字而设计的。
    * A1 j3 l& I; T" K
  • 同样,当键不是字符串时,它也不适用于 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}  P$ C  n, d! c  k; f
考虑到 Python 其他实现(PyPy、Jython、IronPython),这种不一致是很糟糕的。因此它在 Python 3 已经修复,因为这种用法可能是一个突破性的变化。/ a) F7 t) O, ?, S. g) _$ R/ B
我告诉你,故意编写只适用于语言版本或某些任意约束的代码是恶意的。& Q$ t7 t# g1 |* r- p5 T! \$ s
更多评论:
7 b7 n9 F: H9 {0 d+ X. w* }% {dict(x.items()   y.items() 还是 Python 2 中最可读的解决方案。可读性很重要。
: n* x) C4 b% M5 Q' w  o# P我的回答:merge_two_dicts(x,y)事实上,如果我们真的关心可读性,对我来说似乎更清楚。而且它不向前兼容,因为 Python 2 越来越被弃用。& y3 O$ P: C  c" P1 |2 X4 c
{**x,**y}嵌套字典似乎没有处理。嵌套键的内容只是被覆盖,而不是合并 […] 我最终被这些答案所困扰,我很惊讶没有人提到它。在我对合并一词的解释中,这些答案描述了用另一个字典更新一个字典,而不是合并。
1 I- U; ~# S0 C$ _- s$ h/ l! M是的,我必须让你回到这个问题,它要求正确两个字典进行浅层合并,第一个值被第二个值覆盖 - 在单个表达式中。
0 _' m( I3 `& g假设有两个字典,一个可能会递归地将它们合并到一个函数中,但是您应该注意不要修改来自任一来源的字典,避免这种情况的最可靠方法是在赋值时进行复制。由于键必须是可散列的,因此通常是不可变的,复制它们是没有意义的:
0 y+ }, d* r# Z3 t4 j
    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+ a: M& B0 v, {. K
用法:7 r5 E; D  S  j  ?" p; C0 b! `
    8 p& s0 c( E* Z1 m5 G6 z5 C+ @
  • >>> x = {'a{1:{}b >>> y = {'b{c >>> dict_of_dicts_merge(x,y){'b ac code]其他类型的事故远远超出了这个问题。1 }! v& s+ S" [4 v& ^, C7 i! s, V
  • 性能差但正确Ad-hoc这些方法的性能较低,但它们会提供正确的行为。少得多比高性能copy和update或新的拆包,因为他们通过在更高的抽象水平的每个键-但是他们做的尊重优先顺序(后者字典优先)! R; l. J) n+ P9 ~
  • 您还可以在字典理解中手动链接字典:[code]{k: v for d in dicts for k,v in d.items()} # iteritems in Python 2.7
    , p- R( p7 ^: z! G' m+ b4 {1 z
或者在 Python 2.也许早在 2.4 引入生成器表达式时:- d+ S4 q$ Y% U8 X1 Q5 q+ B& X
    dict((k,v) for d in dicts for k,v in d.items()) # iteritems in Python 2
    % u' q. o/ S' B3 c
itertools.chain 将迭代器以正确的顺序链接到键确:
* \6 Q) {) S4 R7 ?6 p5 \
    from itertools import chainz = dict(chain(x.items(),y.items())) # iteritems in Python 2
    5 L4 u  ^3 M9 u* M! M
性能分析我只会分析已知行为的正确用法。(自包含,可以自己复制粘贴。( R( K6 T& ^! H  ?9 L
    # q" x2 c9 [0 B
  • 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
    ( ?) y0 `- f3 w% e
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则