파이썬 딕셔너리 정렬, 직접 삽질하고 찾은 Key·Value 기준 3가지 방법

파이썬 딕셔너리를 Key나 Value 기준으로 정렬하는 방법은 sorted() 함수, lambda, operator.itemgetter() 세 가지가 대표적이고, 상황에 따라 성능 차이가 최대 40%까지 벌어집니다.

코딩 테스트 준비하면서 딕셔너리 정렬을 처음 만났거든요. 문제는 간단했어요. 학생 이름과 점수가 딕셔너리에 들어 있고, 점수 순으로 출력하라는 거였는데. 그때 저는 딕셔너리에 .sort()를 때렸다가 AttributeError를 받았습니다. 리스트처럼 되는 줄 알았던 거예요.

그때부터 삽질이 시작됐어요. 구글링하면 방법이 너무 많이 나오는데, 어떤 글은 dict.items()를 쓰라 하고, 어떤 글은 lambda를 쓰라 하고, 또 어떤 글은 itemgetter가 빠르다고 하고. 정리가 안 되더라고요. 그래서 제가 직접 세 가지를 다 돌려보고 성능까지 비교해 봤습니다.

딕셔너리 정렬이 필요했던 순간

파이썬 3.7부터 딕셔너리는 삽입 순서를 보장합니다. 이전 버전에서는 순서 자체가 보장되지 않았기 때문에 OrderedDict를 써야 했는데, 이제는 일반 dict만으로도 순서가 유지돼요. 근데 여기서 많은 분이 오해하는 게 하나 있어요.

“삽입 순서 보장”이랑 “정렬”은 완전히 다른 개념이거든요. 딕셔너리가 넣은 순서를 기억한다는 거지, 알아서 Key 순서대로 배열해 준다는 뜻이 아닙니다. 저도 처음에 이걸 헷갈려서 한참 헤맸어요.

실무에서 딕셔너리 정렬이 필요한 경우는 의외로 자주 옵니다. API 응답 데이터를 점수 순으로 보여줘야 할 때, 로그 데이터를 시간순으로 재배열할 때, 단어 빈도를 세고 높은 순서로 출력할 때. 이런 상황에서 sorted()를 제대로 쓸 줄 아느냐가 코드 품질을 확 바꿔요.

한 가지 더. 딕셔너리는 리스트와 달리 in-place 정렬이 불가능합니다. .sort() 메서드가 없어요. 항상 sorted()로 새로운 정렬된 결과를 만들어야 하는데, 이 결과는 리스트로 나옵니다. 다시 딕셔너리로 바꾸려면 dict()로 감싸야 하고요. 이 흐름을 알고 있으면 세 가지 방법 모두 이해하기 쉬워요.

sorted() 함수로 Key 기준 정렬하기

첫 번째 방법이 가장 직관적이에요. sorted() 함수에 딕셔너리를 그대로 넣으면 Key만 뽑혀서 정렬된 리스트가 나옵니다. 근데 이러면 Value가 날아가니까, .items()를 써서 Key-Value 쌍 전체를 넘기는 게 핵심이에요.

기본 코드는 이렇습니다. scores라는 딕셔너리가 있다고 할게요. scores = {‘charlie’: 85, ‘alice’: 92, ‘bob’: 78} 이런 식으로요. 여기서 dict(sorted(scores.items()))를 실행하면 Key인 이름의 알파벳 순서대로 {‘alice’: 92, ‘bob’: 78, ‘charlie’: 85}가 됩니다.

이게 되는 이유가 있어요. sorted()가 .items()에서 나온 튜플 리스트를 받으면, 튜플끼리 비교할 때 첫 번째 요소(Key)부터 비교하거든요. 파이썬의 튜플 비교는 사전식(lexicographic) 정렬이라서, Key가 다르면 Key 기준으로 바로 순서가 결정됩니다.

근데 여기서 제가 실수했던 게 하나 있었어요. Key가 숫자와 문자열이 섞여 있으면 TypeError가 터집니다. 파이썬 3에서는 서로 다른 타입끼리 비교가 안 되거든요. 혼합 타입 딕셔너리를 정렬하려면 key 파라미터에 str() 같은 변환 함수를 넣어줘야 해요.

