본문으로 건너뛰기

리스트 안의 딕셔너리

파이썬 목차딕셔너리와 집합레슨 36 / 59

목차

리스트 안의 딕셔너리는 값 여럿을 묶은 딕셔너리를 리스트에 차례로 담은 것입니다. orders[1]['qty']처럼 바깥 리스트부터 한 겹씩 꺼냅니다.

주문 하나에는 메뉴와 수량처럼 값이 둘 이상입니다. 메뉴 리스트와 수량 리스트를 따로 두면 같은 자리 번호끼리 짝을 맞춰야 해서, 하나를 지우거나 차례를 바꾸면 짝이 어긋나기 쉽습니다. 주문 하나를 딕셔너리로 묶어 리스트에 담으면 한 주문의 값이 늘 함께 다닙니다.

3단계 중 1단계. 다음을 누르면 1번 줄부터 실행합니다.

1바깥부터 한 겹씩 꺼낸다

orders = [
    {'item': '커피', 'qty': 2},
    {'item': '라테', 'qty': 1},
]
second = orders[1]
print(second)
print(second['qty'])
print(orders[1]['qty'])
실행 결과
{'item': '라테', 'qty': 1}
1
1

1~4번 줄은 딕셔너리 둘을 담은 리스트를 만들고 이름 orders를 붙입니다. 5번 줄 orders[1]은 리스트의 1번 칸이 가리키는 딕셔너리이고, 7번 줄은 그 딕셔너리에서 키 'qty'의 값을 꺼냅니다. 8번 줄 orders[1]['qty']는 이 두 걸음을 한 줄로 쓴 것입니다. 왼쪽부터 orders[1]이 먼저 딕셔너리 하나가 되고, 그 딕셔너리에서 ['qty']를 꺼냅니다. 바깥은 리스트라 자리 번호로, 안쪽은 딕셔너리라 키로 꺼냅니다. 5번 줄은 새 딕셔너리를 만들지 않습니다. 1번 칸이 가리키는 바로 그 딕셔너리에 이름 second를 하나 더 붙입니다(규칙 3).

2칸은 딕셔너리를 가리킨다

리스트 레슨에서 칸 안의 수와 문자열은 칸에 바로 적었습니다. 정확히는 칸도 값을 가리킵니다. 그림은 바뀌는 값인 딕셔너리가 칸에 들면 칸에서 화살표를 그려, 리스트와 딕셔너리를 따로 보입니다. 그러면 리스트를 copy()로 복사할 때 무엇이 새로 생길까요? 무엇이 찍힐지 정한 뒤 펼쳐 보세요.

orders = [
    {'item': '커피', 'qty': 2},
    {'item': '라테', 'qty': 1},
]
backup = orders.copy()
orders[0]['qty'] = 3
print(backup[0]['qty'])
실행 결과 보기
실행 결과
3

예상과 다르면 그곳이 배울 곳입니다. copy()는 바깥 리스트만 새로 만듭니다. 새 리스트의 칸도 같은 딕셔너리를 가리켜서, orders[0]으로 바꾼 딕셔너리를 backup[0]으로 봐도 바뀌어 있습니다(규칙 3). 이렇게 한 겹만 새로 만드는 복사를 얕은 복사(shallow copy)라고 부릅니다.

3돌면서 꺼내기

for가 리스트의 칸을 하나씩 꺼내 order를 붙이면, order는 그 칸이 가리키는 딕셔너리입니다. 바퀴마다 키로 값을 꺼냅니다.

orders = [
    {'item': '커피', 'qty': 2},
    {'item': '라테', 'qty': 1},
]
for order in orders:
    item = order['item']
    print(item, order['qty'])
실행 결과
커피 2
라테 1

예제

주문 합계 내기

메뉴판 딕셔너리에서 메뉴마다 가격을 꺼내 수량을 곱해 더합니다.

menu = {
    '커피': 4000,
    '라테': 4500,
}
orders = [
    {'item': '커피', 'qty': 2},
    {'item': '라테', 'qty': 1},
]
total = 0
for order in orders:
    price = menu[order['item']]
    qty = order['qty']
    total = total + price * qty
print(total)
실행 결과
12500

11번 줄은 대괄호 안의 order['item']을 먼저 계산해 '커피'를 얻고, 그 키로 menu['커피']가 4000을 꺼냅니다. 커피 2잔 8000원에 라테 4500원을 더해 12500입니다.

자주 틀리는 곳

틀린 생각: 바깥 리스트를 copy()하면 안의 딕셔너리도 새로 생긴다.

copy()가 리스트를 복사하니, 리스트에 든 것도 모두 새로 생길 것 같습니다. 새로 생기는 것은 바깥 리스트 하나입니다.

orders = [{'qty': 2}]
backup = orders.copy()
orders[0]['qty'] = 3
print(backup)
실행 결과
[{'qty': 3}]

backup도 0번 칸이 orders와 같은 딕셔너리를 가리켜, 3으로 바뀐 수량이 보입니다.

