파이썬 3.5 이전에는 딕셔너리에 대해 이터레이션을 수행하면 키를 임의의 순서로 돌려줬으며, 이터레이션 순서는 원소가 삽입된 순서와 일치하지 않았다.
baby_names = {
'cat': 'kitten',
'dog': 'puppy',
}
# 파이써 3.5에서는 {'dog': 'puppy', 'cat': 'kitten'}가 출력되지만 3.7 이후에서는 {'cat': 'kitten', 'dog': 'puppy'}
print(baby_names)
{'cat': 'kitten', 'dog': 'puppy'}
이러한 이유는 예전 딕셔너리 구현이 내장 hash 함수와 파이썬 인터프리터가 시작할 때 초기화되는 난수 씨드를 사용하는 해시 테이블 알고리즘으로 만들어졌기 때문이다.
파이썬 3.6부터 삽입순서를 보존하도록 개선되었다. (3.7 아닌가??) -> 3.7부터 파이썬 언어 명세에 내용이 포함됨
print(list(baby_names.keys()))
print(list(baby_names.values()))
print(list(baby_names.items()))
print(baby_names.popitem())
['cat'] ['kitten'] [('cat', 'kitten')] ('cat', 'kitten')
함수에 대한 키워드 인자는 예전에는 이러한 이슈때문에 뒤죽박죽 처럼 보였다.
def my_func(**kwargs):
for key, value in kwargs.items():
print(f'{key} = {value}')
my_func(goose='gosling', kangaroo='joey')
goose = gosling kangaroo = joey
class MyClass:
def __init__(self):
self.alligator = 'hatchling'
self.elephant = 'calf'
a = MyClass()
for key, value in a.__dict__.items():
print(f'{key} = {value}')
alligator = hatchling elephant = calf
collections에 포함되어 있는 OrderDict은 dict과는 특성이 조금 다름.
popitem 호출을 많이 한다면, OrderDict이 더 나을 수 있다.
파이썬에서는 list, dict 등의 프로토콜을 흉내내는 커스텀 컨테이너 타입을 정의할 수 있다.
대부분의 경우 엄격한 클래스 계층보다 객체의 동작이 객체의 실질적인 타입을 결정하는 덕 타이핑에 의존하며, 이로인해 함정에 빠질 수 있다.
votes = {
'otter': 1281,
'polar bear': 587,
'fox': 863,
}
def populate_ranks(votes, ranks):
names = list(votes.keys())
names.sort(key=votes.get, reverse=True)
for i, name in enumerate(names, 1):
ranks[name] = i
def get_winner(ranks):
return next(iter(ranks))
ranks = {}
populate_ranks(votes, ranks)
print(ranks)
winner = get_winner(ranks)
print(winner)
{'otter': 1, 'fox': 2, 'polar bear': 3} otter
만약 등수가 아닌 알파벳 순으로 표시해야 할때, collections.abc 모듈을 사용해 딕셔너리와 비슷하지만 알파벳 순서대로 이터레이션 해주는 클래스를 새로 정의할 수 있다.
from collections.abc import MutableMapping
class SortedDict(MutableMapping):
def __init__(self):
self.data = {}
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value
def __delitem__(self, key):
del self.data[key]
def __iter__(self):
keys = list(self.data.keys())
keys.sort()
for key in keys:
yield key
def __len__(self):
return len(self.data)
sorted_ranks = SortedDict()
populate_ranks(votes, sorted_ranks)
print(sorted_ranks.data)
winner = get_winner(sorted_ranks)
print(winner)
{'otter': 1, 'fox': 2, 'polar bear': 3} fox
fox 가 출력되므로 요구사항에 맞지 않다.
해결 방법
def get_winner(ranks):
for name, rank in ranks.items():
if rank == 1:
return name
winner = get_winner(sorted_ranks)
print(winner)
otter
def get_winner(ranks):
if not isinstance(ranks, dict):
raise TypeError('dict 인스턴스가 필요합니다')
return next(iter(ranks))
get_winner(sorted_ranks)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-19-19075fd8aa1c> in <module> ----> 1 get_winner(sorted_ranks) <ipython-input-18-5d795183d0cc> in get_winner(ranks) 1 def get_winner(ranks): 2 if not isinstance(ranks, dict): ----> 3 raise TypeError('dict 인스턴스가 필요합니다') 4 return next(iter(ranks)) TypeError: dict 인스턴스가 필요합니다
from typing import Dict, MutableMapping
def populate_ranks(votes: Dict[str, int],
ranks: Dict[str, int]) -> None:
names = list(votes.keys())
names.sort(key=votes.get, reverse=True)
for i, name in enumerate(names, 1):
ranks[name] = i
def get_winner(ranks: Dict[str, int]) -> str:
return next(iter(ranks))
class SortedDict(MutableMapping[str, int]):
def __init__(self):
self.data = {}
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value
def __delitem__(self, key):
del self.data[key]
def __iter__(self):
keys = list(self.data.keys())
keys.sort()
for key in keys:
yield key
def __len__(self):
return len(self.data)
sorted_ranks = SortedDict()
populate_ranks(votes, sorted_ranks)
print(sorted_ranks.data)
winner = get_winner(sorted_ranks)
print(winner)
{'otter': 1, 'fox': 2, 'polar bear': 3} fox
!pip install mypy
Looking in indexes: http://mirror.kakao.com/pypi/simple Collecting mypy Downloading http://mirror.kakao.com/pypi/packages/57/2b/e619ca2c36cd5be7c37cc4b4a6e5d2c90c874a384e6ff86c5e898a02412c/mypy-0.790-cp38-cp38-manylinux1_x86_64.whl (22.0 MB) |████████████████████████████████| 22.0 MB 12.3 MB/s eta 0:00:01 Collecting mypy-extensions<0.5.0,>=0.4.3 Downloading http://mirror.kakao.com/pypi/packages/5c/eb/975c7c080f3223a5cdaff09612f3a5221e4ba534f7039db34c35d95fa6a5/mypy_extensions-0.4.3-py2.py3-none-any.whl (4.5 kB) Collecting typed-ast<1.5.0,>=1.4.0 Downloading http://mirror.kakao.com/pypi/packages/77/49/51308e8c529e14bb2399ff6d22998583aa9ae189ec191e6f7cbb4679f7d5/typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl (768 kB) |████████████████████████████████| 768 kB 15.7 MB/s eta 0:00:01 Collecting typing-extensions>=3.7.4 Downloading http://mirror.kakao.com/pypi/packages/60/7a/e881b5abb54db0e6e671ab088d079c57ce54e8a01a3ca443f561ccadb37e/typing_extensions-3.7.4.3-py3-none-any.whl (22 kB) Installing collected packages: mypy-extensions, typed-ast, typing-extensions, mypy Successfully installed mypy-0.790 mypy-extensions-0.4.3 typed-ast-1.4.1 typing-extensions-3.7.4.3
!python3 -m mypy --strict dict_test.py
dict_test.py:6: error: Argument "key" to "sort" of "list" has incompatible type overloaded function; expected "Callable[[str], _SupportsLessThan]" dict_test.py:16: error: Function is missing a return type annotation dict_test.py:16: note: Use "-> None" if function does not return a value dict_test.py:19: error: Function is missing a type annotation dict_test.py:22: error: Function is missing a type annotation dict_test.py:25: error: Function is missing a type annotation dict_test.py:28: error: Function is missing a return type annotation dict_test.py:34: error: Function is missing a return type annotation dict_test.py:43: error: Call to untyped function "SortedDict" in typed context dict_test.py:44: error: Argument 2 to "populate_ranks" has incompatible type "SortedDict"; expected "Dict[str, int]" dict_test.py:46: error: Argument 1 to "get_winner" has incompatible type "SortedDict"; expected "Dict[str, int]" Found 10 errors in 1 file (checked 1 source file)