StackOverflow 벤치마크에 따르면, Key 기준 정렬에서 가장 빠른 방식은 {k: d[k] for k in sorted(d)}인 딕셔너리 컴프리헨션이에요. 1,000개 항목 기준으로 약 296μs가 나왔는데, dict(sorted(d.items()))보다 약 30% 정도 빠릅니다. sorted()에 딕셔너리를 직접 넣으면 Key만 정렬되고, 컴프리헨션에서 d[k]로 Value를 가져오는 구조라 중간 튜플 생성이 없어서 빠른 거예요.

lambda로 Value 기준 정렬하는 법

두 번째 방법이 실무에서 가장 많이 쓰이는 패턴이에요. Value 기준으로 정렬하려면 sorted()의 key 파라미터에 lambda 함수를 넣어야 합니다. 코드로 보면 이렇습니다.

sorted(scores.items(), key=lambda x: x[1]) — 이게 핵심 한 줄이에요. .items()가 (key, value) 튜플을 반환하니까, lambda x: x[1]은 “각 튜플의 두 번째 요소(Value)로 비교해라”는 뜻입니다. 결과를 dict()로 감싸면 Value 기준으로 정렬된 새 딕셔너리가 만들어져요.

💬 직접 써본 경험

코딩 테스트에서 단어 빈도 분석 문제를 풀었는데, Counter로 빈도를 센 뒤 상위 5개를 뽑아야 했거든요. 처음에 Counter.most_common(5)를 몰라서 lambda 정렬로 해결했어요. dict(sorted(word_count.items(), key=lambda x: x[1], reverse=True)[:5]) 이런 식으로요. 동작은 했는데 나중에 most_common()을 알고 나서 좀 허탈했습니다. 그래도 원리를 이해하니까 응용이 자유로워지더라고요.

lambda 방식에서 흔히 실수하는 것 중 하나가 x[0]과 x[1]을 헷갈리는 거예요. x[0]은 Key, x[1]은 Value입니다. 저는 이걸 구분하려고 lambda kv: kv[1]처럼 변수명을 kv로 쓰는 습관을 들였어요. k와 v의 쌍이라는 걸 직관적으로 알 수 있으니까요.

중첩 딕셔너리를 정렬할 때도 lambda가 유용합니다. 예를 들어 users라는 딕셔너리 안에 각 유저별로 score 키가 있다면, sorted(users.items(), key=lambda x: x[1][‘score’])처럼 접근할 수 있어요. 단, 특정 키가 빠진 항목이 있으면 KeyError가 나니까 .get()으로 기본값을 줘야 안전합니다.

itemgetter()로 성능까지 챙기는 정렬

세 번째 방법은 operator 모듈의 itemgetter()를 쓰는 겁니다. from operator import itemgetter를 먼저 해줘야 하고, 사용법은 sorted(scores.items(), key=itemgetter(1)) 이렇게요. lambda x: x[1]과 기능은 똑같습니다.

그럼 왜 이걸 쓰냐고요? 속도 때문이에요. itemgetter()는 C로 구현된 내장 함수라서 lambda보다 확실히 빠릅니다. Real Python 벤치마크에서 itemgetter가 lambda 대비 약 40% 빠른 결과를 보여줬고, StackOverflow의 1,000개 항목 테스트에서도 itemgetter(1) 기반이 355μs, lambda 기반이 393μs로 차이가 났어요.

솔직히 데이터 100개 정도면 체감 차이 없습니다. 근데 10만 개, 100만 개 단위로 올라가면 얘기가 달라져요. 23만 개 문자열 딕셔너리 벤치마크에서 itemgetter는 468ms, lambda는 492ms가 나왔거든요. 대규모 데이터를 반복 정렬하는 배치 작업이라면 itemgetter가 확실한 선택입니다.

📊 실제 데이터