맞는 규칙: copy()는 바깥 리스트만 새로 만들고, 새 리스트의 칸도 같은 딕셔너리를 가리킵니다. 안쪽 딕셔너리를 바꾸면 두 리스트 어느 쪽으로 봐도 바뀌어 있습니다(규칙 3).

틀린 생각: 리스트 안 딕셔너리의 값은 키만 대면 바로 꺼낼 수 있다.

'qty'를 찾으면 리스트 안 딕셔너리에서 알아서 찾아 줄 것 같습니다. 바깥은 리스트라 먼저 자리 번호로 딕셔너리 하나를 꺼내야 합니다.

orders = [
    {'item': '커피', 'qty': 2},
    {'item': '라테', 'qty': 1},
]
print(orders['qty'])
실행 결과
Traceback (most recent call last):  File "main.py", line 5, in <module>    print(orders['qty'])          ~~~~~~^^^^^^^TypeError: list indices must be integers or slices, not str

맨 아래 줄은 '리스트의 자리 번호는 정수나 자르기여야 하고 문자열은 안 된다'는 뜻입니다.

맞는 규칙: 바깥 리스트는 자리 번호로, 안쪽 딕셔너리는 키로 꺼냅니다. orders[1]['qty']처럼 바깥부터 한 겹씩 씁니다.

확인 문제

1. 1절 코드에서 5번 줄부터를 바꿨습니다. 무엇이 찍히는지 두 줄을 글자 그대로 쓰세요.

orders = [
    {'item': '커피', 'qty': 2},
    {'item': '라테', 'qty': 1},
]
last = orders[-1]
print(last['item'])
print(orders[0]['qty'] + 1)
힌트

바깥 리스트에서 어느 칸을 꺼내는지 먼저 정하고, 그 딕셔너리에서 키로 꺼냅니다.

정답
실행 결과
라테
3

orders[-1]은 마지막 칸이 가리키는 라테 딕셔너리라서 last['item']은 라테입니다. orders[0]['qty']는 커피 딕셔너리의 수량 2이고, 1을 더해 3입니다.

흔한 오답은 첫 줄을 커피로 쓰는 것입니다. -1을 첫 칸으로 본 것입니다. 음수 번호는 뒤에서부터 셉니다.

2. 장바구니에 담은 과일 수를 모두 더해 5를 찍으려 합니다. 2번 줄의 배 수량이 4면 7이 찍혀야 합니다. 빈칸을 채우세요.

cart = [
    {'name': '배', 'qty': 2},
    {'name': '귤', 'qty': 3},
]
count = 0
for fruit in cart:
    qty = ________
    count = count + qty
print(count)
힌트

6번 줄에서 fruit가 무엇을 가리키는지 봅니다.

정답

fruit['qty']입니다.

cart = [
    {'name': '배', 'qty': 2},
    {'name': '귤', 'qty': 3},
]
count = 0
for fruit in cart:
    qty = fruit['qty']
    count = count + qty
print(count)
실행 결과
5

바퀴마다 fruit는 칸이 가리키는 딕셔너리라서, 키 'qty'로 수량을 꺼냅니다.

다르게 써도 되는 곳: fruit.get('qty', 0)도 같습니다.

흔한 오답은 cart['qty']입니다. 바깥 리스트에 키를 대어 TypeError로 멈춥니다. 키만 대면 리스트 안 딕셔너리의 값을 바로 꺼낼 수 있다고 본 것입니다. 안쪽 딕셔너리는 fruit입니다.

3. 장바구니 리스트를 copy()로 복사해 두고 원래 리스트를 고쳤습니다. 무엇이 찍히는지 두 줄을 글자 그대로 쓰세요.

cart = [
    {'name': '사과', 'qty': 3},
    {'name': '배', 'qty': 2},
]
saved = cart.copy()
cart[1]['qty'] = 5
extra = {'name': '귤', 'qty': 1}
cart.append(extra)
print(len(saved))
print(saved[1]['qty'])
힌트

6번 줄은 안쪽 딕셔너리를, 8번 줄은 바깥 리스트를 바꿉니다. 각각 saved와 같은 것인지 따로 봅니다.

정답
실행 결과
2
5

copy()는 바깥 리스트를 새로 만들어서, 8번 줄이 cart에 더한 귤은 saved에 없습니다. 그래서 saved의 칸은 둘입니다. 안쪽 딕셔너리는 새로 생기지 않아 saved의 1번 칸도 cart와 같은 배 딕셔너리를 가리키므로, 6번 줄에서 바꾼 수량 5가 보입니다.

흔한 오답은 둘째 줄을 2로 쓰는 것입니다. copy()가 안쪽 딕셔너리까지 새로 만든다고 본 것입니다(복사된다는 생각). 새로 생기는 것은 바깥 리스트뿐입니다.

5번 줄 다음에 second['qty'] = 3을 더 쓰면 찍히는 줄을 모두 봅니다.

처음 펼 때 약 6MB를 받습니다.컴퓨터에서 해 보기

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

파이썬 목차로