[python] 파이썬에서 두 개의 사전을 단일 표현식으로 병합하는 방법은 무엇입니까?

나는 두 개의 파이썬 사전을 가지고 있으며이 두 사전을 반환하는 단일 표현식을 병합하려고합니다. 이 update()방법은 사전을 수정하지 않고 결과를 반환하면 필요한 것입니다.

>>> x = {'a': 1, 'b': 2}
>>> y = {'b': 10, 'c': 11}
>>> z = x.update(y)
>>> print(z)
None
>>> x
{'a': 1, 'b': 10, 'c': 11}

어떻게에서 그 최종 병합 된 사전을 얻을 수있다 z, 없다 x?

(더 명확하게 말하면, 마지막으로 승리하는 충돌 처리는 dict.update()내가 찾고있는 것입니다.)



답변

두 개의 파이썬 사전을 단일 표현식으로 병합하려면 어떻게해야합니까?

사전의 경우 xy, z얕게로부터 값을 사전에 합병되고 y에서 해당 교체 x.

  • Python 3.5 이상에서 :

    z = {**x, **y}
  • Python 2에서 (또는 3.4 이하) 함수를 작성하십시오.

    def merge_two_dicts(x, y):
        z = x.copy()   # start with x's keys and values
        z.update(y)    # modifies z with y's keys and values & returns None
        return z

    그리고 지금:

    z = merge_two_dicts(x, y)
  • 파이썬 3.9.0a4 이상 (최종 릴리스 날짜 2020 약 10월)에서 : PEP-584 , 여기서 논의 , 더이 문제를 단순화하기 위해 구현되었다 :

    z = x | y          # NOTE: 3.9+ ONLY

설명

두 개의 dicts가 있고 원래 dicts를 변경하지 않고 새 dict로 병합하려고한다고 가정하십시오.

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}

원하는 결과는 z값이 병합 된 새 사전 ( ) 을 가져 오고 두 번째 dict의 값은 첫 번째 값을 덮어 씁니다.

>>> z
{'a': 1, 'b': 3, 'c': 4}

제안이에 대한 새로운 구문, PEP (448)파이썬 3.5로 사용할 수는 있다

z = {**x, **y}

그리고 그것은 실제로 하나의 표현입니다.

리터럴 표기법과 병합 할 수도 있습니다.

z = {**x, 'foo': 1, 'bar': 2, **y}

그리고 지금:

>>> z
{'a': 1, 'b': 3, 'foo': 1, 'bar': 2, 'c': 4}

이제 3.5, PEP 478 릴리스 일정에 구현 된 것으로 표시되며 이제 Python 3.5 문서 의 새로운 기능 으로 전환되었습니다 .

그러나 많은 조직이 여전히 Python 2를 사용하고 있으므로 이전 버전과 호환되는 방식으로이 작업을 수행 할 수 있습니다. Python 2 및 Python 3.0-3.4에서 사용할 수있는 고전적인 Pythonic 방식은 2 단계 프로세스로이를 수행하는 것입니다.

z = x.copy()
z.update(y) # which returns None since it mutates z

두 가지 접근 방식 모두에서 두 y번째로 올 것이며 그 값이의 값을 대체 할 x것이므로 최종 결과를 'b'가리킬 것 3입니다.

아직 Python 3.5에서는 없지만 단일 표현식을 원합니다.

아직 Python 3.5를 사용하지 않거나 이전 버전과 호환되는 코드를 작성해야하고 단일 표현식으로 작성 하려는 경우 가장 효과적인 방법은 코드를 함수에 넣는 것입니다.