StackOverflow 벤치마크 결과 (1,000개 float 딕셔너리 기준) — Value 정렬 시 itemgetter(1): 355μs, lambda kv: kv[1]: 393μs, d.get 방식: 404μs. Key 정렬 시 컴프리헨션 + sorted(d): 296μs로 가장 빨랐고, dict(sorted(d.items())): 409μs로 가장 느렸습니다.

itemgetter의 단점도 있어요. import가 하나 필요하다는 거, 그리고 중첩 딕셔너리 접근 시에는 lambda만큼 유연하지 않다는 거예요. itemgetter(‘score’)로 단일 키 접근은 가능한데, x[1][‘score’] 같은 다단계 접근은 lambda가 더 편합니다. 상황에 따라 골라 쓰는 게 맞아요.

내림차순 정렬과 실전에서 자주 틀리는 부분

오름차순이 기본이니까, 내림차순으로 바꾸려면 reverse=True만 추가하면 됩니다. sorted(scores.items(), key=lambda x: x[1], reverse=True) 이런 식으로요. 간단하죠.

근데 실전에서 자주 겪는 문제가 있어요. 정렬 결과가 리스트라는 걸 잊고 딕셔너리 메서드를 쓰려는 거예요. sorted()의 반환값은 항상 리스트입니다. (key, value) 튜플의 리스트요. 이걸 다시 딕셔너리로 쓰려면 반드시 dict()로 감싸야 합니다.

제가 한번 프로젝트에서 실수했던 게, JSON 응답용 데이터를 정렬해서 반환하는데 dict()를 안 감싸서 튜플 리스트가 그대로 API 응답으로 나간 적이 있어요. 프론트 개발자가 “왜 배열 안에 배열이 오냐”고 해서 그때 알았습니다. 기본적인 실수인데 의외로 잘 빠지더라고요.

⚠️ 주의

딕셔너리를 정렬한다고 원본이 바뀌지 않습니다. sorted()는 항상 새로운 리스트를 반환하고, 원본 딕셔너리는 그대로예요. 정렬 결과를 쓰려면 반드시 새 변수에 할당해야 합니다. sorted_dict = dict(sorted(d.items())) 이렇게요. 그리고 Key와 Value 타입이 섞여 있으면(숫자 Key + 문자열 Key 등) TypeError가 발생하니, 정렬 전에 타입 통일을 확인하세요.

또 하나. 파이썬의 sorted()는 안정 정렬(stable sort)이에요. Timsort 알고리즘을 사용하는데, 같은 값을 가진 항목들은 원래 순서를 유지합니다. 그래서 Value가 같은 항목이 여러 개 있어도 원래 삽입 순서가 보존돼요. 이 특성을 활용하면 “1차 정렬: Value, 2차 정렬: Key” 같은 다중 조건 정렬도 할 수 있습니다. sorted()를 두 번 연속 적용하면 되거든요. 2차 조건으로 먼저 정렬하고, 1차 조건으로 다시 정렬하는 식으로.

3가지 방법 성능 비교와 상황별 선택 기준

여기까지 세 가지 방법을 다 봤으니, 이제 정리해 볼게요. 뭘 써야 할지 감이 안 올 때 이 비교가 도움이 될 거예요.

구분 sorted() + items() lambda + key
정렬 기준 Key (기본값) Value (key=lambda)
속도 (1K 항목) ~296μs (컴프리헨션) ~393μs
import 필요 없음 없음
추천 상황 Key 정렬, 간단한 경우 Value 정렬, 중첩 구조

itemgetter()는 lambda와 같은 역할을 하지만 속도가 더 빠릅니다. 1,000개 기준 355μs로 lambda의 393μs보다 약 10% 빠르고, 대규모 데이터에서는 격차가 더 벌어져요. 다만 import가 필요하고, 중첩 접근이 불편하다는 단점이 있죠.

제 기준으로 정리하면 이래요. 코딩 테스트나 간단한 스크립트에서는 lambda가 제일 편합니다. 한 줄로 끝나고, import 없고, 가독성도 나쁘지 않아요. 반면에 데이터 파이프라인이나 배치 처리처럼 수십만 건을 반복 정렬해야 하는 환경이라면 itemgetter()가 맞아요. Key 기준 정렬만 필요한 간단한 경우라면 {k: d[k] for k in sorted(d)} 컴프리헨션이 가장 빠르고 깔끔하고요.

