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
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 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 “Add Union Operators To dict”(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에 표준 타이핑 표기법을 사용할 수 있습니다:
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)에 추가되었습니다.
이제 불변 매핑을 공유해야 하는 사용 사례가 더 많아졌습니다.
PEP 416의 frozendict는 (PEP 603의 frozenmap처럼) 순서가 없었지만, 이제 frozendict는 삽입 순서를 유지합니다. 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를 상속하면, dict 메서드를 호출하여 불변인 frozendict를 변경할 수 있게 됩니다. 예를 들어 dict.__setitem__(frozendict, key, value)를 호출할 수 있게 됩니다.
dict 메서드로 frozendict를 수정하는 것을 막을 수도 있겠지만, 그러려면 frozendict를 명시적으로 제외해야 하며 이는 dict 성능에 영향을 줄 수 있습니다. 또한 일부 메서드에서 frozendict를 제외하는 것을 잊어버릴 위험도 더 큽니다.
frozendict가 dict를 상속하지 않으면 이런 문제가 없습니다.
보류된 아이디어
frozendict 리터럴을 위한 새 문법
frozendict 리터럴을 작성하기 위한 다양한 문법이 제안되었습니다.
필요하다면 새 문법은 나중에 추가할 수 있습니다.
dict를 frozendict로 변환하는 메서드
dict.freeze()와 같이, 가변 dict를 O(1) 복잡도로 불변 frozendict로 변환하는 여러 방법이 제안되었습니다. 이 방식은 dict의 내용을 frozendict로 옮기는 것으로, 그 결과 dict는 비게 됩니다. 또 다른 방식은 “쓰기 시 복사(copy-on-write)”를 사용하는 것으로, dict가 처음 수정될 때에만 복사합니다.
이러한 메서드는 필요하다면 나중에 추가할 수 있지만, 지금 당장 추가할 필요는 없다고 생각합니다. 또한 이러한 메서드를 추가한다면 list/tuple과 set/frozenset에도 비슷한 메서드를 추가하는 것이 좋을 것입니다. PEP 351 (동결 프로토콜)도 참고하십시오.
타입 어노테이션
frozendict 타입을 정의하기 위해 class TD(TypedDict, frozen=True) 또는 Frozen[MyTypedDict]를 추가하자는 제안이 있었습니다.
이러한 타입은 필요하다면 나중에 추가할 수 있다고 생각합니다.
참고 문헌
감사의 글
이 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.