def merge_two_dicts(x, y):
    """Given two dicts, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

그리고 하나의 표현이 있습니다.

z = merge_two_dicts(x, y)

정의되지 않은 수의 dict을 0에서 매우 큰 수로 병합하는 함수를 만들 수도 있습니다.

def merge_dicts(*dict_args):
    """
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
    """
    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

이 함수는 모든 dicts에 대해 Python 2 및 3에서 작동합니다. 예를 들면 다음 ag같습니다.

z = merge_dicts(a, b, c, d, e, f, g) 

과에서 키 값 쌍은 gdicts보다 우선합니다 af, 등등.

다른 답변의 비판

이전에 받아 들여진 답변에 표시된 것을 사용하지 마십시오.

z = dict(x.items() + y.items())

Python 2에서는 각 dict에 대해 메모리에 두 개의 목록을 작성하고 처음 두 개의 길이와 같은 길이의 메모리에 세 번째 목록을 작성한 다음 세 목록을 모두 버리고 dict를 작성하십시오. Python 3에서는dict_items 두 개의 목록이 아닌 두 개의 객체를 함께 추가하기 때문에 실패 합니다.

>>> c = dict(a.items() + b.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items'

예를 들어 목록으로 명시 적으로 작성해야합니다 z = dict(list(x.items()) + list(y.items())). 이것은 자원과 계산 능력의 낭비입니다.

마찬가지로, items()파이썬 3에서 ( viewitems()파이썬 2.7에서) 통합을 취하는 것도 값이 해시 불가능한 객체 (예 : 목록) 일 때 실패합니다. 값이 해시 가능하더라도 집합은 의미 적으로 순서가 지정되지 않으므로 우선 순위와 관련하여 동작이 정의되지 않습니다. 따라서 이것을하지 마십시오 :

>>> c = dict(a.items() | b.items())

이 예제는 값을 해싱 ​​할 수 없을 때 발생하는 상황을 보여줍니다.

>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

다음은 y가 우선해야하는 예입니다. 대신 x의 값은 임의의 세트 순서로 유지됩니다.

>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}

사용하지 말아야 할 또 다른 핵 :

z = dict(x, **y)

이것은 dict생성자를 사용하며 매우 빠르고 메모리 효율적입니다 (우리의 2 단계 프로세스보다 약간 더 많음). 여기서 무슨 일이 일어나고 있는지 정확히 알지 못하면 (즉, 두 번째 dict는 dict에 키워드 인수로 전달됩니다. 생성자), 읽기가 어렵고 의도 된 사용법이 아니기 때문에 Pythonic이 아닙니다.

다음 은 django에서 사용되는 사용법의 예입니다 .

딕셔너리는 해시 가능 키 (예 : 고정 세트 또는 튜플)를 사용하려고하지만 키가 문자열이 아닌 경우 Python 3에서이 방법이 실패합니다.

>>> c = dict(a, **b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

로부터 메일 링리스트 , 귀도 반 로섬 (Guido van Rossum), 언어의 창조자는 썼다 :

나는 dict ({}, ** {1 : 3})을 불법으로 선언하는 것이 좋습니다. 결국 ** 메커니즘을 남용하기 때문입니다.

분명히 dict (x, ** y)는 “call x.update (y) and return x”에 대한 “cool hack”으로 돌아가고 있습니다. 개인적으로 나는 그것이 차가운 것보다 더 비열한 것을 안다.

의도 된 사용법 은 가독성을위한 사전 작성을 위한 것임을 이해하는 것입니다 ( 예 : 언어 작성자의 이해 ) dict(**y).

dict(a=1, b=10, c=11)

대신에

{'a': 1, 'b': 10, 'c': 11}

의견에 대한 답변

Guido의 말에도 불구하고 dict(x, **y)dict 사양과 일치합니다. 이것은 파이썬 2와 3 모두에서 작동합니다. 이것은 문자열 키에서만 작동한다는 사실은 키워드 매개 변수가 작동하는 방식의 직접적인 결과이며 dict의 단점이 아닙니다. ** 대신에 ** 연산자를 사용하여 메커니즘을 남용하지 않습니다. 실제로 **는 dicts를 키워드로 전달하도록 정확하게 설계되었습니다.

키가 문자열이 아닌 경우에도 3에서 작동하지 않습니다. 암시 적 호출 계약은 네임 스페이스가 일반적인 dicts를 취하는 반면 사용자는 문자열 인 키워드 인수 만 전달해야한다는 것입니다. 다른 모든 소명은 그것을 시행했다. dict파이썬 2 에서이 일관성을 깨뜨 렸습니다.

>>> foo(**{('a', 'b'): None})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() keywords must be strings
>>> dict(**{('a', 'b'): None})
{('a', 'b'): None}

파이썬 (Pypy, Jython, IronPython)의 다른 구현을 고려할 때이 불일치가 나빴습니다. 따라서이 사용법은 주요 변경 사항이 될 수 있으므로 Python 3에서 수정되었습니다.

한 버전의 언어에서만 작동하거나 특정 임의의 제약 조건에서만 작동하는 코드를 의도적으로 작성하는 것은 악의적 인 무능함이라고 귀하에게 제출합니다.

더 많은 의견 :

dict(x.items() + y.items()) 여전히 파이썬 2에서 가장 읽기 쉬운 솔루션입니다.

내 대답 : merge_two_dicts(x, y)실제로 가독성에 관심이 있다면 실제로 나에게 훨씬 분명해 보입니다. 그리고 파이썬 2가 점점 더 이상 사용되지 않으므로 앞으로 호환되지 않습니다.

{**x, **y}중첩 된 사전을 처리하지 않는 것 같습니다. 중첩 된 키의 내용은 단순히 덮어 쓰지 않고 병합되지 않습니다. […] 나는 재귀 적으로 병합되지 않는 이러한 답변에 의해 화상을 입었고 아무도 그것을 언급하지 않은 것에 놀랐습니다. “병합”이라는 단어를 해석 할 때이 답변은 “한 dict을 다른 dict로 업데이트”하고 병합하지 않는 것을 설명합니다.

예. 나는 두 개의 사전을 얕게 병합 하고 첫 번째 값 을 번째 값으로 덮어 씁니다.

두 개의 사전 사전을 가정하면 하나를 재귀 적으로 단일 함수로 병합 할 수 있지만 소스 중 하나에서 dicts를 수정하지 않도록주의해야하며 값을 지정할 때 사본을 작성하지 않아야합니다. 키는 해시 가능해야하며 일반적으로 불변이므로 키를 복사하는 것은 의미가 없습니다.

from copy import deepcopy

def 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

용법:

>>> x = {'a':{1:{}}, 'b': {2:{}}}
>>> y = {'b':{10:{}}, 'c': {11:{}}}
>>> dict_of_dicts_merge(x, y)
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}

다른 값 유형에 대한 우발적 인 문제는이 질문의 범위를 훨씬 넘어서므로 “사전의 사전 병합”에 대한 표준 질문에 대한 답변을 알려 드리겠습니다 .

성능은 떨어지지 만 Ad-hoc은 정확

이러한 접근 방식은 성능이 떨어지지 만 올바른 동작을 제공합니다. 그들은 것입니다 훨씬 성능이 좋은 이상 copyupdate새로운 풀기가 더 높은 추상화 수준에서 각 키 – 값 쌍을 반복하기 때문에 나,하지만 그들은 우선 순위를 (후자 dicts이 우선 순위가) 존중

dict 이해 내에서 dicts를 수동으로 연결할 수도 있습니다.

{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7

또는 파이썬 2.6 (및 생성기 표현식이 도입되었을 때 2.4 이전) :

dict((k, v) for d in dicts for k, v in d.items())

itertools.chain 키-값 쌍에 대해 반복자를 올바른 순서로 연결합니다.

import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))

성능 분석

나는 올바르게 작동하는 것으로 알려진 사용법의 성능 분석 만 할 것입니다.

import timeit

다음은 Ubuntu 14.04에서 수행됩니다.

Python 2.7 (시스템 Python)에서 :

>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.5726828575134277
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.163769006729126
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.iteritems(), y.iteritems()))))
1.1614501476287842
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
2.2345519065856934

Python 3.5 (deadsnakes PPA)에서 :

>>> min(timeit.repeat(lambda: {**x, **y}))
0.4094954460160807
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.7881555100320838
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.4525277839857154
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.items(), y.items()))))
2.3143140770262107
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
3.2069112799945287

사전에 대한 자료


답변

귀하의 경우 할 수있는 일은 다음과 같습니다.

z = dict(x.items() + y.items())

이것은 원하는대로 최종 dict를 넣고 두 번째 dict z의 값으로 key의 값을 b올바르게 재정의 y합니다.

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}

Python 3을 사용하면 조금 더 복잡합니다. 만들려면 z:

>>> z = dict(list(x.items()) + list(y.items()))
>>> z
{'a': 1, 'c': 11, 'b': 10}

Python 버전 3.9.0a4 이상을 사용하는 경우 직접 사용할 수 있습니다.

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = x | y
print(z)

Output: {'a': 1, 'c': 11, 'b': 10}


답변

대안:

z = x.copy()
z.update(y)


답변

또 다른 간결한 옵션 :

z = dict(x, **y)

참고 : 이것은 대중적인 대답이되었지만 y문자열이 아닌 키가있는 경우 이것이 작동한다는 사실은 CPython 구현 세부 사항을 남용하고 Python 3에서는 작동하지 않는다는 점을 지적하는 것이 중요합니다 . 또는 PyPy, IronPython 또는 Jython에서. 또한 Guido는 팬이 아닙니다 . 따라서 순방향 호환 또는 교차 구현 휴대용 코드에 대해서는이 기술을 권장 할 수 없습니다. 이는 완전히 피해야한다는 것을 의미합니다.


답변

이것은 아마도 대중적인 대답은 아니지만 아마도 거의 그렇게하고 싶지 않습니다. 병합 된 사본을 원할 경우 copy (또는 원하는 내용에 따라 deepcopy )를 사용한 다음 업데이트하십시오. 두 줄의 코드는 .items () + .items ()로 한 줄을 만드는 것보다 훨씬 더 읽기 쉽습니다. 암시적인 것보다 명시적인 것이 좋습니다.

또한 .items () (Python 3.0 이전)를 사용하면 dict의 항목을 포함하는 새 목록을 만듭니다. 사전이 큰 경우 오버 헤드가 많이 발생합니다 (병합 된 dict가 작성되는 즉시 버려지는 두 개의 큰 목록). update ()는 항목별로 두 번째 dict을 실행할 수 있기 때문에 더 효율적으로 작동 할 수 있습니다.

시간 측면에서 :

>>> timeit.Timer("dict(x, **y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()\ntemp.update(y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.694622993469238
>>> timeit.Timer("dict(x.items() + y.items())", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
41.484580039978027

IMO 첫 두 개 사이의 작은 속도 저하는 가독성을 위해 가치가 있습니다. 또한 사전 생성을위한 키워드 인수는 Python 2.3에서만 추가되었지만 copy () 및 update ()는 이전 버전에서 작동합니다.


답변

후속 답변에서이 두 대안의 상대적 성능에 대해 물었습니다.

z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

내 컴퓨터에서 (Python 2.5.2를 실행하는 상당히 일반적인 x86_64) 대안 z2은 짧고 간단 할뿐만 아니라 훨씬 빠릅니다. timeit파이썬과 함께 제공 되는 모듈을 사용하여 직접 확인할 수 있습니다 .

예 1 : 연속 된 20 개의 정수를 자신에 매핑하는 동일한 사전 :

% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z1=dict(x.items() + y.items())'
100000 loops, best of 3: 5.67 usec per loop
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z2=dict(x, **y)'
100000 loops, best of 3: 1.53 usec per loop

z23.5 배 정도 승리합니다. 다른 사전은 매우 다른 결과를 산출하는 것처럼 보이지만 z2항상 앞서 나오는 것처럼 보입니다. ( 동일한 테스트에 대해 일관되지 않은 결과가 나오면 -r기본값 3보다 큰 숫자로 전달 하십시오.)

예제 2 : 겹치지 않는 사전은 252 개의 짧은 문자열을 정수로 매핑하거나 그 반대로 매핑합니다.

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z1=dict(x.items() + y.items())'
1000 loops, best of 3: 260 usec per loop
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z2=dict(x, **y)'
10000 loops, best of 3: 26.9 usec per loop

z2 내 책에서 꽤 큰 승리입니다!

이 두 가지를 비교 한 후의 z1성능 저하가 두 항목 목록 구성의 오버 헤드로 인한 것일 수 있는지 궁금해서이 변형이 더 잘 작동하는지 궁금해졌습니다.

from itertools import chain
z3 = dict(chain(x.iteritems(), y.iteritems()))

몇 가지 빠른 테스트, 예 :

% python -m timeit -s 'from itertools import chain; from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z3=dict(chain(x.iteritems(), y.iteritems()))'
10000 loops, best of 3: 66 usec per loop

그것 z3보다 약간 빠르지 z1만 거의 빠르지 않다는 결론을 이끌어 z2냅니다. 여분의 타이핑을 모두 가치가 없습니다.

이 논의에는 여전히 중요한 것이 빠져 있는데, 이는 두 대안을 병합하는 “명확한”방법과 이러한 대안을 성능 비교하는 update것입니다. x 또는 y를 수정하지 않는 표현식과 동일한 기반을 유지하기 위해 다음과 같이 x를 수정하지 않고 사본을 만들 것입니다.

z0 = dict(x)
z0.update(y)

전형적인 결과 :

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z0=dict(x); z0.update(y)'
10000 loops, best of 3: 26.9 usec per loop

즉, z0z2본질적으로 동일한 성능을 갖고있는 것 같다. 이것이 우연의 일치라고 생각하십니까? 난 …

사실, 순수한 파이썬 코드가 이것보다 더 나은 것은 불가능하다고 주장하기까지하였습니다. 그리고 C 확장 모듈에서 훨씬 더 잘 할 수 있다면, 파이썬 사람들이 코드 (또는 접근 방식의 변형)를 파이썬 코어에 통합하는 데 관심이있을 것입니다. 파이썬은 dict많은 곳에서 사용 합니다; 운영 최적화는 큰 문제입니다.

이것을 다음과 같이 쓸 수도 있습니다.

z0 = x.copy()
z0.update(y)

Tony와는 달리 (놀랍지 않게) 표기법의 차이는 성능에 측정 가능한 영향을 미치지 않는 것으로 나타났습니다. 나에게 맞는 것을 사용하십시오. 물론, 그는 2 문장 버전이 이해하기 훨씬 쉽다는 것을 지적하는 것이 옳습니다.


답변

파이썬 3.0 이상에서 , 당신이 사용할 수있는 collections.ChainMap하나의 업데이트 할 수있는 뷰를 생성하기 위해 함께하는 그룹 여러 dicts 또는 다른 매핑을 :

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(ChainMap({}, y, x))
>>> for k, v in z.items():
        print(k, '-->', v)

a --> 1
b --> 10
c --> 11

Python 3.5 이상 업데이트 : PEP 448 확장 사전 패킹 및 언 패킹을 사용할 수 있습니다 . 이것은 빠르고 쉽습니다.

>>> x = {'a':1, 'b': 2}
>>> y = y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}