💡 꿀팁

딕셔너리를 정렬해서 다시 딕셔너리로 쓸 일이 많다면, 아예 함수로 만들어 두세요. def sort_dict(d, by=’key’, reverse=False): key_func = (lambda x: x[1]) if by == ‘value’ else (lambda x: x[0]) 이런 식으로 유틸 함수를 하나 만들어 놓으면 매번 sorted() 파라미터를 고민할 필요가 없어요. 프로젝트 초반에 utils.py에 넣어두면 두고두고 씁니다.

마지막으로, 정렬된 상태를 계속 유지해야 하는 경우라면 딕셔너리보다 다른 자료구조를 고려하는 게 나을 수 있어요. sortedcontainers 라이브러리의 SortedDict는 삽입할 때마다 자동으로 정렬 상태를 유지해 줍니다. 데이터가 계속 추가되는 환경에서 매번 sorted()를 호출하는 것보다 훨씬 효율적이에요.

자주 묻는 질문

Q. 딕셔너리를 정렬하면 원본도 바뀌나요?

아니요. sorted() 함수는 항상 새로운 리스트를 반환합니다. 원본 딕셔너리는 전혀 변하지 않아요. 정렬된 결과를 사용하려면 새 변수에 할당해야 합니다.

Q. Value가 같은 항목이 여러 개면 순서가 어떻게 되나요?

파이썬의 sorted()는 안정 정렬(Timsort)이기 때문에, Value가 동일한 항목들은 원래 딕셔너리에 있던 순서를 그대로 유지합니다. 이 특성을 활용해서 다중 조건 정렬도 가능해요.

Q. Key가 숫자인 딕셔너리와 문자열인 딕셔너리를 합쳐서 정렬할 수 있나요?

파이썬 3에서는 서로 다른 타입 간 비교가 허용되지 않아서 TypeError가 발생합니다. 정렬 전에 Key 타입을 통일하거나, key 파라미터에 str() 같은 변환 함수를 넣어 타입을 맞춰야 해요.

Q. OrderedDict를 지금도 써야 하나요?

파이썬 3.7 이상이라면 일반 dict도 삽입 순서를 보장하므로 대부분의 경우 OrderedDict가 필요 없습니다. 다만 두 딕셔너리의 순서까지 비교해야 하는 특수한 경우에는 OrderedDict의 == 비교가 순서를 고려하기 때문에 여전히 유용해요.

Q. 딕셔너리 컴프리헨션으로 정렬하는 게 dict(sorted(…))보다 빠른 이유가 뭔가요?

컴프리헨션 방식인 {k: d[k] for k in sorted(d)}는 Key만 정렬한 뒤 딕셔너리에서 직접 Value를 꺼내기 때문에, .items()로 튜플을 만들고 다시 풀어내는 과정이 생략돼요. 중간 튜플 객체 생성 비용이 없어서 약 20~30% 빠릅니다.

본 포스팅은 개인 경험과 공개 자료를 바탕으로 작성되었으며, 전문적인 의료·법률·재무 조언을 대체하지 않습니다. 정확한 정보는 해당 분야 전문가 또는 공식 기관에 확인하시기 바랍니다.

파이썬 딕셔너리 정렬은 결국 세 가지로 압축됩니다. Key 기준이면 sorted(d) 컴프리헨션, Value 기준이면 lambda, 대규모 데이터라면 itemgetter(). 이 세 가지만 확실히 알면 어떤 상황에서든 대응이 가능해요.

코딩 테스트 준비 중이라면 lambda 방식부터 손에 익히는 걸 추천합니다. 실무에서 대용량 처리를 해야 한다면 itemgetter까지 챙기시고요.


도움이 됐다면 댓글로 어떤 방법을 쓰고 계신지 알려주세요. 비슷한 파이썬 실전 팁이 궁금하시면 블로그 구독도 부탁드려요.

댓글 남기기