딕셔너리
리스트는 위치를 기준으로 값을 찾게 해줍니다. 하지만 이름으로 무언가를 찾고 싶을 때가 더 많습니다. "3번 항목을 줘"가 아니라 "지민의 점수를 알려줘" 같은 식으로요. 딕셔너리는 데이터를 키-값 쌍으로 저장합니다. 값을 찾을 때 위치가 아니라 키를 기준으로 찾는 것입니다.
딕셔너리 만들기
중괄호를 쓰고, 각 키와 값 사이에는 콜론을, 쌍 사이에는 쉼표를 넣습니다. 키는 거의 항상 문자열입니다. 값에는 숫자, 문자열, 다른 리스트, 심지어 다른 딕셔너리까지 무엇이든 올 수 있습니다.
player = {
"name": "지민",
"score": 87,
"level": 5,
"alive": True,
}값 접근하기
값을 가져올 때는 대괄호에 키를 넣습니다. 키가 존재하지 않으면 파이썬은 KeyError를 발생시킵니다. 키가 있는지 확신할 수 없을 때는 .get()을 사용하세요. 오류로 멈추는 대신 None을 반환하거나, 지정한 기본값을 반환합니다.
player = {"name": "지민", "score": 87}
player["name"] # "지민"
player["score"] # 87
player["lives"] # KeyError (키가 존재하지 않음)player.get("score") # 87
player.get("lives") # None (오류 없이 기본값인 None을 반환)
player.get("lives", 3) # 3 (키가 없으면 이 기본값을 사용)키가 없을 수도 있는 상황에서는 .get()이 더 안전합니다:
count = inventory.get("arrows", 0) # 딕셔너리에 "arrows"가 없으면 0d["key"]는 값을 건네주지만 키가 없으면 KeyError를 일으킵니다. 확신이 없을 때는 .get()을 쓰세요. 오류로 멈추는 대신 None을 반환하거나 직접 넘긴 기본값을 반환합니다. 이제는 뭐든 기본값을 지정해두는 습관이 생겼는데, 그 뒤로 키가 없어서 프로그램이 멈추는 일이 없어졌어요. 추가하고 수정하기
대괄호로 키에 값을 대입합니다. 키가 이미 있으면 값이 교체되고, 없으면 새 항목이 만들어집니다. 다른 딕셔너리 전체를 한 번에 합치고 싶다면 .update()를 사용하세요.
player = {"name": "지민", "score": 87}
player["score"] = 92 # 기존 값 수정
player["level"] = 5 # 새 키 추가extras = {"level": 5, "alive": True}
player.update(extras) # extras의 키로 추가하거나 덮어씀.update()를 쓰세요. 항목 제거하기
항목을 제거하는 방법은 네 가지입니다. .pop()은 키를 제거하고 그 값을 돌려줍니다. 기본값을 지정한 .pop()은 키가 없을 수도 있을 때 안전합니다. del은 반환값 없이 키를 제거합니다. .clear()는 딕셔너리 전체를 비웁니다.
player = {"name": "지민", "score": 87, "level": 5}
player.pop("level") # "level"을 제거하고 5를 반환
player.pop("lives", None) # 안전한 pop, 키가 없으면 None 반환
del player["score"] # "score"를 제거, 반환값 없음
player.clear() # 전부 제거기본값을 지정한 .pop()은 없을 수도 있는 키를 제거할 때 가장 안전한 방법입니다.
.pop(key)는 키를 제거하고 그 값을 돌려주고, .pop(key, None)은 키가 없어도 태연하게 넘어갑니다. del d[key]는 아무것도 반환하지 않고 제거하고, .clear()는 전부 비웁니다. .pop()에 기본값을 지정해 둔 덕분에 KeyError로 놀랄 일이 많이 줄었어요. 순회하기
딕셔너리의 서로 다른 부분을 순회할 수 있게 해주는 세 가지 뷰가 있습니다. 딕셔너리를 그대로 순회하면 키를 얻습니다. .values()는 값을 줍니다. .items()는 둘 다 한 번에 주는데, 가장 많이 쓰게 될 방법입니다. 각 쌍을 두 개의 이름으로 풀어내면 루프가 깔끔하고 읽기 쉬워집니다.
player = {"name": "지민", "score": 87, "level": 5}
for key in player: # 키를 순회 (가장 흔한 방식)
print(key)
for key in player.keys(): # 위와 동일, 명시적으로 키 뷰 사용
print(key)
for value in player.values(): # 값들
print(value)
for key, value in player.items(): # 둘 다, 가장 유용함
print(f"{key}: {value}").items()가 가장 많이 쓰게 될 방법입니다. 각 쌍을 두 개의 이름으로 풀어내면 루프를 읽기 쉬워집니다.
.values()는 값을, .items()는 둘 다 한 번에 주는데, 가장 자주 쓰게 되는 방법이에요. for key, value in player.items()처럼 각 쌍을 두 이름으로 풀어 쓰니 루프가 훨씬 읽기 편해졌어요. 멤버십 확인하기
in은 딕셔너리에 어떤 키가 존재하는지 확인합니다. 값이 아니라 키만 확인합니다. 존재하지 않는지 확인하려면 not in을 사용하세요.
player = {"name": "지민", "score": 87}
"name" in player # True
"lives" in player # False
"lives" not in player # Truein은 키만 확인합니다. 값을 확인하려면 in player.values()를 쓰면 되지만, 이런 경우는 거의 필요하지 않습니다.
in은 딕셔너리에 키가 있는지 알려주는데, 항상 키만이고 값은 절대 확인하지 않습니다. 딕셔너리가 아무리 커져도 속도는 그대로 유지됩니다. 무언가가 없는지 확인하고 싶으면 not in으로 바꿔 쓰세요. 딕셔너리 중첩하기
값 자체가 딕셔너리일 수도 있습니다. 여러 단계를 가진 구조화된 데이터, 예를 들어 통계 섹션이 있는 플레이어나 하위 섹션이 있는 설정 파일을 이런 식으로 표현합니다. 대괄호 두 쌍으로 중첩된 값에 접근합니다. 첫 번째는 바깥쪽 키를, 두 번째는 안쪽 키를 고릅니다.
users = {
"지민": {"score": 87, "level": 5},
"서준": {"score": 74, "level": 3},
}
users["지민"]["score"] # 87
users["서준"]["level"] # 3대괄호를 연달아 붙여서 접근합니다. 구조가 깊게 중첩되면 다루기 번거로워지니, 가능하면 얕게 유지하세요.
setdefault
.setdefault()는 키가 있으면 그 값을 읽고, 없으면 기본값으로 설정한 뒤 그 값을 반환합니다. 키가 반드시 존재하게 만들면서도 이미 있는 값을 덮어쓰고 싶지 않을 때 유용합니다.
inventory = {}
inventory.setdefault("arrows", 0) # "arrows": 0으로 설정하고 0을 반환
inventory.setdefault("arrows", 10) # "arrows"가 이미 있어서 변경 없이 0을 반환키 존재 여부를 먼저 확인하지 않고 그룹화된 구조를 쌓아 올릴 때 유용합니다:
groups = {}
for name, team in players:
groups.setdefault(team, []).append(name).setdefault(key, default)은 키가 있으면 읽고, 없으면 기본값으로 설정한 뒤 반환합니다. 이미 들어 있는 값을 망치지 않으면서 키가 존재하도록 만드는 깔끔한 방법이에요. 저는 주로 그룹화할 때 쓰는데, "이 키가 있나?" 하는 확인이 따로 필요 없어요. collections.defaultdict와 Counter
표준 라이브러리에는 흔한 패턴을 자동으로 처리해주는 딕셔너리 서브클래스가 두 가지 있습니다. defaultdict는 없는 키에 대해 기본값을 만들어주므로 KeyError를 겪을 일이 없습니다. Counter는 시퀀스에서 각 항목이 몇 번 나타나는지 세어서 딕셔너리 형태로 결과를 돌려줍니다.
defaultdict와 Counter는 표준 라이브러리에 있으므로 먼저 임포트해야 합니다. 임포트는 모듈 챕터에서 자세히 다룹니다.
from collections import defaultdict
groups = defaultdict(list)
for name, team in players:
groups[team].append(name) # team이 새로 생겨도 KeyError 없음from collections import Counter
words = ["cat", "dog", "cat", "bird", "cat", "dog"]
counts = Counter(words)
# Counter({'cat': 3, 'dog': 2, 'bird': 1})
counts.most_common(2) # [('cat', 3), ('dog', 2)]Counter를 쓰면 "반복문으로 개수 세기" 같은 상용구 코드를 많이 줄일 수 있습니다.
defaultdict는 없는 키에 대해 기본값을 채워주니까, 그룹화나 카운팅을 할 때 더 이상 KeyError를 겪지 않습니다. Counter는 시퀀스에서 각 항목이 몇 번 나오는지 세어서 딕셔너리로 돌려줍니다. 손으로 짠 카운팅 루프를 처음 Counter로 바꿔봤을 때, 코드가 절반으로 줄었어요. 실전 활용
점수 트래커를 만들고 모든 항목에 대한 요약을 출력해 보겠습니다:
scores = {"지민": 87, "서준": 74, "수아": 92, "도윤": 55}
total = sum(scores.values())
average = total / len(scores)
print(f"Players: {len(scores)}")
print(f"Average: {average:.1f}")
print(f"Highest: {max(scores.values())}")
print(f"Lowest: {min(scores.values())}")
print()
for name, score in scores.items():
print(f" {name}: {score}")
