PEP 814 – frozendict 내장 타입 추가
- Author:
- Victor Stinner <vstinner at python.org>, Donghee Na <donghee.na at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Type:
- Standards Track
- Created:
- 12-Nov-2025
- Python-Version:
- 3.15
- Post-History:
- 13-Nov-2025
- Resolution:
- 11-Feb-2026
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
새로운 공개 불변 타입 frozendict가 builtins모듈에 추가됩니다.
frozendict는 의도하지 않은 수정을 방지하므로 설계상 안전할 것으로 예상합니다. 이 추가는 CPython의 표준 라이브러리뿐만 아니라, 신뢰할 수 있는 불변 딕셔너리 타입을 활용할 수 있는 서드 파티 유지 관리자에게도 도움이 됩니다.
근거
제안된 frozendict타입은 다음을 수행합니다:
collections.abc.Mapping프로토콜을 구현합니다.- 피클링을 지원합니다.
다음 사용 사례는 불변 매핑이 바람직한 이유를 보여 줍니다:
- 불변 매핑은 해시 가능하므로 딕셔너리 키 또는 집합 요소로 사용할 수 있습니다.
- 이러한 해시 가능 속성으로 인해
@functools.lru_cache()로 데코레이트된 함수는 불변 매핑을 인자로 받을 수 있습니다. 불변 매핑과 달리, 이러한 함수에 일반dict를 전달하면 오류가 발생합니다. - 불변 매핑을 함수 매개변수의 기본값으로 사용하면 변경 가능한 기본값의 문제를 피할 수 있습니다.
PyPI에는 이미 서드 파티 frozendict 및 frozenmap 패키지가 제공되고 있으며, 이는 불변 매핑에 대한 수요가 있음을 입증합니다.
사양
새로운 공개 불변 타입 frozendict가 builtins모듈에 추가됩니다. 이는 dict의 서브클래스가 아니라 object를 직접 상속합니다.
생성
frozendict는 dict와 유사한 생성 API를 구현합니다:
frozendict()는 비어 있는 새로운 불변 매핑을 생성합니다.frozendict(**kwargs)는**kwargs로부터 매핑을 생성합니다. 예:frozendict(x=1, y=2).frozendict(collection)은 전달된 컬렉션 객체로부터 매핑을 생성합니다. 전달된 컬렉션 객체는 다음 중 하나일 수 있습니다:dict입니다.- 다른
frozendict입니다. - 또는 키/값 튜플의 이터러블입니다.
frozendict(collection, **kwargs)는 앞의 두 생성을 결합합니다.
키는 해시 가능해야 하지만 값은 해시 불가능해도 됩니다. 해시 가능한 값을 사용하면 해시 가능한 frozendict가 생성됩니다.
dict에서 frozendict, 즉 frozendict(dict)를 생성하는 복잡도는 O(n)입니다. 항목은 얕은 복사됩니다.
삽입 순서가 보존됩니다.
반복
frozendict가 표준 collections.abc.Mapping 프로토콜을 구현하므로 반복에 필요한 모든 메서드를 지원합니다.:
assert list(m.items()) == [('foo', 'bar')]
assert list(m.keys()) == ['foo']
assert list(m.values()) == ['bar']
assert list(m) == ['foo']
frozendict를 반복할 때는 dict와 마찬가지로 삽입 순서를 사용합니다.
해싱 및 비교
frozendict 인스턴스는 튜플 객체와 마찬가지로 해시 가능하게 만들 수 있습니다.:
hash(frozendict(foo='bar')) # works
hash(frozendict(foo=['a', 'b', 'c'])) # error, list is not hashable
해시 값은 항목의 순서에 의존하지 않습니다. 해시 값은 키와 값으로 계산됩니다. hash(frozendict)의 의사 코드:
hash(frozenset(frozendict.items()))
동등성 검사는 항목의 순서에 의존하지도 않습니다. 예제:
>>> a = frozendict(x=1, y=2)
>>> b = frozendict(y=2, x=1)
>>> hash(a) == hash(b)
True
>>> a == b
True
frozendict와 dict를 비교할 수 있습니다. 예제:
>>> frozendict(x=1, y=2) == dict(x=1, y=2)
True
합집합 연산자
병합(|) 연산자를 사용하여 두 frozendict를 결합하거나 frozendict와 dict를 결합할 수 있습니다. 예제:
>>> frozendict(x=1) | frozendict(y=1)
frozendict({'x': 1, 'y': 1})
>>> frozendict(x=1) | dict(y=1)
frozendict({'x': 1, 'y': 1})
일부 키가 공통으로 존재하면 오른쪽 피연산자의 값이 사용됩니다.:
>>> frozendict(x=1, y=2) | frozendict(y=5)
frozendict({'x': 1, 'y': 5})
업데이트 연산자 |=는 frozendict를 제자리에서 수정하지 않고 새로운 frozendict를 생성합니다.:
>>> d = frozendict(x=1)
>>> copy = d
>>> d |= frozendict(y=2)
>>> d
frozendict({'x': 1, 'y': 2})
>>> copy # left unchanged
frozendict({'x': 1})
또한 PEP 584 “dict에 합집합 연산자 추가”를 참조하십시오.
복사
frozendict.copy()는 얕은 복사본을 반환합니다. CPython에서는 단순히 동일한 frozendict를 반환합니다(새 참조).
깊은 복사본을 얻으려면 copy.deepcopy()를 사용하십시오.
예제:
>>> import copy
>>> d = frozendict(mutable=[])
>>> shallow_copy = d.copy()
>>> deep_copy = copy.deepcopy(d)
>>> d['mutable'].append('modified')
>>> d
frozendict({'mutable': ['modified']})
>>> shallow_copy # modified!
frozendict({'mutable': ['modified']})
>>> deep_copy # unchanged
frozendict({'mutable': []})
타이핑
frozendict에 표준 typing 표기법을 사용할 수 있습니다.:
m: frozendict[str, int] = frozendict(x=1)
표현
frozendict는 표현에 특수 구문을 사용하지 않습니다. frozendict 인스턴스의 repr()는 다음과 같이 표시됩니다.
>>> frozendict(x=1, y=2)
frozendict({'x': 1, 'y': 2})
C API
다음 API를 추가하십시오.
PyAnyDict_Check(op)매크로PyAnyDict_CheckExact(op)매크로PyFrozenDict_Check()매크로PyFrozenDict_CheckExact()매크로PyFrozenDict_New(collection)함수PyFrozenDict_Type
frozendict가 dict 서브클래스가 아니더라도 PyDict_GetItemRef() 및 이와 유사한 “PyDict_Get” 함수와 함께 사용할 수 있습니다.
frozendict를 PyDict_SetItem() 또는 PyDict_DelItem()에 전달하면 TypeError가 발생합니다. frozendict에 PyDict_Check()를 적용한 결과는 거짓입니다.
dict와 frozendict의 차이
dict에는frozendict보다 더 많은 메서드가 있습니다.__delitem__(key)__setitem__(key, value)clear()pop(key)popitem()setdefault(key, value)update(*args, **kwargs)
- 모든 키와 값을 해시할 수 있다면
frozendict는hash(frozendict)로 해시할 수 있습니다.
표준 라이브러리에서 frozendict의 가능한 후보
frozendict를 도입하면 안전성을 높이고 의도하지 않은 수정을 설계상 방지할 수 있는 여러 표준 라이브러리 모듈을 식별했습니다. 또한 아래에 나열된 것 외에도 추가적인 잠재적 사용 사례가 있다고 생각합니다. 그러나 이는 해당 모듈 유지 관리자의 승인 없이 이러한 변경을 진행할 의도가 있다는 뜻은 아닙니다.
참고: 변수를 수정된 새 frozendict 또는 새로운 변경 가능한 dict에 다시 바인딩하는 것은 여전히 가능합니다.
Python 모듈
함수 결과에서 dict를 frozendict로 교체합니다.
email.headerregistry:ParameterizedMIMEHeader.params()(MappingProxyType을 교체)enum:EnumType.__members__()(MappingProxyType을 교체)
상수에 대해 dict를 frozendict로 교체합니다.
_opcode_metadata:_specializations,_specialized_opmap,opmap_pydatetime:specs(_format_time()에서)_pydecimal:_condition_mapbdb:_MonitoringTracer.EVENT_CALLBACK_MAPdataclasses:_hash_actiondis:deoptmap,COMPILER_FLAG_NAMESfunctools:_convertgettext:_binary_ops,_c2py_opsimaplib:Commands,Mon2numjson.decoder:_CONSTANTS,BACKSLASHjson.encoder:ESCAPE_DCTjson.tool:_group_to_theme_colorlocale:locale_encoding_alias,locale_alias,windows_localeopcode:_cache_format,_inline_cache_entriesoptparse:_builtin_cvtplatform:_ver_stages,_default_architectureplistlib:_BINARY_FORMATssl:_PROTOCOL_NAMESstringprep:b3_exceptionssymtable:_scopes_value_to_nametarfile:PAX_NUMBER_FIELDS,_NAMED_FILTERStoken:tok_name,EXACT_TOKEN_TYPEStomllib._parser:BASIC_STR_ESCAPE_REPLACEMENTStyping:_PROTO_ALLOWLIST
frozendict 타입을 허용합니다.
builtins:eval()및exec()(globals인자)
확장 모듈
상수에 대해 dict를 frozendict로 교체합니다.
errno:errorcode
PEP 416 frozendict와의 관계
2012년 이후(PEP 416), Python 생태계가 발전했습니다.
asyncio는 2014년(Python 3.4)에 추가되었습니다.- 자유 스레딩은 2024년(Python 3.13)에 추가되었습니다.
concurrent.interpreters는 2025년(Python 3.14)에 추가되었습니다.
이제 변경 불가능한 매핑을 공유할 사용 사례가 더 많습니다.
frozendict은 이제 삽입 순서를 보존하지만, PEP 416의 frozendict는 순서가 없었습니다(PEP 603의 frozenmap처럼). frozendict은 Python 3.6부터 삽입 순서를 보존하는 dict 구현에 의존합니다.
frozendict를 추가한 첫 번째 동기는 Python에서 샌드박스를 구현하는 것이었습니다. 이 PEP에서는 더 이상 그렇지 않습니다.
types.MappingProxyType은 2012년(Python 3.3)에 추가되었습니다. 이 타입은 해시할 수 없으며, 이를 상속하는 것도 불가능합니다. 변경할 수 있는 원본 딕셔너리를 가져오기도 쉽습니다. 예를 들어 gc.get_referents()를 사용할 수 있습니다.
PEP 603 frozenmap과의 관계
collections.frozenmap은 frozendict와 다른 특성을 가집니다.
frozenmap항목은 순서가 정해져 있지 않은 반면,frozendict은 삽입 순서를 보존합니다.frozenmap에는 다음과 같은 추가 메서드가 있습니다.including(key, value)excluding(key)union(mapping=None, **kw)
frozenmap을 변경하는 이러한 메서드의 복잡도는 O(1)입니다.- 매핑 조회(
mapping[key])의 복잡도는frozenmap에서는 O(log n)이고,frozendict에서는 O(1)입니다.
참조 구현
- https://github.com/python/cpython/pull/141508
frozendict은dict타입과 코드 대부분을 공유합니다.PyDictObject를 상속하고 추가ma_hash멤버를 가지는PyFrozenDictObject구조체를 추가합니다.
스레드 안전성
frozendict가 생성되면 얕은 불변성이 보장됩니다. 이는 다른 스레드가 해당 값을 수정하지 않는 한 동기화 없이 스레드 간에 안전하게 공유할 수 있다는 의미입니다.
거부된 아이디어
dict에서 상속합니다
frozendict가 dict를 상속하면, 불변 frozendict를 변경하기 위해 dict 메서드를 호출할 수 있게 됩니다. 예를 들어 dict.__setitem__(frozendict, key, value)를 호출할 수 있게 됩니다.
dict 메서드를 사용하여 frozendict를 수정하지 못하게 할 수도 있지만, 그러려면 frozendict를 명시적으로 제외해야 하며, 이는 dict 성능에 영향을 줄 수 있습니다. 또한 일부 메서드에서 frozendict를 제외하는 것을 잊을 위험도 더 큽니다.
frozendict가 dict를 상속하지 않으면 이러한 문제가 없습니다.
보류된 아이디어
frozendict 리터럴의 새 구문
frozendict 리터럴을 작성하기 위한 다양한 구문이 제안되었습니다.
필요하다면 나중에 새 구문을 추가할 수 있습니다.
dict를 frozendict로 변환하는 메서드
변경 가능한 dict를 변경 불가능한 frozendict로 O(1) 복잡도로 변환하는 여러 방법이 제안되었으며, 예를 들어 dict.freeze() 등이 있습니다. 한 가지 아이디어는 dict의 내용을 frozendict로 옮기는 것입니다. 그러면 dict가 비게 됩니다. 또 다른 아이디어는 “copy-on-write”를 사용하는 것입니다. dict를 처음 수정할 때만 복사합니다.
이러한 메서드는 필요할 경우 나중에 추가할 수 있지만, 지금 당장 추가해야 하는 것은 아니라고 봅니다. 또한 이러한 메서드를 추가한다면, list/tuple 및 set/frozenset에 대해서도 비슷한 메서드를 추가하면 좋을 것입니다. 또한 PEP 351 (동결 프로토콜)을 참조하십시오.
타입 어노테이션
class TD(TypedDict, frozen=True) 또는 Frozen[MyTypedDict]를 추가하여 frozendict 타입을 정의하자는 내용이 제안되었습니다.
이러한 타입은 필요할 경우 나중에 추가할 수 있다고 봅니다.
참조
감사의 말
이 PEP는 Yury Selivanov(PEP 603)의 선행 작업을 기반으로 합니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.