
이미지 출처: Pexels · Polina Zimmerman · Pexels License
이름으로 점수를 찾는 코드입니다. 두 가지로 짰습니다. 둘 다 92를 출력합니다.
A. 리스트 두 개
names = ["홍길동", "김철수", "이영희"]
scores = [88, 92, 75]
i = names.index("김철수")
print(scores[i])
B. 딕셔너리 한 개
scores = {"홍길동": 88, "김철수": 92, "이영희": 75}
print(scores["김철수"])
A는 5줄, B는 3줄입니다. 줄 수 이야기를 하려는 게 아닙니다. 두 코드는 값을 찾는 방식이 다르고, 그 차이가 나중에 어디서 터지는지도 다릅니다.
세 라운드로 견주겠습니다.
대결 구도 — 번호로 찾느냐, 이름표로 찾느냐
리스트는 번호로 찾습니다. scores[1]의 1은 두 번째 칸이라는 뜻입니다. 값 자체와는 아무 상관이 없습니다.
딕셔너리는 이름표로 찾습니다. scores["김철수"]의 "김철수"는 몇 번째가 아니라 그 값에 붙여 둔 이름입니다.

왼쪽은 번호를 알아야 열리고, 오른쪽은 이름표를 그대로 대면 열립니다.
이름표를 붙이는 문법은 중괄호입니다.
scores = {"홍길동": 88, "김철수": 92, "이영희": 75}
이름표: 값 쌍을 쉼표로 잇고 {}로 감쌉니다. 이름표를 키(key), 담긴 것을 **값(value)**이라고 부릅니다.

키는 유일하고 바뀌지 않는 것만 쓸 수 있습니다. 값에는 제한이 없습니다.
키에 조건이 있는 이유는 서랍 이름표를 떠올리면 됩니다. 같은 이름표가 두 개 붙어 있으면 어느 서랍인지 알 수 없고, 이름표가 계속 바뀌면 찾을 수 없습니다. 그래서 리스트는 키가 될 수 없습니다. 리스트는 내용이 바뀌니까요.
d = {["a", "b"]: 1}
TypeError: unhashable type: 'list'
라운드 1 — 꺼내기
A는 두 단계입니다. 이름이 몇 번째인지 먼저 찾고(names.index), 그 번호로 점수를 꺼냅니다(scores[i]). B는 한 단계입니다.
단계 수보다 중요한 것은 A가 두 리스트의 순서가 맞다는 것을 전제한다는 점입니다. 그 전제가 깨지면 어떻게 되는지 보겠습니다.
names = ["홍길동", "김철수", "이영희"]
scores = [88, 92, 75]
names.remove("홍길동")
i = names.index("김철수")
print("김철수의 점수는", scores[i])📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
88이 나옵니다. 김철수의 점수는 92인데요.
names에서 홍길동을 지웠더니 김철수가 0번으로 당겨졌고, scores는 그대로라 0번이 여전히 홍길동의 88입니다. 에러는 나지 않습니다. 점수가 있고 번호도 유효하니 파이썬이 막을 이유가 없습니다.
딕셔너리는 이름표와 값이 한 몸이라 이런 어긋남이 생기지 않습니다.
scores = {"홍길동": 88, "김철수": 92, "이영희": 75}
del scores["홍길동"]
print("김철수의 점수는", scores["김철수"])
print(scores)📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
라운드 1은 딕셔너리입니다. 리스트 두 개를 나란히 두는 구조는 한쪽만 고치는 순간 조용히 틀립니다.
라운드 2 — 고치고 더하기
새 사람을 넣어 보겠습니다.
A는 두 줄이고 순서를 맞춰야 합니다.
names.append("박민수")
scores.append(60)
B는 한 줄입니다.
scores["박민수"] = 60
값을 고치는 것도 같은 문법입니다. 없으면 새로 만들고 있으면 덮어씁니다.
scores = {"홍길동": 88, "김철수": 92}
scores["박민수"] = 60
print(scores)
scores["박민수"] = 100
print(scores)📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
여기에 함정이 하나 있습니다. 덮어쓸 때도 아무 말이 없습니다. 이미 있는 키인 줄 모르고 다시 넣으면 앞의 값이 사라지는데, 에러도 경고도 나지 않습니다.
키가 중복될 수 없다는 성질은 만들 때도 똑같이 작동합니다.
scores = {"홍길동": 88, "홍길동": 99}
print(scores)
{'홍길동': 99}
같은 이름표 두 개를 붙인 게 아니라 뒤엣것이 앞엣것을 덮었습니다.
라운드 2도 딕셔너리입니다. 다만 덮어쓰기가 조용하다는 점은 기억해 둘 값입니다.
라운드 3 — 없는 것을 물었을 때
여기서는 딕셔너리가 한 대 맞습니다.
scores = {"홍길동": 88, "김철수": 92}
print(scores["박민수"])📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
KeyError: '박민수'
프로그램이 그 자리에서 멈춥니다. 뒤에 줄이 더 있어도 실행되지 않습니다.

