파이썬

[Python] 단일 표현식에서 두 개의 사전을 병합하려면 어떻게 해야 합니까?

zooheon 2022. 7. 31. 13:50
반응형

두 개의 사전을 새 사전으로 병합하고 싶습니다.

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
z = merge(x, y)

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

키가 두 사전에 모두 있을 때마다 k값만 y[k]유지해야 합니다.

 

단일 표현식에서 두 개의 Python 사전을 병합하려면 어떻게 해야 합니까?

사전 xy의 경우 얕게 병합된 사전 z은 에서 값을 가져오고 에서 값을 y대체합니다 x.

  • Python 3.9.0 이상(2020년 10월 17일 출시 , 여기 PEP-584에서 논의 ):

    z = x | y
    
  • Python 3.5 이상:

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

    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
    

    그리고 지금:

    z = merge_two_dicts(x, y)
    

설명

두 개의 사전이 있고 원래 사전을 변경하지 않고 새 사전으로 병합하려는 경우:

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

원하는 결과는 z값이 병합된 새 사전( )을 얻고 두 번째 사전의 값이 첫 번째 사전의 값을 덮어쓰는 것입니다.

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

PEP 448 에서 제안 되고 Python 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에서 사용할 수 있는 고전적인 Python 방식은 이 작업을 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 dictionaries, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

다음과 같은 단일 표현식이 있습니다.

z = merge_two_dicts(x, y)

0에서 매우 큰 숫자까지 임의의 수의 사전을 병합하는 함수를 만들 수도 있습니다.

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

이 함수는 모든 사전에 대해 Python 2 및 3에서 작동합니다. : a_g

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

의 키-값 쌍은 에 g대한 사전보다 우선 a합니다 f.

다른 답변에 대한 비판

이전에 허용된 답변에 표시된 내용을 사용하지 마세요.

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

Python 2에서는 각 사전에 대해 메모리에 두 개의 목록을 만들고 처음 두 개를 합친 길이와 같은 길이로 메모리에 세 번째 목록을 만든 다음 세 개의 목록을 모두 버려 사전을 만듭니다. 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()Python 3( Python 2.7)에서 합집합을 취하는 것도 실패합니다. viewitems()값이 해시 가능하더라도 집합은 의미적으로 순서가 지정되지 않으므로 우선 순위와 관련하여 동작이 정의되지 않습니다. 따라서 다음과 같이 하지 마십시오.

>>> 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세트의 임의 순서로 인해 from 값이 유지되는 예입니다.

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

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

z = dict(x, **y)

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

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

frozenset사전은 해시 가능한 키(예: s 또는 튜플) 를 사용하도록 의도 되었지만 키가 문자열이 아닌 경우 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)는 "x.update(y) 호출 및 x 반환"에 대한 "멋진 해킹"으로 돌아갑니다. 개인적으로 쿨하다기 보다는 징그럽다.

의도된 사용법 은 가독성을 위해 사전을 만드는 것입니다. :dict(**y)

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

대신에

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

댓글에 대한 응답

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

다시 말하지만 키가 문자열이 아닌 경우 3에서는 작동하지 않습니다. 암시적 호출 계약은 네임스페이스가 일반 사전을 사용하는 반면 사용자는 문자열인 키워드 인수만 전달해야 한다는 것입니다. 다른 모든 콜러블은 이를 시행했습니다. dictPython 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}

이 불일치는 Python의 다른 구현(PyPy, Jython, IronPython)을 고려할 때 좋지 않았습니다. 따라서 이 사용법은 주요 변경 사항이 될 수 있으므로 Python 3에서 수정되었습니다.

한 가지 버전의 언어에서만 작동하거나 특정 임의의 제약 조건이 주어졌을 때만 작동하는 코드를 의도적으로 작성하는 것은 악의적인 무능함임을 알려드립니다.

추가 의견:

dict(x.items() + y.items())여전히 Python 2에서 가장 읽기 쉬운 솔루션입니다. 가독성이 중요합니다.

내 대답: merge_two_dicts(x, y)우리가 실제로 가독성에 대해 우려한다면 실제로 나에게 훨씬 더 명확해 보입니다. 그리고 Python 2가 점점 더 사용되지 않으므로 앞으로 호환되지 않습니다.

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

예. 첫 번째 값이 두 번째 값으로 덮어쓰여진 두 개의 사전을 단일 표현식으로 얕은 병합을 요구하는 질문으로 다시 안내해 드리겠습니다 .

사전의 두 사전을 가정하면 하나는 단일 함수에서 재귀적으로 병합할 수 있지만 어느 한 소스에서 사전을 수정하지 않도록 주의해야 하며 이를 방지하는 가장 확실한 방법은 값을 할당할 때 복사본을 만드는 것입니다. 키는 해시 가능해야 하고 일반적으로 변경할 수 없으므로 복사하는 것은 무의미합니다.

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: {}}}

다른 값 유형에 대한 우연성을 생각해 내는 것은 이 질문의 범위를 훨씬 벗어나므로 "Dictionaries of dictionaries merge"에 대한 표준 질문에 대한 답변을 알려 드리겠습니다 .

성능은 떨어지지만 올바른 애드혹

이러한 접근 방식은 성능이 떨어지지만 올바른 동작을 제공합니다. 더 높은 수준의 추상화에서 각 키-값 쌍을 반복하기 때문에 및 /또는 새로운 압축 해제보다 성능이 훨씬 떨어지지 만 우선 순위 존중합니다(후자의 사전이 우선합니다).copyupdate

dict comprehension 내에서 사전을 수동으로 연결할 수도 있습니다 .

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

또는 Python 2.6에서(제너레이터 표현식이 도입되었을 때 아마도 2.4부터):

dict((k, v) for d in dicts for k, v in d.items()) # iteritems in Python 2

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

from itertools import chain
z = dict(chain(x.items(), y.items())) # iteritems in Python 2

성능 분석

나는 올바르게 작동하는 것으로 알려진 사용법의 성능 분석만 할 것입니다. (자체 포함되어 있으므로 직접 복사하여 붙여넣을 수 있습니다.)

from timeit import repeat
from itertools import chain

x = dict.fromkeys('abcdefg')
y = dict.fromkeys('efghijk')

def merge_two_dicts(x, y):
    z = x.copy()
    z.update(y)
    return z

min(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())))

Python 3.8.1, NixOS:

>>> 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 -a
Linux nixos 4.19.113 #1-NixOS SMP Wed Mar 25 07:06:15 UTC 2020 x86_64 GNU/Linux

사전에 대한 리소스

 

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

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

이렇게 하면 원하는 대로 최종 딕셔너리를 넣고 z키 값을 두 번째( ) 딕셔너리 값 b으로 적절하게 재정의 합니다.y

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

list()Python 2를 사용하는 경우 호출 을 제거할 수도 있습니다 . z를 생성하려면:

>>> z = dict(x.items() + 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)
{'a': 1, 'c': 11, 'b': 10}

 

대안:

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

 

반응형