
파이썬 3.7부터 dict는 키를 삽입한 순서대로 순회한다는 것이 언어 명세로 보장됩니다. 덕분에 정렬 코드를 따로 쓰지 않고도 "가장 먼저 등록된 항목"을 첫 번째로 꺼내 쓰는 코드가 자연스럽게 자리 잡았습니다. 여기까지는 안심해도 좋습니다.
함정은 함수가 받는 인자가 항상 진짜 dict라는 보장이 없다는 데 있습니다. 파이썬은 collections.abc.MutableMapping을 상속하거나 __getitem__, keys 같은 메서드만 갖춰도 딕셔너리처럼 동작하는 객체를 쉽게 만들 수 있게 해 줍니다. 이런 사용자 정의 타입은 내부를 정렬된 트리로 구현했든 알파벳순으로 돌려주든 자유입니다. 겉보기에는 똑같이 []로 접근되고 for 문에 들어가지만, 순서 보장은 어디에도 적혀 있지 않습니다.
from collections.abc import MutableMapping
class SortedDict(MutableMapping): # dict가 아니지만 dict처럼 보입니다
def __init__(self):
self.data = {}
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value
def __delitem__(self, key):
del self.data[key]
def __iter__(self):
for key in sorted(self.data): # 삽입 순서가 아니라 정렬 순서입니다
yield key
def __len__(self):
return len(self.data)
def first_winner(votes):
for name in votes: # 삽입 순서를 암묵적으로 가정하고 있습니다
return name
ballots = SortedDict()
ballots['otter'] = 1281
ballots['bear'] = 803
print(first_winner(ballots)) # 'bear' — 먼저 넣은 것은 otter였습니다
first_winner는 잘못된 곳이 없어 보이지만, 순서 보장이 함수 밖의 타입에 달려 있습니다. 타입이 바뀌는 순간 예외도 경고도 없이 결과만 조용히 틀어지므로 추적이 대단히 어렵습니다.
def first_winner(votes: dict[str, int]) -> str:
# 순서에 의존하지 않고 기준을 코드로 명시합니다
return max(votes, key=votes.get)
def first_inserted(votes) -> str:
if not isinstance(votes, dict): # 실행 시점에 타입을 확인합니다
raise TypeError('dict 인스턴스가 필요합니다')
return next(iter(votes))
첫 번째 방식은 "가장 먼저 넣은 것"이라는 모호한 기준을 "득표가 가장 많은 것"이라는 명시적 기준으로 바꿔 순서 의존 자체를 없앱니다. 두 번째 방식은 순서가 정말 필요할 때 isinstance로 계약을 강제합니다. 타입 애너테이션(type annotation)에 dict를 적어 두면 mypy 같은 정적 분석기가 호출부에서 미리 잡아 주므로, 셋 중 무엇을 쓰든 안전성이 올라갑니다.
dict의 삽입 순서 보존은 언어가 보장하는 동작입니다.dict를 상속하지 않은 딕셔너리 유사 객체는 순서를 보존한다고 가정해서는 안 됩니다.isinstance로 검사하거나 타입 애너테이션과 정적 분석으로 계약을 강제합니다.설정 로딩 계층에서 자주 걸립니다. YAML 파서나 설정 라이브러리가 돌려주는 객체는 dict가 아니라 자체 매핑 클래스인 경우가 흔한데, "먼저 선언된 핸들러부터 등록한다" 같은 규칙을 순회 순서에 맡기면 라이브러리 버전이 올라갈 때 등록 순서가 뒤바뀔 수 있습니다. 우선순위 필드를 두고 명시적으로 정렬하는 편이 안전합니다.
ORM이나 데이터 검증 라이브러리의 결과를 다룰 때도 마찬가지입니다. RowMapping이나 Pydantic 모델의 dict() 반환값을 그대로 CSV 헤더 순서로 쓰기 전에, 컬럼 순서를 별도 리스트로 고정해 두면 스키마 변경에도 출력이 흔들리지 않습니다.