PEP 455 – collections에 키를 변환하는 딕셔너리 추가
- Author:
- Antoine Pitrou <solipsis at pitrou.net>
- BDFL-Delegate:
- Raymond Hettinger
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 13-Sep-2013
- Python-Version:
- 3.5
- Post-History:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 collections모듈에 새로운 데이터 구조를 제안하며, 이 PEP에서는 이를 “TransformDict”라고 부릅니다. 이 구조는 조회할 때 주어진 함수를 사용하여 키를 변환하지만, 읽을 때는 원래 키를 유지하는 가변 매핑입니다.
거부
근거는 https://mail.python.org/pipermail/python-dev/2015-May/140003.html 에서 확인할 수 있으며, 이전의 부분 검토 내용은 https://mail.python.org/pipermail/python-dev/2013-October/129937.html 에서 확인할 수 있습니다.
근거
이 패턴에는 수많은 특수화된 변형이 존재합니다. 가장 일반적인 것은 대소문자를 구분하지 않으면서 원래 대소문자를 보존하는 딕셔너리, 즉 대소문자를 구분하지 않는 방식으로 키를 일치시키지만 원래 대소문자는 유지하는 딕셔너리와 유사한 컨테이너입니다. 이는 네트워크 프로그래밍에서 매우 흔히 필요한 기능입니다. 많은 프로토콜이 메시지에 “키 / 값” 속성의 배열을 포함하며, 이때 키는 텍스트 문자열이고, 수신할 때는 대소문자를 무시하도록 지정되어 있지만 재전송할 때는 사양이나 사용자 지정에 따라 보존하거나 단순하지 않은 방식으로 정규화해야 하기 때문입니다.
또 다른 일반적인 요청은 일반적인 일치 방식 대신 각 키의 id()에 따라 키를 일치시키는 아이덴티티 딕셔너리입니다.
둘 다 키를 조회할 때 주어진 변환 함수를 키에 적용하는 더 일반적인 패턴의 사례입니다. 전자의 예에서는 해당 함수가 str.lower 또는 str.casefold이고, 후자의 예에서는 내장 id 함수입니다.
(이 패턴은 사용자에게 보이는 집합의 키를 내부 조회 집합으로 사상한다고 말할 수도 있습니다.)
의미론
TransformDict는 MutableMapping구현입니다. 즉, dict 자체와 표준 라이브러리의 다른 딕셔너리 유사 클래스처럼 잘 알려진 가변 매핑 API를 충실히 구현합니다. 따라서 이 PEP에서는 대부분 TransformDict 메서드의 의미론을 다시 설명하지 않습니다.
변환 함수는 전단사일 필요가 없으며, 대소문자를 구분하지 않는 예와 같이 엄밀히 전사일 수 있습니다(즉, 서로 다른 키가 같은 값을 조회할 수 있습니다).:
>>> d = TransformDict(str.casefold)
>>> d['SomeKey'] = 5
>>> d['somekey']
5
>>> d['SOMEKEY']
5
TransformDict는 항목을 생성할 때 처음 사용된 키를 유지합니다.:
>>> d = TransformDict(str.casefold)
>>> d['SomeKey'] = 1
>>> d['somekey'] = 2
>>> list(d.items())
[('SomeKey', 2)]
변환 함수가 해시 가능한 키를 반환하기만 한다면 원래 키는 해시 가능할 필요가 없습니다.:
>>> d = TransformDict(id)
>>> l = [None]
>>> d[l] = 5
>>> l in d
True
생성자
위의 예에서 보인 것처럼 TransformDict를 생성하려면 키 변환 함수를 첫 번째 인자로 전달해야 합니다(defaultdict를 생성할 때 첫 번째 인자로 팩토리 함수를 전달해야 하는 것과 유사합니다).
생성자는 특정 키-값 쌍으로 TransformDict를 초기화하는 데 사용할 수 있는 다른 선택적 인자도 받습니다. 이러한 선택적 인자는 dict 및 defaultdict생성자의 선택적 인자와 동일합니다.:
>>> d = TransformDict(str.casefold, [('Foo', 1)], Bar=2)
>>> sorted(d.items())
[('Bar', 2), ('Foo', 1)]
원래 키 가져오기
TransformDict에는 저장된 키를 해당 값과 함께 반환하는 조회 메서드도 있습니다.:
>>> d = TransformDict(str.casefold, {'Foo': 1})
>>> d.getitem('FOO')
('Foo', 1)
>>> d.getitem('bar')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'bar'
메서드 이름 getitem()은 가변 매핑의 표준 popitem()메서드를 따릅니다.
변환 함수 가져오기
TransformDict에는 변환 함수를 반환하는 간단한 읽기 전용 속성 transform_func이 있습니다.
대안 제안 및 질문
마지막 원래 키 유지
대부분의 python-dev 응답자는 마지막 키보다 사용자가 처음 제공한 키를 유지하는 것이 더 직관적이라고 보았습니다. 또한 서로 다르지만 같은 값을 갖는 키를 사용할 때 dict 객체 자체의 동작과도 일치합니다.:
>>> d = {}
>>> d[1] = 'hello'
>>> d[1.0] = 'world'
>>> d
{1: 'world'}
또한 첫 키를 유지하는 방식에서 마지막 키를 명시적으로 유지하는 것도 다음 접근 방식을 사용하면 여전히 가능합니다.:
d.pop(key, None)
d[key] = value
반대로 마지막 키를 유지하는 방식에서 첫 키를 유지하는 것은 컨테이너 코드의 일부를 다시 작성하지 않고는 가능해 보이지 않습니다.
인코더 / 디코더 쌍 사용
함수 쌍을 사용할 필요는 없습니다. 컨테이너가 원래 키를 유지하기 때문입니다. 또한 인코더 / 디코더 쌍을 사용하려면 변환이 전단사여야 하므로, 대소문자를 구분하지 않는 매칭과 같은 중요한 사용 사례가 불가능해집니다.
값에 대한 변환 함수 제공
딕셔너리 값은 조회에 사용되지 않으며, 그 의미는 컨테이너의 작동과 완전히 무관합니다. 따라서 “원래” 값과 “변환된” 값을 모두 둘 이유가 없습니다. 변환된 값은 어떤 용도로도 사용되지 않기 때문입니다.
일반적인 컨테이너가 아닌 특수화된 컨테이너 제공
특수화된 대소문자 구분 없는 딕셔너리 변형 대신 일반적인 TransformDict 구성을 제공하는 이유가 무엇인지 질문이 제기되었습니다. 그 이유는 코드 측면과 성능 측면에서 일반적인 구성을 제공하는 비용이 거의 동일하며, 더 많은 사용 사례를 충족할 수 있기 때문입니다.
대소문자 구분 없는 딕셔너리도 실제로 서로 다른 변환 함수를 사용할 수 있습니다. 예를 들어 str.lower, str.casefold 또는 경우에 따라 ASCII 호환 인코딩으로 인코딩된 텍스트를 처리할 때 bytes.lower를 사용할 수 있습니다.
기타 생성자 패턴
세르히 스토르차카가 두 가지 다른 생성자 패턴을 제안했습니다.
- 타입 팩토리 방식:
d = TransformDict(str.casefold)(Foo=1)
- 서브클래싱 방식:
class CaseInsensitiveDict(TransformDict): __transform__ = str.casefold d = CaseInsensitiveDict(Foo=1)
두 접근 방식 모두 옹호할 수 있지만, 표준 라이브러리의 확립된 관행을 따르지 않으므로 거부되었습니다.
구현
collections 모듈에 대한 패치는 버그 추적 시스템(http://bugs.python.org/issue18986)에서 추적되고 있습니다.
기존 작업
대소문자 구분 없는 딕셔너리는 많이 요청되는 기능입니다.
- http://twistedmatrix.com/documents/current/api/twisted.python.util.InsensitiveDict.html
- https://mail.python.org/pipermail/python-list/2013-May/647243.html
- https://mail.python.org/pipermail/python-list/2005-April/296208.html
- https://mail.python.org/pipermail/python-list/2004-June/241748.html
- http://bugs.python.org/msg197376
- http://stackoverflow.com/a/2082169
- http://stackoverflow.com/a/3296782
- http://code.activestate.com/recipes/66315-case-insensitive-dictionary/
- https://gist.github.com/babakness/3901174
- http://www.wikier.org/blog/key-insensitive-dictionary-in-python
- http://en.sharejs.com/python/14534
- http://www.voidspace.org.uk/python/archive.shtml#caseless
아이덴티티(identity) 딕셔너리도 요청된 바 있습니다:
- https://mail.python.org/pipermail/python-ideas/2010-May/007235.html
- http://www.gossamer-threads.com/lists/python/python/209527
표준 라이브러리의 여러 모듈이 객체 메모이제이션을 위해 아이덴티티 조회를 사용하는데, 예를 들어 pickle, json, copy, cProfile, doctest, _threading_local 등이 있습니다.
다른 언어들
C# / .Net
.Net에는 사용자 정의 IEqualityComparer를 지정할 수 있는 제네릭 Dictionary 클래스가 있습니다: http://msdn.microsoft.com/en-us/library/xfhwa508.aspx
이를 사용하는 것이 대소문자를 구분하지 않는 딕셔너리를 작성하는 권장 방법입니다: http://stackoverflow.com/questions/13230414/case-insensitive-access-for-generic-dictionary
Java
Java에는 특화된 CaseInsensitiveMap이 있습니다: http://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/CaseInsensitiveMap.html
또한 별도의 IdentityHashMap도 있습니다: http://docs.oracle.com/javase/6/docs/api/java/util/IdentityHashMap.html
C++
C++ 표준 템플릿 라이브러리는 사용자 정의 가능한 해시 및 동등성 함수를 갖춘 unordered_map을 제공합니다: http://www.cplusplus.com/reference/unordered_map/unordered_map/
Copyright
This document has been placed in the public domain.