回答

收藏

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

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

我有两个 Python 字典,我想写一个单一的表达式来返回这两个字典,合并(即合并)。update()方法将是我需要的,如果它返回其结果而不是修改字典。
) A# a  ?$ L4 \% V' K1 d
    " j. Y& o8 z# c! h( `, k# X) Y
  • >>> 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?# @8 o5 K! ?! ?& D$ ]
  • (应该特别清楚最后一次胜利的冲突处理dict.update()也是我在寻找的。5 L- E' u8 p( J; y- V1 a6 U
  •                                                                7 w2 N- l7 J' z& C0 g# |
  •     解决方案:                                                               
    ' F- i5 e3 C3 {3 N2 @# d2 j! ?
  •                                                                 如何在单个表达式中合并两个 Python 字典?对于字典xand y,z成为浅层合并的字典,其中值y取代 值x。
    ! l' o+ [9 K( {2 y6 S+ Y4 P3 C

  • & u2 _/ F& L5 d9 [. v* g( ?
  • 在Python 3.9.0或更高(释放2020年10月17日)EP-在这里讨论、实现、提供最简单的方法:py  z = x | y          # NOTE: 3.9  ONLY5 ]# s/ `7 a3 k. R! J
  • ' p" w# L! z! I4 x
  • 在 Python 3.5 或更高版本:py  z = {**x,**y}; X! c% d  N  x2 l1 R; j6 ~

  • ; q( Q& L, i. V1 _) e$ l
  • 在 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
    $ i1 ~0 y. c$ l& O% \8 j
  • 现在:
    & T3 W2 R( Z  K# s1 n
  • py  z = merge_two_dicts(x,y)7 B3 o6 ?$ W# }; \2 a7 K1 [
  • 解释假设你有两个字典,你想在不改变原始字典的情况下将它们合并到一个新字典中:
    & @; Y. @' G$ p3 `: i% {& V% l
  • [code]x = {'a': 1,'b': 2}y = {'b': 3,'ccode]期待的结果是得到一个新的字典 ( z),第二值,第二字典值覆盖第一字典值。: ?) a% z# G; n* o0 Q
  • [code]>>> z{'a': 1,'b': 3,'ccode]在PEP 448 Python 3.在5 中使用的新语法是[code]z = {**x,**y}
    ) _' ?# o- g* v, l" K# X
它确实是一种单一的表达。
( g& U& D. k1 G' D7 m8 d请注意,我们也可以与文本符号合并:+ V& ]$ d0 e( |! V9 ~
    z = {**x,'foo': 1,'bar': 2,**y}# ^$ _$ ^/ x: \' o( R" z( \
现在:: d% [8 ~+ T+ b1 l; ~# u. G* Q* I

    8 c1 v8 F3 X9 `. V0 A2 W/ W4 }
  • >>> z{'a': 1,'b': 3,'foo': 1,'bar': 2,'ccode]它现在显示为 3.5 PEP 478的发布时间表已经实现,现在已经进入Python 3.在5 新功能文档中。
    ; ^1 h/ K3 n; f$ m; ^
  • 然而,许多组织仍在使用 Python 2,您可能希望以后以兼容的方式执行此操作。Python 2 和 Python 3.0-3.4 中可用的经典 Pythonic 方法是将其作为两个过程执行:[code]z = x.copy()z.update(y) # which returns None since it mutates z4 }+ y( w, r* N. y" ?# |% m4 ^
在这两种方法中,y排名第二,其值将被替换x的值,因此b我们的最终结果将指向3。9 X0 B: J7 b5 }
还没有在 Python 3.5 上,但我想要一个单一的表达式假如你还没用 Python 3.5 或者需要编写后兼容的代码,你想要在单个表达式中使用,那么性能最好的方法是将其放入函数中:3 ?' D  y3 Z" y1 \
    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( ]4 F2 d, n# I7 y# ^
然后你有一个表达式:
6 F  C1 s* A* ?4 ~7 A, R
    z = merge_two_dicts(x,y)3 }1 E% s( i8 P
您还可以创建一个函数来合并任何数量的字典,从零到非常大的数字:
4 i6 b  ?* k- P4 N
    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
    ( O4 H3 V2 a/ D7 ?
该函数适用于所有字典 Python 2 和 3a到g:
4 H$ a1 S$ ^" E5 }, b6 ?
    z = merge_dicts(a,b,c,d,e,f,g) 2 U" x4 t& K& x, [6 [
和键值对g优先于字典ato f,依此类推。
4 E- a3 Z( h: f批评其他答案不要使用你在以前的答案中看到的内容:- N9 L2 {, ^; \+ B! w0 d
    z = dict(x.items()   y.items())
    0 d/ a; K5 V, b
在 Python 2 在内存中,你是每个 dict 创建两个列表,在内存中创建第三个列表,其长度等于前两个列表的长度,然后丢弃所有三个列表创建 dict。在 Python 3 中,这将失败,因为你要两个dict_items将对象添加在一起,而不是两个列表 -# j7 g' ^  Z7 h  G1 B" _
    >>> 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'
    4 |( _2 H7 f4 t' Q, i/ c, x
例如,您必须创建它们的显式列表z = dict(list(x.items()  list(y.items())). 这是浪费资源和计算能力。
9 O5 @1 H+ N7 `: n2 Z类似地,当值是不可散列的对象(如列表),items()在 Python 3(viewitems()在 Python 2.7 中)并集也会失败。即使你的值可以散列,因为集合在语义上是无序的,所以行为在优先级上是不定义的。所以不要这样做:& m5 v3 W3 I$ j7 F
    >>> c = dict(a.items() | b.items())4 d3 c( i* S* h3 j1 I+ `
这个例子展示了当值不能散列时会发生什么:
7 _# d9 t# ?/ D" F. N9 B" g: w
    >>> x = {'a>>> y = {'b>>> dict(x.items() | y.items())Traceback (most recent call last):  File "",line 1,in TypeError: unhashable type: 'list'; D, {- ?+ P4 G1 h. p( Z$ g4 g/ J0 L
这是一个y应该有优先示例,但是x  由于集合的任意顺序而保留from 的值:
9 m7 y# x: ]4 E! m3 X

    , ~% {. q& |3 W6 M2 z
  • >>> x = {'a': 2}>>> y = {'a': 1}>>> dict(x.items() | y.items()){'a code]另一个你不应该用的黑客:[code]z = dict(x,**y)
    8 D; k  P! o; K
这使用dict构造函数非常快,内存效率高(甚至比我们的两个步骤多一点),但除非你确切知道这里发生了什么(也就是说,第二个 dict 作为关键字参数传递给 dict 构造函数),很难阅读,不是预期用法,所以不是 Pythonic。9 \/ T- X) e4 l
这是在 django修复用法示例。6 C5 ^+ U  P* G# ~0 {
字典旨在使用可散列键(例如frozensets 或元组),但是当键不是字符串时此方法在 Python 3 失败。
9 r7 Q) N& ?- {6 m$ F6 b
    >>> c = dict(a,**b)Traceback (most recent call last):  File "",line 1,in TypeError: keyword arguments must be strings1 d" ~- a5 W8 ]3 b
该语言的创造者 Guido van Rossum 写道:, }: w2 y' \2 f
我可以声明 dict({},{1:3}) 是非法的,因为毕竟是对的    滥用机制。
) H7 d  ~) b  @! @" T; K
1 z7 L0 o, _# z8 U" e: G5 p# F显然 dict(x,**y) 作为 call x.update(y) and return x”的“cool hack四处走动。就我个人而言,我认为它比酷更卑鄙。
& e) J: N6 Q: U9 Z! K% n+ u/ z0 i根据我的理解(以及语言创造者的理解),预期使用dict(**y)以可读性为目的创建字典,例如:$ o; M5 v7 O$ A2 o6 c
    dict(a=1,b=10,c=11)
    : I' T$ d& R* ?* T/ ?  [7 a. F
代替6 |2 n/ U4 T$ C; d2 W
      v# M6 k8 u$ ~& A  ?9 |
  • {'a': 1,'b': 10,'c code]回复评论不管 Guido 怎么说,dict(x,**y)它都符合 dict 规范,顺便说一句。适用于 Python 2 和 3。事实上,这只适用于字符串键,而不是 dict 的缺点。在这个地方使用     运算符不是滥用机制,事实上,    是为了传递字典作为关键字而设计的。
    " W' ^6 o$ i, r5 Z
  • 同样,当键不是字符串时,它也不适用于 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 p0 T5 b' d7 X5 V
考虑到 Python 其他实现(PyPy、Jython、IronPython),这种不一致是很糟糕的。因此它在 Python 3 已经修复,因为这种用法可能是一个突破性的变化。
# ?: x, v2 j1 U- l( d+ R! O我告诉你,故意编写只适用于语言版本或某些任意约束的代码是恶意的。
/ s% a4 P: J' U更多评论:6 @! d9 P; i" L* C6 j6 x
dict(x.items()   y.items() 还是 Python 2 中最可读的解决方案。可读性很重要。% Y6 }9 w1 O4 i' N' Y6 v( h
我的回答:merge_two_dicts(x,y)事实上,如果我们真的关心可读性,对我来说似乎更清楚。而且它不向前兼容,因为 Python 2 越来越被弃用。
; M# I2 n/ h" g{**x,**y}嵌套字典似乎没有处理。嵌套键的内容只是被覆盖,而不是合并 […] 我最终被这些答案所困扰,我很惊讶没有人提到它。在我对合并一词的解释中,这些答案描述了用另一个字典更新一个字典,而不是合并。- O0 Z! B1 O7 O) w* j
是的,我必须让你回到这个问题,它要求正确两个字典进行浅层合并,第一个值被第二个值覆盖 - 在单个表达式中。3 ^; {( F# t4 U5 I  p8 A
假设有两个字典,一个可能会递归地将它们合并到一个函数中,但是您应该注意不要修改来自任一来源的字典,避免这种情况的最可靠方法是在赋值时进行复制。由于键必须是可散列的,因此通常是不可变的,复制它们是没有意义的:* a+ g. ~/ Y/ a7 F, r  e% U9 r
    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% V$ ^9 A) B3 |3 }1 d
用法:2 G; z: O3 d4 i3 y

    3 f5 d. e2 V3 {7 ]
  • >>> x = {'a{1:{}b >>> y = {'b{c >>> dict_of_dicts_merge(x,y){'b ac code]其他类型的事故远远超出了这个问题。
    $ D2 b  c$ p3 q, r2 J
  • 性能差但正确Ad-hoc这些方法的性能较低,但它们会提供正确的行为。少得多比高性能copy和update或新的拆包,因为他们通过在更高的抽象水平的每个键-但是他们做的尊重优先顺序(后者字典优先)2 l" P- X- I4 `8 L' t3 I" a( v8 z
  • 您还可以在字典理解中手动链接字典:[code]{k: v for d in dicts for k,v in d.items()} # iteritems in Python 2.7. n5 `  o& G( v! v
或者在 Python 2.也许早在 2.4 引入生成器表达式时:/ W3 L6 M4 ]- S0 I
    dict((k,v) for d in dicts for k,v in d.items()) # iteritems in Python 26 T: n% `# b* E- r+ c5 N
itertools.chain 将迭代器以正确的顺序链接到键确:+ v% C+ u' b: l
    from itertools import chainz = dict(chain(x.items(),y.items())) # iteritems in Python 2
    # L' [3 d+ Q% b  r; A# t1 l+ u/ X
性能分析我只会分析已知行为的正确用法。(自包含,可以自己复制粘贴。0 O; b; f- M3 p/ |( {

    4 Q9 I& O9 j% s6 v# H* ?5 j, H# e
  • 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+ f' e& u. W, k' |* T5 h/ D: y
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则