PEP 584 – dict에 합집합 연산자 추가
- Author:
- Steven D’Aprano <steve at pearwood.info>, Brandt Bucher <brandt at python.org>
- BDFL-Delegate:
- Guido van Rossum <guido at python.org>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 01-Mar-2019
- Python-Version:
- 3.9
- Post-History:
- 01-Mar-2019, 16-Oct-2019, 02-Dec-2019, 04-Feb-2020, 17-Feb-2020
- Resolution:
- Python-Dev thread
Table of Contents
- 초록
- 동기
- 근거
- 명세
- 참조 구현
- 주요 반대 의견
- 기각된 아이디어
- 예시
- IPython/zmq/ipkernel.py
- IPython/zmq/kernelapp.py
- matplotlib/backends/backend_svg.py
- matplotlib/delaunay/triangulate.py
- matplotlib/legend.py
- numpy/ma/core.py
- praw/internal.py
- pygments/lexer.py
- requests/sessions.py
- sphinx/domains/__init__.py
- sphinx/ext/doctest.py
- sphinx/ext/inheritance_diagram.py
- sphinx/highlighting.py
- sphinx/quickstart.py
- sympy/abc.py
- sympy/parsing/maxima.py
- sympy/printing/ccode.py 및 sympy/printing/fcode.py
- sympy/utilities/runtests.py
- 관련 논의
- Copyright
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 내장 dict 클래스에 병합(|) 및 갱신(|=) 연산자를 추가할 것을 제안합니다.
Note
이 PEP가 수락된 후, 여러 다른 표준 라이브러리 매핑에도 새 연산자를 구현하기로 결정되었습니다.
동기
두 딕셔너리를 병합하는 현재의 방법들에는 몇 가지 단점이 있습니다:
dict.update
d1.update(d2)는 d1을 제자리에서 수정합니다. e = d1.copy(); e.update(d2)는 표현식이 아니며 임시 변수가 필요합니다.
{**d1, **d2}
딕셔너리 언패킹은 보기에 좋지 않고 쉽게 떠올릴 수 있는 방법도 아닙니다. 처음 보았을 때 그 의미를 짐작하거나, 이를 두 딕셔너리를 병합하는 “명백한 방법”으로 여길 수 있는 사람은 거의 없을 것입니다.
PEP 448에는 미안하지만, 더 단순한 맥락에서**d를 알고 있더라도 일반적인 Python 사용자에게 두 딕셔너리를 합쳐 새 딕셔너리를 만드는 방법을 묻는다면{**d1, **d2}를 떠올릴 사람이 많을지 의문입니다. 저 자신도 이 스레드가 시작되었을 때는 그것을 잊고 있었습니다!
{**d1, **d2}는 매핑의 타입을 무시하고 항상 dict를 반환합니다. type(d1)({**d1, **d2})는 호환되지 않는 __init__ 메서드를 가진 defaultdict 같은 dict 서브클래스에서 실패합니다.
collections.ChainMap
ChainMap은 안타깝게도 잘 알려져 있지 않으며 “명백한” 방법이라고 할 수 없습니다. 또한 중복된 키를 예상과 반대 순서로 처리합니다(“마지막에 나온 것이 우선” 대신 “처음 나온 것이 우선”). 딕셔너리 언패킹과 마찬가지로, 원하는 서브클래스를 따르게 하기가 까다롭습니다. 같은 이유로, type(d1)(ChainMap(d2, d1))는 일부 dict 서브클래스에서 실패합니다.
게다가 ChainMap은 기반 딕셔너리를 감싸므로, ChainMap에 쓰기를 하면 원래 딕셔너리가 수정됩니다.:
>>> d1 = {'spam': 1}
>>> d2 = {'eggs': 2}
>>> merged = ChainMap(d2, d1)
>>> merged['eggs'] = 999
>>> d2
{'eggs': 999}
dict(d1, **d2)
이 “깔끔한 요령”은 잘 알려져 있지 않으며, d2의 키가 모두 문자열일 때만 동작합니다.:
>>> d1 = {"spam": 1}
>>> d2 = {3665: 2}
>>> dict(d1, **d2)
Traceback (most recent call last):
...
TypeError: keywords must be strings
근거
새 연산자와 dict.update 메서드의 관계는 리스트 연결(+) 및 확장(+=) 연산자와 list.extend의 관계와 같습니다. 이는 |/|=와 set.update의 관계와는 다소 다르다는 점에 유의하십시오. 저자들은 제자리 연산자가 (list처럼) 더 넓은 범위의 타입을 받아들이도록 허용하는 것이 더 유용한 설계이며, 이항 연산자의 피연산자 타입을 (역시 list처럼) 제한하는 것이 양쪽에서 일어나는 복잡한 암묵적 타입 변환으로 인한 조용한 오류를 피하는 데 도움이 된다고 판단했습니다.
키 충돌은 가장 오른쪽 값을 유지하는 방식으로 해결됩니다. 이는 마지막에 나온 값이 항상 우선하는, 기존의 유사한 dict 연산들의 동작과 일치합니다.:
{'a': 1, 'a': 2}
{**d, **e}
d.update(e)
d[k] = v
{k: v for x in (d, e) for (k, v) in x.items()}
위의 모든 예는 같은 규칙을 따릅니다. 이 PEP는 이 동작이 단순하고 명백하며 대개 우리가 원하는 동작이고, 딕셔너리의 기본 동작이어야 한다는 입장을 취합니다. 이는 딕셔너리 합집합이 교환 법칙을 만족하지 않는다는 뜻입니다. 일반적으로 d | e != e | d입니다.
마찬가지로, 딕셔너리에서 키-값 쌍의 반복 순서도 위의 예와 같은 의미를 따르며, 새로 추가되는 각 키(와 그 값)는 현재 순서의 끝에 덧붙여집니다.
명세
딕셔너리 합집합은 왼쪽 피연산자에 오른쪽 피연산자를 병합한 새 dict를 반환하며, 두 피연산자는 각각 dict (또는 dict 서브클래스의 인스턴스)여야 합니다. 두 피연산자에 같은 키가 있으면 마지막에 나온 값(즉, 오른쪽 피연산자의 값)이 우선합니다:
>>> d = {'spam': 1, 'eggs': 2, 'cheese': 3}
>>> e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
>>> d | e
{'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
>>> e | d
{'cheese': 3, 'aardvark': 'Ethel', 'spam': 1, 'eggs': 2}
복합 대입 버전은 제자리에서 동작합니다:
>>> d |= e
>>> d
{'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
복합 대입은 하나의 위치 인자로 호출한 update 메서드와 똑같이 동작하므로, 매핑 프로토콜을 구현하는 모든 것(더 구체적으로는 keys와 __getitem__ 메서드를 가진 모든 것)이나 키-값 쌍의 이터러블도 받아들입니다. 이는 리스트뿐만 아니라 모든 이터러블을 받아들이는 list += 및 list.extend와 유사합니다. 위에서 계속:
>>> d | [('spam', 999)]
Traceback (most recent call last):
...
TypeError: can only merge dict (not "list") to dict
>>> d |= [('spam', 999)]
>>> d
{'spam': 999, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
새 키가 추가될 때, 오른쪽 매핑의 타입에 순서가 있다면 새 키의 순서는 그 매핑 안에서의 순서를 따릅니다.
참조 구현
저자 중 한 명이 C 구현을 작성했습니다.
근사적인 순수 Python 구현은 다음과 같습니다:
def __or__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = dict(self)
new.update(other)
return new
def __ror__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = dict(other)
new.update(self)
return new
def __ior__(self, other):
dict.update(self, other)
return self
주요 반대 의견
딕셔너리 합집합은 교환 법칙이 성립하지 않습니다
합집합은 교환 법칙이 성립하지만, 딕셔너리 합집합은 그렇지 않습니다(d | e != e | d).
답변
Python에는 교환 법칙이 성립하지 않는 합집합의 선례가 있습니다:
>>> {0} | {False}
{0}
>>> {False} | {0}
{False}
결과가 같을 수는 있지만, 둘은 분명히 다릅니다. 일반적으로 a | b는 b | a와 같은 연산이 아닙니다.
딕셔너리 합집합은 비효율적일 것입니다
매핑에 파이프 연산자를 제공하는 것은 확장성이 떨어지는 코드를 작성하도록 부추기는 셈입니다. 딕셔너리 합집합을 반복하는 것은 비효율적입니다. d | e | f | g | h는 임시 매핑 세 개를 만들고 없앱니다.
답변
같은 인자(논거)는 시퀀스 연결에도 적용됩니다.
시퀀스 이어 붙이기는 시퀀스에 있는 항목의 총수에 따라 커지므로 O(N**2)(이차) 성능으로 이어집니다. 딕셔너리 합집합에는 중복 키가 있을 가능성이 높으므로 임시 매핑이 그만큼 빠르게 커지지 않습니다.
많은 수의 리스트나 튜플을 이어 붙이는 경우가 드문 것처럼, 이 PEP의 저자들은 많은 수의 딕셔너리를 병합하는 경우도 드물 것이라고 생각합니다. collections.Counter는 많은 연산자를 지원하는 dict 서브클래스이지만, 많은 수의 Counter를 결합해서 성능 문제를 겪었다고 알려진 사례는 없습니다. 또한 저자들이 표준 라이브러리를 조사한 결과 딕셔너리를 세 개 이상 병합하는 사례는 없었으므로, 실제로 성능 문제가 될 가능성은 낮습니다… “N이 충분히 작으면 모든 것이 빠릅니다”.
성능이 문제가 될 만큼 많은 수의 딕셔너리를 병합해야 할 것으로 예상된다면, 명시적인 루프와 제자리 병합을 사용하는 편이 나을 수 있습니다:
new = {}
for d in many_dicts:
new |= d
딕셔너리 합집합은 손실이 있습니다
딕셔너리 합집합은 데이터를 잃을 수 있습니다(값이 사라질 수 있습니다). 다른 어떤 형태의 합집합에도 손실은 없습니다.
답변
이 인자(논거)의 앞부분이 왜 문제가 되는지는 분명하지 않습니다. dict.update()는 값을 버릴 수는 있지만 키를 버리지는 않습니다. 이는 예상된 동작이며, update()로 쓰든 |로 쓰든 관계없이 계속 예상된 동작으로 남을 것입니다.
다른 종류의 합집합도 되돌릴 수 없다는 의미에서 정보를 잃습니다. 합집합만으로는 두 피연산자를 되찾을 수 없습니다. a | b == 365… a와 b는 무엇입니까?
오직 한 가지 방법
딕셔너리 합집합은 Zen의 ‘오직 한 가지 방법’이라는 공안을 위반합니다.
답변
그런 격언은 없습니다. “오직 한 가지 방법”은 오래전 Perl 커뮤니티에서 시작된 Python에 대한 중상입니다.
하나보다 많은 방법
좋습니다, Zen에는 오직 한 가지 방법만 있어야 한다는 말은 없습니다. 하지만 “하나보다 많은 방법”을 허용하는 것을 금지하기는 합니다.
답변
그런 금지는 없습니다. “파이썬의 선(Zen of Python)”은 “명백한 방법은 오직 하나”를 선호한다고 표현할 뿐입니다:
There should be one-- and preferably only one --obvious way to do
it.
여기서 강조하는 점은 “그것”을 하는 명백한 방법이 있어야 한다는 것입니다. 딕셔너리 갱신 연산의 경우, 우리가 하고 싶어 할 만한 서로 다른 연산이 적어도 두 가지 있습니다.
- 딕셔너리를 제자리에서 갱신하기: 명백한 방법은
update()메서드를 사용하는 것입니다. 이 제안이 수락되면|=복합 대입 연산자도 동작하겠지만, 이는 복합 대입이 정의된 방식에 따른 부수 효과입니다. 어느 것을 선택할지는 취향의 문제입니다. - 기존의 두 딕셔너리를 병합하여 새로운 세 번째 딕셔너리 만들기: 이 PEP는
|병합 연산자를 사용하는 것을 명백한 방법으로 제안합니다.
실제로 Python에서는 “오직 한 가지 방법”에 대한 이러한 선호가 자주 지켜지지 않습니다. 예를 들어, 모든 for 루프는 while 루프로 다시 작성할 수 있고, 모든 if 블록은 if/ else 블록으로 작성할 수 있습니다. 리스트, 집합, 딕셔너리 컴프리헨션은 모두 제너레이터 표현식으로 대체할 수 있습니다. 리스트는 이어붙이기를 구현하는 방법을 무려 다섯 가지나 제공합니다.
- 이어붙이기 연산자:
a + b - 제자리 이어붙이기 연산자:
a += b - 슬라이스 대입:
a[len(a):] = b - 시퀀스 언패킹:
[*a, *b] - extend 메서드:
a.extend(b)
“오직 한 가지 방법”에 어긋난다는 이유로 유용한 기능을 너무 엄격하게 거부해서는 안 됩니다.
딕셔너리 합집합은 코드를 이해하기 어렵게 만듭니다
딕셔너리 합집합을 쓰면 코드가 무슨 뜻인지 알기 어려워집니다. 특정인의 말을 인용하는 대신 반론을 바꾸어 표현하면 다음과 같습니다. “spam | eggs를 보더라도 spam과 eggs가 무엇인지 모르면 그것이 무엇을 하는지 알 수 없습니다”.
답변
매우 옳은 말입니다. 하지만 | 연산자가 다음 중 무엇이든 의미할 수 있는 오늘날에도 이는 똑같이 옳습니다:
int/bool비트 단위 ORset/frozenset합집합- 그 밖의 모든 오버로드된 연산
가능한 의미에 딕셔너리 합집합을 추가한다고 해서 코드를 이해하기가 더 어려워지는 것 같지는 않습니다. spam과 eggs가 매핑이라는 것을 파악하는 데에는 이들이 집합이나 정수라는 것을 파악하는 데 드는 것 이상의 수고가 필요하지 않습니다. 그리고 좋은 명명 규칙이 도움이 될 것입니다:
flags |= WRITEABLE # Probably numeric bitwise-or.
DO_NOT_RUN = WEEKENDS | HOLIDAYS # Probably set union.
settings = DEFAULT_SETTINGS | user_settings | workspace_settings # Probably dict union.
전체 set API는 어떻습니까?
딕셔너리는 “집합과 유사”하므로, 집합 연산자 전체, 즉 |, &, ^, -를 지원해야 합니다.
답변
이 PEP는 딕셔너리가 집합 연산자 전체를 지원해야 하는지에 대해 입장을 취하지 않으며, 이 문제는 이후의 PEP에 맡기고자 합니다(저자 중 한 명이 그러한 PEP의 초안 작성에 관심이 있습니다). 이후의 PEP에 도움이 되도록 간략한 요약을 아래에 제시합니다.
집합 대칭 차집합(^)은 명확하고 자연스럽습니다. 예를 들어, 다음과 같은 두 딕셔너리가 주어지면:
d1 = {"spam": 1, "eggs": 2}
d2 = {"ham": 3, "eggs": 4}
대칭 차집합 d1 ^ d2는 {"spam": 1, "ham": 3}이 됩니다.
집합 차집합(-) 역시 명확하고 자연스러우며, 이 PEP의 이전 버전에서는 이를 제안에 포함했습니다. 위의 딕셔너리가 주어지면 d1 - d2는 {"spam": 1}이 되고 d2 - d1은 {"ham": 3}이 됩니다.
집합 교집합(&)은 조금 더 문제가 있습니다. 두 딕셔너리에서 키의 교집합을 구하는 것은 쉽지만, 값을 어떻게 처리해야 할지는 분명하지 않습니다. 위의 두 딕셔너리가 주어지면 d1 & d2의 유일한 키가 "eggs"여야 한다는 것은 분명합니다. 그러나 “마지막으로 본 것이 우선”하는 방식은 다른 딕셔너리 연산(및 제안된 합집합 연산자)과 일관된다는 장점이 있습니다.
Mapping과 MutableMapping은 어떻습니까?
collections.abc.Mapping과 collections.abc.MutableMapping이 |와 |=를 정의해야 하며, 그러면 서브클래스가 새 연산자를 직접 정의할 필요 없이 그냥 상속받을 수 있습니다.
답변
이 클래스들에 새 연산자를 추가하는 것이 문제가 되는 주된 이유는 두 가지입니다:
- 현재 둘 다
copy메서드를 정의하지 않는데,|가 새 인스턴스를 만들려면 이 메서드가 필요합니다. MutableMapping에|=를 추가하는 것(또는Mapping에copy메서드를 추가하는 것)은 가상 서브클래스에 호환성 문제를 일으킵니다.
기각된 아이디어
거부된 의미 체계
충돌하는 키를 처리하기 위해 제안된 다른 해결책이 적어도 네 가지 있었습니다. 이러한 대안은 딕셔너리의 서브클래스에 맡깁니다.
예외 발생
이 동작에 사용 사례가 많거나 자주 유용할지는 분명하지 않지만, 딕셔너리 합집합 연산자를 사용할 때마다 try/except 절로 감싸야 하므로 번거로울 가능성이 큽니다.
값 더하기 (Counter가 +로 하는 것처럼)
기본 동작으로 사용하기에는 너무 특수합니다.
가장 왼쪽 값(처음 나온 값) 우선
이 동작에 사용 사례가 많은지는 분명하지 않습니다. 사실, 인자의 순서를 뒤집기만 하면 됩니다:
d2 | d1 # d1 merged with d2, keeping existing values in d1
값을 리스트로 이어 붙이기
이는 기본 동작이 되기에는 너무 특수할 가능성이 큽니다. 값이 이미 리스트인 경우 어떻게 해야 할지 분명하지 않습니다:
{'a': [1, 2]} | {'a': [3, 4]}
이 경우 결과는 {'a': [1, 2, 3, 4]}여야 합니까, 아니면 {'a': [[1, 2], [3, 4]]}여야 합니까?
기각된 대안
덧셈 연산자 사용
이 PEP는 원래 + 및 += 연산자를 사용하는 딕셔너리 덧셈 제안으로 시작되었습니다. 그 선택은 극도로 논란이 많은 것으로 드러났으며, 많은 사람이 연산자 선택에 대해 심각한 이의를 제기했습니다. 자세한 내용은 PEP의 이전 버전과 메일링 리스트 discussions를 참조하십시오.
왼쪽 시프트 연산자 사용
<< 연산자는 Python-Ideas에서 그다지 많은 지지를 받지 못한 것으로 보이지만, 큰 반대도 없었습니다. 아마도 가장 강력한 반대 의견은 Chris Angelico의 다음 발언이었을 것입니다
정보의 흐름을 나타내기 위해 연산자를 남용하는 “귀여움”은 C++가 그렇게 한 직후에 이미 식상해졌습니다.
새로운 왼쪽 화살표 연산자 사용
또 다른 제안은 새로운 연산자 <-를 만드는 것이었습니다. 안타깝게도 이는 모호합니다. d <- e는 d merge e를 의미할 수도 있고 d less-than minus e를 의미할 수도 있습니다.
메서드 사용
dict.merged() 메서드를 사용하면 연산자가 아예 필요하지 않게 됩니다. 한 가지 미묘한 점은 언바운드 메서드로 호출될 때와 바운드 메서드로 호출될 때 약간 다른 구현이 필요할 가능성이 크다는 것입니다.
언바운드 메서드로서의 동작은 다음과 비슷할 수 있습니다:
def merged(cls, *mappings, **kw):
new = cls() # Will this work for defaultdict?
for m in mappings:
new.update(m)
new.update(kw)
return new
바운드 메서드로서의 동작은 다음과 비슷할 수 있습니다:
def merged(self, *mappings, **kw):
new = self.copy()
for m in mappings:
new.update(m)
new.update(kw)
return new
장점
- 메서드가 연산자보다 더 찾기 쉽다고 볼 수 있습니다.
- 메서드는 임의 개수의 위치 인자와 키워드 인자를 받을 수 있으므로, 임시 딕셔너리를 생성하는 비효율을 피할 수 있습니다.
update메서드처럼(key, value)쌍의 시퀀스를 받습니다.- 메서드이므로 “처음 값 우선”, “고유 키” 등과 같은 다른 동작이 필요할 경우 서브클래스에서 쉽게 재정의할 수 있습니다.
단점
- 일반 인스턴스 메서드와
classmethod의 동작을 결합한 새로운 종류의 메서드 데코레이터가 필요할 가능성이 큽니다. 이 메서드를 오버라이드해야 하는 사람들을 위해 이 데코레이터는 공개되어야 합니다(반드시 내장일 필요는 없습니다). 개념 증명이 있습니다. - 연산자가 아닙니다. Guido는 연산자가 유용한 이유를 논의합니다. 다른 관점은 Alyssa Coghlan의 블로그 글을 참고하십시오.
함수 사용하기
메서드 대신 새로운 내장 함수 merged()를 사용합니다. 가능한 구현 중 하나는 다음과 같을 수 있습니다:
def merged(*mappings, **kw):
if mappings and isinstance(mappings[0], dict):
# If the first argument is a dict, use its type.
new = mappings[0].copy()
mappings = mappings[1:]
else:
# No positional arguments, or the first argument is a
# sequence of (key, value) pairs.
new = dict()
for m in mappings:
new.update(m)
new.update(kw)
return new
다른 대안으로, 임의의 키워드를 포기하고 충돌 시의 동작을 지정하는 단일 키워드 매개변수를 받는 방법이 있습니다:
def merged(*mappings, on_collision=lambda k, v1, v2: v2):
# implementation left as an exercise to the reader
장점
- 위의 메서드 방식이 가진 장점 대부분을 똑같이 가집니다.
- 충돌 시 다른 동작을 구현하는 데 서브클래스가 필요하지 않고, 함수만 있으면 됩니다.
단점
- 내장 함수로 둘 만큼 중요하지 않을 수 있습니다.
- “먼저 온 것이 우선” 같은 동작이 필요한 경우, 임의의 키워드 인자를 처리하는 능력을 잃지 않고 동작을 오버라이드하기가 어렵습니다.
예시
이 PEP의 저자들은 dict 합집합의 후보가 될 수 있는 딕셔너리 병합 관련 서드파티 라이브러리들을 조사했습니다.
이 목록은 저자 중 한 명의 컴퓨터에 마침 설치되어 있던 임의의 서드파티 패키지 중 일부를 바탕으로 한 대략적인 목록이며, 어떤 패키지의 현재 상태도 반영하지 않을 수 있습니다. 또한 (관련 없는) 추가 리팩터링이 가능할 수도 있지만, 공정한 비교를 위해 재작성한 버전에서는 새 연산자의 사용만 추가했다는 점에 유의하십시오. 또한 그렇게 하는 것이 효율적인 경우에는 결과를 하나의 표현식으로 줄였습니다.
IPython/zmq/ipkernel.py
변경 전:
aliases = dict(kernel_aliases)
aliases.update(shell_aliases)
변경 후:
aliases = kernel_aliases | shell_aliases
IPython/zmq/kernelapp.py
변경 전:
kernel_aliases = dict(base_aliases)
kernel_aliases.update({
'ip' : 'KernelApp.ip',
'hb' : 'KernelApp.hb_port',
'shell' : 'KernelApp.shell_port',
'iopub' : 'KernelApp.iopub_port',
'stdin' : 'KernelApp.stdin_port',
'parent': 'KernelApp.parent',
})
if sys.platform.startswith('win'):
kernel_aliases['interrupt'] = 'KernelApp.interrupt'
kernel_flags = dict(base_flags)
kernel_flags.update({
'no-stdout' : (
{'KernelApp' : {'no_stdout' : True}},
"redirect stdout to the null device"),
'no-stderr' : (
{'KernelApp' : {'no_stderr' : True}},
"redirect stderr to the null device"),
})
변경 후:
kernel_aliases = base_aliases | {
'ip' : 'KernelApp.ip',
'hb' : 'KernelApp.hb_port',
'shell' : 'KernelApp.shell_port',
'iopub' : 'KernelApp.iopub_port',
'stdin' : 'KernelApp.stdin_port',
'parent': 'KernelApp.parent',
}
if sys.platform.startswith('win'):
kernel_aliases['interrupt'] = 'KernelApp.interrupt'
kernel_flags = base_flags | {
'no-stdout' : (
{'KernelApp' : {'no_stdout' : True}},
"redirect stdout to the null device"),
'no-stderr' : (
{'KernelApp' : {'no_stderr' : True}},
"redirect stderr to the null device"),
}
matplotlib/backends/backend_svg.py
변경 전:
attrib = attrib.copy()
attrib.update(extra)
attrib = attrib.items()
변경 후:
attrib = (attrib | extra).items()
matplotlib/delaunay/triangulate.py
변경 전:
edges = {}
edges.update(dict(zip(self.triangle_nodes[border[:,0]][:,1],
self.triangle_nodes[border[:,0]][:,2])))
edges.update(dict(zip(self.triangle_nodes[border[:,1]][:,2],
self.triangle_nodes[border[:,1]][:,0])))
edges.update(dict(zip(self.triangle_nodes[border[:,2]][:,0],
self.triangle_nodes[border[:,2]][:,1])))
다음과 같이 다시 작성합니다:
edges = {}
edges |= zip(self.triangle_nodes[border[:,0]][:,1],
self.triangle_nodes[border[:,0]][:,2])
edges |= zip(self.triangle_nodes[border[:,1]][:,2],
self.triangle_nodes[border[:,1]][:,0])
edges |= zip(self.triangle_nodes[border[:,2]][:,0],
self.triangle_nodes[border[:,2]][:,1])
matplotlib/legend.py
변경 전:
hm = default_handler_map.copy()
hm.update(self._handler_map)
return hm
변경 후:
return default_handler_map | self._handler_map
numpy/ma/core.py
변경 전:
_optinfo = {}
_optinfo.update(getattr(obj, '_optinfo', {}))
_optinfo.update(getattr(obj, '_basedict', {}))
if not isinstance(obj, MaskedArray):
_optinfo.update(getattr(obj, '__dict__', {}))
변경 후:
_optinfo = {}
_optinfo |= getattr(obj, '_optinfo', {})
_optinfo |= getattr(obj, '_basedict', {})
if not isinstance(obj, MaskedArray):
_optinfo |= getattr(obj, '__dict__', {})
praw/internal.py
변경 전:
data = {'name': six.text_type(user), 'type': relationship}
data.update(kwargs)
변경 후:
data = {'name': six.text_type(user), 'type': relationship} | kwargs
pygments/lexer.py
변경 전:
kwargs.update(lexer.options)
lx = lexer.__class__(**kwargs)
변경 후:
lx = lexer.__class__(**(kwargs | lexer.options))
requests/sessions.py
변경 전:
merged_setting = dict_class(to_key_val_list(session_setting))
merged_setting.update(to_key_val_list(request_setting))
변경 후:
merged_setting = dict_class(to_key_val_list(session_setting)) | to_key_val_list(request_setting)
sphinx/domains/__init__.py
변경 전:
self.attrs = self.known_attrs.copy()
self.attrs.update(attrs)
변경 후:
self.attrs = self.known_attrs | attrs
sphinx/ext/doctest.py
변경 전:
new_opt = code[0].options.copy()
new_opt.update(example.options)
example.options = new_opt
변경 후:
example.options = code[0].options | example.options
sphinx/ext/inheritance_diagram.py
변경 전:
n_attrs = self.default_node_attrs.copy()
e_attrs = self.default_edge_attrs.copy()
g_attrs.update(graph_attrs)
n_attrs.update(node_attrs)
e_attrs.update(edge_attrs)
변경 후:
g_attrs |= graph_attrs
n_attrs = self.default_node_attrs | node_attrs
e_attrs = self.default_edge_attrs | edge_attrs
sphinx/highlighting.py
변경 전:
kwargs.update(self.formatter_args)
return self.formatter(**kwargs)
변경 후:
return self.formatter(**(kwargs | self.formatter_args))
sphinx/quickstart.py
변경 전:
d2 = DEFAULT_VALUE.copy()
d2.update(dict(("ext_"+ext, False) for ext in EXTENSIONS))
d2.update(d)
d = d2
변경 후:
d = DEFAULT_VALUE | dict(("ext_"+ext, False) for ext in EXTENSIONS) | d
sympy/abc.py
변경 전:
clash = {}
clash.update(clash1)
clash.update(clash2)
return clash1, clash2, clash
변경 후:
return clash1, clash2, clash1 | clash2
sympy/parsing/maxima.py
변경 전:
dct = MaximaHelpers.__dict__.copy()
dct.update(name_dict)
obj = sympify(str, locals=dct)
변경 후:
obj = sympify(str, locals=MaximaHelpers.__dict__|name_dict)
sympy/printing/ccode.py 및 sympy/printing/fcode.py
변경 전:
self.known_functions = dict(known_functions)
userfuncs = settings.get('user_functions', {})
self.known_functions.update(userfuncs)
변경 후:
self.known_functions = known_functions | settings.get('user_functions', {})
sympy/utilities/runtests.py
변경 전:
globs = globs.copy()
if extraglobs is not None:
globs.update(extraglobs)
변경 후:
globs = globs | (extraglobs if extraglobs is not None else {})
위 예시들은 | 연산자가 때로는 코드 줄 수를 줄이고 명확성을 높여 가독성을 뚜렷하게 향상한다는 것을 보여 줍니다. 그러나 | 연산자를 사용하는 다른 예시들은 길고 복잡한 단일 표현식으로 이어지며, PEP 8에서 정한 최대 줄 길이인 80열을 훨씬 넘을 수도 있습니다. 다른 모든 언어 기능과 마찬가지로, |가 코드를 개선하는지는 프로그래머가 스스로 판단해야 합니다.
관련 논의
메일링 리스트 스레드(결코 전체 목록이 아닙니다):
- + 및 +=를 사용한 딕셔너리 결합
- PEP: 딕셔너리 덧셈 및 뺄셈
- PEP 584: 내장 dict 클래스에 + 및 += 연산자 추가
- PEP 584 진전시키기(dict + 및 += 연산자)
- PEP 584: dict에 합집합 연산자 추가
- PEP 584 수락: dict에 합집합 연산자 추가
표현식 안에서 두 딕셔너리를 병합하는 기능은 자주 요청되는 기능입니다. 예를 들면 다음과 같습니다:
https://stackoverflow.com/questions/38987/how-to-merge-two-dictionaries-in-a-single-expression
https://stackoverflow.com/questions/6005066/adding-dictionaries-together-python
때때로 병합에 대해 다른 동작을 요청하는 사람들도 있습니다:
https://stackoverflow.com/questions/1031199/adding-dictionaries-in-python
https://stackoverflow.com/questions/877295/python-dict-add-by-valuedict-2
…딕셔너리를 키의 집합으로 취급하자는 제안도 하나 포함됩니다.
Ian Lee의 proto-PEP와 2015년의 논의. 추가 논의는 Python-Ideas에서 이루어졌습니다.
(주의 깊은 독자라면 이 PEP의 저자 중 한 명이 2015년에는 이 아이디어에 더 회의적이었다는 점을 알아차릴 것입니다.)
https://treyhunner.com/2016/02/how-to-merge-dictionaries-in-python/
https://code.tutsplus.com/tutorials/how-to-merge-two-python-dictionaries–cms-26230
이 PEP의 이전 초안에 대한 직접적인 반응으로, Serhiy Storchaka는 copy(); update() 관용구를 딕셔너리 언패킹으로 대체하자는 버그 트래커 티켓을 올렸습니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.