|
我有两个 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' ^" Idef 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 |/ Uz = 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 Bfrom 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 @% gdict((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 hfrom 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 |
|