오류 이름이 무엇이 잘못됐는지를, 따옴표 안이 어느 이름표인지를 알려 줍니다.
리스트도 없는 것을 물으면 멈춥니다. 다만 이름이 다릅니다.
| 자료형 | 없는 것을 물으면 | 오류 이름 |
|---|---|---|
| 리스트 | scores[99] | IndexError |
| 리스트 | names.index("없는이름") | ValueError |
| 딕셔너리 | scores["없는이름"] | KeyError |
멈추는 게 싫다면 get()이 있습니다.
scores = {"홍길동": 88, "김철수": 92}
print(scores.get("박민수"))
print(scores.get("박민수", 0))
print("박민수" in scores)📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
get(키)— 없으면None을 돌려줍니다. 멈추지 않습니다.get(키, 기본값)— 없으면 그 기본값을 돌려줍니다.키 in 딕셔너리— 있는지 먼저 물어봅니다.True또는False를 돌려줍니다.
그런데 get()이 항상 더 안전한 것은 아닙니다. 없는 키를 물었는데 None이 돌아오고 그걸 계속 쓰면, 문제가 실수한 줄이 아니라 한참 뒤에서 터집니다.
score = scores.get("박민수")
print(score + 10)
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
지난 편의 email.lower 실수와 같은 모양입니다. 조용히 넘어간 값은 나중에 더 찾기 어려운 자리에서 터집니다.
라운드 3은 리스트의 판정승도 딕셔너리의 판정승도 아닙니다. 둘 다 멈추고, 다른 점은 오류 이름뿐입니다. 여기서 갈리는 것은 자료형이 아니라 내가 get을 쓸지 in으로 먼저 확인할지입니다.
판정
| 관점 | 리스트 두 개 | 딕셔너리 | 판정 |
|---|---|---|---|
| 꺼내기 | index()로 번호를 먼저 찾는다 | 이름표로 바로 | 딕셔너리 |
| 짝 유지 | 한쪽만 고치면 조용히 어긋난다 | 이름표와 값이 한 몸 | 딕셔너리 |
| 더하기·고치기 | 두 리스트에 각각 | 한 줄로 둘 다 | 딕셔너리 |
| 없는 것 | IndexError / ValueError | KeyError | 무승부 |
| 순서가 뜻을 가질 때 | 1등·2등·대기 순번 | 순서로 꺼내는 도구가 아니다 | 리스트 |
| 같은 종류의 나열 | 점수만 모으기, 이름만 모으기 | 굳이 키를 지어내야 한다 | 리스트 |
언제 무엇을 쓰나
한 문장으로 가릅니다. 무엇으로 찾을 것인지를 보면 됩니다.
- 순서로 찾는다면 리스트입니다. 대기 1번·2번·3번이나 1등부터 10등까지처럼, 몇 번째인지가 뜻을 가질 때입니다.
- 이름으로 찾는다면 딕셔너리입니다. 이름으로 점수를, 상품코드로 가격을, 도시로 인구를 찾을 때처럼 몇 번째인지가 아무 뜻이 없을 때입니다.
리스트 두 개를 나란히 두고 순서를 맞춰 쓰고 있다면, 그건 대개 딕셔너리로 바꿔야 할 신호입니다.
한 가지 덧붙이면, 요즘 파이썬의 딕셔너리는 넣은 순서를 기억합니다. 그래서 출력하면 넣은 차례대로 나옵니다. 다만 그건 보여 줄 때의 이야기이고, 값을 꺼내는 수단은 여전히 번호가 아니라 키입니다. scores[0]은 0번 칸이 아니라 키가 0인 항목을 찾습니다.
딕셔너리 문법 일곱 줄
| 하는 일 | 문법 | 예 |
|---|---|---|
| 만들기 | {키: 값, ...} | {"a": 1, "b": 2} |
| 빈 것 만들기 | {} | d = {} |
| 꺼내기 | d[키] | d["a"] → 1 |
| 안전하게 꺼내기 | d.get(키, 기본값) | d.get("z", 0) → 0 |
| 넣기·고치기 | d[키] = 값 | d["c"] = 3 |
| 지우기 | del d[키] | del d["a"] |
| 있는지 | 키 in d | "a" in d → True |
| 개수 | len(d) | len(d) → 2 |
순회는 짝으로 돕니다.
scores = {"홍길동": 88, "김철수": 92, "이영희": 75}
for name in scores:
print(name)
print("---")
for name, score in scores.items():
print(name, score)
print("---")
print(sum(scores.values()) / len(scores))📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
for name in scores:— 그냥 돌면 키만 나옵니다. 값이 아닙니다.for name, score in scores.items():— 키와 값을 짝으로 받습니다.scores.values()— 값만 모읍니다. 평균 같은 계산에 씁니다. 키만 필요하면scores.keys()입니다.
첫 번째가 헷갈리기 쉬운 자리입니다. 리스트를 돌면 값이 나오는데 딕셔너리를 돌면 키가 나옵니다.
한 줄만 바꾸면 결과는 어떻게 될까요
미니 퀴즈
참고 자료
- 파이썬 공식 자습서 5.5 "딕셔너리" — 키의 조건, 만들기, del, in, 키 목록 https://docs.python.org/ko/3/tutorial/datastructures.html
- 파이썬 표준 라이브러리 4.10.1 "매핑 형 — dict" — get·items·values·keys 전체 목록 https://docs.python.org/ko/3/library/stdtypes.html
- 파이썬 공식 자습서 5.6 "루프 테크닉" — items()로 키와 값을 함께 돌기 https://docs.python.org/ko/3/tutorial/datastructures.html
포켓코딩(PYTHON2) 2편 · 1편(파이썬 문자열 메서드)에서 이어집니다. 예제 코드는 강사 개인 GitHub 저장소의 강의 자료를 바탕으로 다시 썼습니다. 다음 편은 튜플과 집합입니다. 괄호만 바꿨는데 왜 고칠 수 없게 되는지, 중복을 없애는 데 왜 집합이 가장 짧은지를 다룹니다.