Python의 dict 클래스는 딕셔너리를 다룰 때 유용한 메서드를 많이 제공해요.
그중 일부는 dicts 개념에서 이미 소개했어요.
여기서는 몇 가지를 더 살펴보고, 딕셔너리를 순회하고 조작하는 몇 가지 기법도 함께 알아볼게요.
setdefault()를 사용해요딕셔너리 개념에서 .get(key, <default value>)가 딕셔너리에 key가 없을 때 기존 value 또는 default value를 반환해서 KeyError를 피한다는 것을 이미 다뤘어요.
이 방식은 추가적인 오류 처리를 하고 싶지는 않지만 찾는 key가 반드시 있을 거라고 믿을 수는 없을 때 유용해요.
비슷하게 "안전한"(KeyError 없이) 삽입 연산을 위해서는 .setdefault(key, <default value>) 메서드가 있어요.
setdefault(key, <default value>)는 딕셔너리에 key가 있으면 그 value를 반환해요.
key가 없으면 (key, default value) 쌍을 _삽입_하고, 쓸 수 있도록 default value를 반환해요.
>>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'}
# Looking for the value associated with key "Rock Brown".
# The key does not exist, so it is added with the default value, and the value is returned.
>>> palette_I.setdefault('Rock Brown', '#694605')
'#694605'
# The (key, default value) pair has now been added to the dictionary.
>>> palette_I
{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Rock Brown': '#694605'}
fromkeys()를 사용해요여러 keys와 기본값으로 딕셔너리를 빠르게 채우려면 클래스 메서드 fromkeys(iterable, <default value>)가 keys의 반복 가능한 객체를 순회하며 새로운 dict를 만들어요.
모든 values는 지정한 default value로 설정돼요:
>>> new_dict = dict.fromkeys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'], 'fill in hex color here')
{'Grassy Green': 'fill in hex color here',
'Purple Mountains Majesty': 'fill in hex color here',
'Misty Mountain Pink': 'fill in hex color here'}
.keys(), .values(), .items() 메서드는 딕셔너리의 반복 가능한 뷰를 반환해요.
이 뷰를 사용하면 항목을 변경하지 않고 손쉽게 순회할 수 있어요.
뷰는 _동적_이기도 해요. 기반 딕셔너리 데이터가 바뀌면 관련된 view object에도 그 변화가 반영돼요:
>>> palette_I = {'Grassy Green': '#9bc400',
'Purple Mountains Majesty': '#8076a3',
'Misty Mountain Pink': '#f9c5bd'}
# Using .keys() returns a list of keys.
>>> palette_I.keys()
dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'])
# Using .values() returns a list of values.
>>> palette_I.values()
dict_values(['#9bc400', '#8076a3', '#f9c5bd'])
# Using .items() returns a list of (key, value) tuples.
>>> palette_I.items()
dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', '#8076a3'), ('Misty Mountain Pink', '#f9c5bd')])
# Views are dynamic. Changing values in the dict changes all of the associated views.
>>> palette_I['Purple Mountains Majesty'] = (128, 118, 163)
>>> palette_I['Deep Red'] = '#932432'
>>> palette_I.values()
dict_values(['#9bc400', (128, 118, 163), '#f9c5bd', '#932432'])
>>> palette_I.keys()
dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'Deep Red'])
>>> palette_I.items()
dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', (128, 118, 163)), ('Misty Mountain Pink', '#f9c5bd'), ('Deep Red', '#932432')])
.keys(), .values(), .items() 더 알아보기Python 3.7+에서 dicts는 항목이 삽입된 순서를 유지하기 때문에 .keys(), .values(), .items()를 사용하면 선입선출(FIFO) 순서로 순회할 수 있어요.
Python 3.8+에서는 뷰를 역순으로 순회할 수도 있어요.
덕분에 reversed(<dict>.keys()), reversed(<dict>.values()), reversed(<dict>.items())를 사용하면 키, 값 또는 (key, value) 쌍을 후입선출(LIFO) 순서로 순회할 수 있어요:
>>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
# Iterating in insertion order (First in, first out)
>>> for item in palette_II.items():
... print(item)
...
('Factory Stone Purple', '#7c677f')
('Green Treeline', '#478559')
('Purple baseline', '#161748')
# Iterating in the reverse direction. (Last in, first out)
>>> for item in reversed(palette_II.items()):
... print (item)
...
('Purple baseline', '#161748')
('Green Treeline', '#478559')
('Factory Stone Purple', '#7c677f')
딕셔너리에는 내장 정렬 메서드가 없어요.
하지만 내장 함수 sorted()를 dict.items()와 함께 사용하면 dict의 _뷰_를 정렬할 수 있어요.
그런 다음 정렬된 뷰로 새로운 딕셔너리를 만들 수 있어요.
순회와 마찬가지로 기본 정렬 기준은 딕셔너리의 keys예요.
# Default ordering for a dictionary is insertion order (First in, first out).
>>> color_palette = {'Grassy Green': '#9bc400',
'Purple Mountains Majesty': '#8076a3',
'Misty Mountain Pink': '#f9c5bd',
'Factory Stone Purple': '#7c677f',
'Green Treeline': '#478559',
'Purple baseline': '#161748'}
# The default sort order for a dictionary uses the keys.
>>> sorted_palette = dict(sorted(color_palette.items()))
>>> sorted_palette
{'Factory Stone Purple': '#7c677f',
'Grassy Green': '#9bc400',
'Green Treeline': '#478559',
'Misty Mountain Pink': '#f9c5bd',
'Purple Mountains Majesty': '#8076a3',
'Purple baseline': '#161748'}
.update()로 딕셔너리 합치기<dict_one>.update(<dict_two>)로 두 딕셔너리를 합칠 수 있어요.
이 메서드는 <dict_two>의 (key,value) 쌍을 가져와 <dict_one>에 써 넣어요:
>>> palette_I = {'Grassy Green': '#9bc400',
'Purple Mountains Majesty': '#8076a3',
'Misty Mountain Pink': '#f9c5bd'}
>>> palette_II = {'Factory Stone Purple': '#7c677f',
'Green Treeline': '#478559',
'Purple Baseline': '#161748'}
>>> palette_I.update(palette_II)
# Note that new items from palette_II are added.
>>> palette_I
{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple Baseline': '#161748'}
두 딕셔너리의 키가 겹치면 dict_one의 value가 dict_two의 해당 value로 덮어써져요:
>>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd',
'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
>>> palette_III = {'Grassy Green': (155, 196, 0), 'Purple Mountains Majesty': (128, 118, 163),
'Misty Mountain Pink': (249, 197, 189)}
>>> palette_I.update(palette_III)
# Overlapping values in palette_I are replaced with values from palette_III
>>> palette_I
{'Grassy Green': (155, 196, 0),
'Purple Mountains Majesty': (128, 118, 163),
'Misty Mountain Pink': (249, 197, 189),
'Factory Stone Purple': '#7c677f',
'Green Treeline': '#478559', 'Purple baseline': '#161748'}
|, |=) 연산자로 딕셔너리 병합하거나 갱신하기Python 3.9에서는 dicts를 병합하는 또 다른 방법인 union 연산자를 소개했어요.
dict_one | dict_two는 dict_one과 dict_two의 (key, value) 쌍으로 이루어진 새로운 딕셔너리를 만들어요.
두 딕셔너리가 키를 공유하면 dict_two의 값이 우선해요.
>>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'}
>>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
>>> new_dict = palette_I | palette_II
>>> new_dict
...
{'Grassy Green': '#9bc400',
'Purple Mountains Majesty': '#8076a3',
'Misty Mountain Pink': '#f9c5bd',
'Factory Stone Purple': '#7c677f',
'Green Treeline': '#478559',
'Purple baseline': '#161748'}
dict_one |= other는 <dict_one>.update(<other>)와 비슷하게 동작하지만, 이때 other는 dict이거나 (key, value) 쌍의 반복 가능한 객체일 수 있어요:
>>> palette_III = {'Grassy Green': (155, 196, 0),
'Purple Mountains Majesty': (128, 118, 163),
'Misty Mountain Pink': (249, 197, 189)}
>>> new_dict |= palette_III
>>> new_dict
...
{'Grassy Green': (155, 196, 0),
'Purple Mountains Majesty': (128, 118, 163),
'Misty Mountain Pink': (249, 197, 189),
'Factory Stone Purple': '#7c677f',
'Green Treeline': '#478559',
'Purple baseline': '#161748'}
딕셔너리와 이를 다루는 메서드에 대한 자세한 설명은 공식 튜토리얼과 공식 라이브러리 레퍼런스가 좋은 출발점이에요.
Real Python과 Finxter에도 Python 딕셔너리에 관한 아주 자세한 글이 있어요.
Mecha Munch™는 식료품 쇼핑 자동화 회사로, 주문 앱 작업을 위해 여러분을 새로 채용했어요. 여러분의 팀은 장바구니의 모든 기본 기능을 처리하는 MVP(최소 기능 제품)를 만드는 임무를 맡았어요. 사용자는 이 앱으로 식료품 주문을 추가하고, 삭제하고, 정렬할 수 있어요. 다행히도 결제와 관련된 기능은 다른 팀이 맡고 있어요!
MVP는 사용자가 장바구니에 항목을 추가할 수 있어야 해요. 한 번에 하나를 추가할 수도 있고, 여러 개를 한꺼번에 추가할 수도 있어요. MVP이므로 항목의 수량은 _반복_으로 나타내요. 사용자가 오렌지 2개를 추가하려면 입력 이터러블에 'Oranges'가 두 번 등장해요. 이미 장바구니에 있는 항목이라면 장바구니의 수량을 1 늘려요. 장바구니에 새로 추가되는 항목이라면 수량 1로 추가해요.
장바구니 딕셔너리와 추가할 항목들의 리스트 형태 이터러블을 인자로 받는 add_item(<current_cart>, <items_to_add>) 함수를 만들어요.
이 함수는 사용자를 위한 새 장바구니 딕셔너리, 즉 갱신된 장바구니를 반환해야 해요.
>>> add_item({'Banana': 3, 'Apple': 2, 'Orange': 1},
('Apple', 'Apple', 'Orange', 'Apple', 'Banana'))
{'Banana': 4, 'Apple': 5, 'Orange': 2}
>>> add_item({'Banana': 3, 'Apple': 2, 'Orange': 1},
['Banana', 'Orange', 'Blueberries', 'Banana'])
{'Banana': 5, 'Apple': 2, 'Orange': 2, 'Blueberries': 1}
이런. 제품 팀이 기능 확장에 빠진 모양이에요. MVP에 기능을 더 추가하고 싶어 하네요. 이제 앱은 사용자의 메모 앱에서 항목을 읽어 장바구니를 만들어야 해요. 사용자에게는 편리하지만, 팀에게는 일이 조금 더 늘었어요.
리스트 형태의 이터러블이라면 무엇이든 인자로 받을 수 있는 read_notes(<notes>) 함수를 만들어요.
이 함수는 항목을 분석해 사용자 장바구니, 즉 딕셔너리를 만들어야 해요.
각 항목은 수량 1로 추가해요.
그런 다음 새로 만든 사용자 장바구니를 반환해요.
>>> read_notes(('Banana','Apple', 'Orange'))
{'Banana': 1, 'Apple': 1, 'Orange': 1}
>>> read_notes(['Blueberries', 'Pear', 'Orange', 'Banana', 'Apple'])
{'Blueberries' : 1, 'Pear' : 1, 'Orange' : 1, 'Banana' : 1, 'Apple' : 1}
앱에는 다양한 요리의 완성된 레시피가 채워진 "아이디어" 섹션이 있어요. 사용자가 이 레시피 중 하나를 선택하면, 그 재료가 모두 자동으로 장바구니에 추가돼요. 콘텐츠 팀이 재료와 수량을 자꾸 바꾸는 바람에, 프로젝트 매니저가 이 "아이디어" 레시피를 수정하는 방법을 만들어 달라고 요청했어요.
"아이디어" 딕셔너리와 레시피 업데이트들의 이터러블을 인자로 받는 update_recipes(<ideas>, <recipe_updates>) 함수를 만들어요.
이 함수는 새로 갱신된 "아이디어" 딕셔너리를 반환해야 해요.
>>>update_recipes(
{'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1},
'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}},
(('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)
)
...
{'Banana Bread': {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3},
'Raspberry Pie': {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}
>>> update_recipes(
{'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1},
'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1},
'Pasta Primavera': {'Eggs': 1, 'Carrots': 1, 'Spinach': 2, 'Tomatoes': 3, 'Parmesan': 2, 'Milk': 1, 'Onion': 1}},
[('Raspberry Pie', {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}),
('Pasta Primavera', {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}),
('Blueberry Crumble', {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3})]
)
...
{'Banana Bread': {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1},
'Raspberry Pie': {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2},
'Pasta Primavera': {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1},
'Blueberry Crumble': {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}}
사용자가 장바구니를 만들기 시작하면, 앱에서 항목을 알파벳순으로 정렬할 수 있어요. 이렇게 하면 항목을 더 쉽게 찾을 수 있고, 데이터베이스에 'potatoes'와 'Potato'가 함께 있는 것 같은 입력 오류가 있을 때도 도움이 돼요.
장바구니, 즉 딕셔너리를 인자로 받아 알파벳순으로 정렬된 새 딕셔너리를 반환하는 sort_entries(<cart>) 함수를 만들어요.
>>> sort_entries({'Banana': 3, 'Apple': 2, 'Orange': 1})
{'Apple': 2, 'Banana':3, 'Orange': 1}
앱은 특정 사용자의 장바구니를 주문 처리를 위해 매장으로 보내야 해요. 그런데 매장 직원은 상품이 매장 어느 통로에 있는지, 그리고 냉장 보관이 필요한지도 알아야 해요. 그래서 (다소 임의로) "주문 처리 장바구니"는 항목의 수량에 위치와 냉장 정보를 결합한 뒤 알파벳 역순으로 정렬해야 해요.
사용자 장바구니와, 각 항목의 매장 통로 번호와 냉장 필요 여부를 나타내는 True/False를 담은 딕셔너리를 인자로 받는 send_to_store(<cart>, <aisle_mapping>) 함수를 만들어요.
이 함수는 고객이 주문하는 각 항목의 (수량, 통로, 냉장 여부)를 담은, 결합된 "주문 처리 장바구니"를 return해야 해요.
항목은 알파벳 _역순_으로 나타나야 해요.
>>> send_to_store({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2},
{'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]})
{'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}
앱이 고객 주문을 끝없이 받기만 할 수는 없어요. 결국 매장의 여러 상품이 떨어질 테니까요. 그래서 여러분의 MVP 앱은 사용자가 주문을 매장으로 보낼 때마다 매장 재고를 갱신해야 해요. 그러지 않으면 고객이 실제로는 없는 상품을 주문하게 돼요.
"주문 처리 장바구니"와 매장 재고를 인자로 받는 update_store_inventory(<fulfillment_cart>, <store_inventory>) 함수를 만들어요.
이 함수는 "주문 처리 장바구니"에 "주문된" 수량만큼 매장 재고 수량을 줄이고, 갱신된 매장 재고를 반환해야 해요.
매장 상품 수량이 0이 되면, 그 수량을 'Out of Stock'이라는 메시지로 바꿔요.
>>> update_store_inventory({'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]},
{'Banana': [15, 'Aisle 5', False], 'Apple': [12, 'Aisle 4', False], 'Orange': [1, 'Aisle 4', False], 'Milk': [4, 'Aisle 2', True]})
{'Banana': [12, 'Aisle 5', False], 'Apple': [10, 'Aisle 4', False], 'Orange': ['Out of Stock', 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True]}
Exercism에 가입하고 Python 트랙을 개념 17개연습 문제 146개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.