파이썬 딕셔너리 자료구조 이해하기

파이썬 딕셔너리 자료구조 이해하기

딕셔너리란?

일반적으로 딕셔너리(사전)는 단어의 의미를 담고있는 책을 말한다. 사전에 각 항목에는 정의되는 단어와 그 단어의 뜻, 총 두 부분으로 나뉜다.

파이썬에서 딕셔너리는 리스트와 튜플과 마찬가지로 객체의 컬렉션을 저장한다. 하지만, 객체들을 배열에 저장하는 대신, 딕셔너리는 키-값 쌍(key-value pairs)라고 불리는 방식으로 정보를 저장한다. 즉, 딕셔너리의 각 객체는 키(key)와 값(value), 두 부분으로 나뉜다.

키-값 쌍의 키는 값 부분을 식별하는 고유한 이름이다. 영어 사전과 같이, 키는 정의된느 단어와 같고, 값은 단어의 정의와 같다. 예를 들어, 다음과 같이 주(state)와 주도(capital)의 이름을 저장하기 위해 딕셔너리를 사용할 수 있다.

Image from Python Basics

영어 사전에서 정의하는 딕셔너리와 파이썬에서의 딕셔너리가 다른 점은 키와 값의 관계가 완전히 임의적(arbitrary)이라는 것이다. 즉, 다음 예제처럼 모든 키를 모든 값에 할당할 수 있다.

Image from Python Basics

위 예제에서 유일한 관계는 딕셔너리에서 각 키가 상응하는 값에 할당되었다는 것이다. 이런 관점에서, 파이썬의 딕셔너리는 영어 사전에서 정의하는 딕셔너리보다는 맵(map)에 훨씬 가깝다고 할 수 있다(맵이라는 용어는 값의 두 값의 집합간의 관계를 설명하는데 사용되는 수학 용어이다).

요약하자면, 파이썬 딕셔너리는 키 집합을 값 집합과 연결하는 자료구조이다. 각 키에는 두 집합 간의 관계를 정의하는 단일 값이 할당된다.


딕셔너리 생성하기

다음의 코드는 주와 주도를 담는 딕셔너리 리터럴(dictionary literal)을 생성한다.

>>> capitals = {
	"California": "Sacramento",
	"New York": "Albany",
	"Texas": "Austin",
}

각 키는 값으로 부터 콜론(:)으로 구분되고, 각 키-값 쌍은 콤마(,)로 구분되며, 전체 딕셔너리는 중괄호({}) 둘러 쌓인다.

또한 dict() 내장 함수를 사용해 튜플의 배열을 딕셔너리로 생성할 수 있다.

>>> key_value_pairs = (
... 	("California", "Sacramento"),
... 	("New York", "Albany"),
... 	("Texas", "Austin"),
)
>>> capitals = dict(key_value_pairs)

딕셔너리를 호출하면 딕셔너리가 어떻게 생성 되었는지와는 상관없이 딕셔너리 리터럴로 표시된다.

>>> capitals
{'California': 'Sacramento', 'New York': 'Albany', 'Texas': 'Austin'}
파이썬 3.6 버전을 사용한다면 위 예제와는 다른 출력값을 얻는다. 파이썬 3.6 버전 이전에는 딕셔너리 키-값 쌍의 순서가 랜덤이였기 때문이다. 이후 버전에서는 키-값 쌍의 순서가 처음에 삽입 되었을 때의 순서와 매치되도록 보장된다.

딕셔너리 이터럴이나 dict() 함수를 사용해 빈 딕셔너리를 생성할 수 있다.

>>> {}
{}
>>> dict()
{}

딕셔너리 값 접근하기

딕셔너리의 값에 접근하기 위해서는 딕셔너리 혹은 딕셔너리에 할당된 변수 이름 뒤에 대괄호로 접근하려는 키를 감싸주면 된다.

>>> capitals = {
	"California": "Sacramento",
	"New York": "Albany",
	"Texas": "Austin",
}

>>> capitals["Texas"]
'Austin'

이전의 시퀀스 자료형과는 다르게 딕셔너리는 인덱스가 아닌 키를 사용해 값에 접근한다. 또한, 인덱스를 사용하지 않고 값에 접근하기 때문에 키를 사용할 때, 어떠한 순서도 정의되지 않는다.


딕셔너리에 값 추가 혹은 제거하기

딕셔너리 또한 리스트처럼 가변 자료구조이다. 즉, 항목을 추가하거나 제거할 수 있다.

다음 예제에서 콜로라도의 주도를 capitals 딕셔너리에 추가해보자.

>>> capitals
{'California': 'Sacramento', 'New York': 'Albany', 'Texas': 'Austin'}
>>> capitals["Colorado"] = "Denver"
>>> capitals
{'California': 'Sacramento', 'New York': 'Albany', 'Texas': 'Austin', 'Colorado': 'Denver'}

위처럼 새로운 값을 딕셔너리에 추가하기 위해서는 딕셔너리 뒤에 새로운 키 "Colorado"를 대괄호로 감싸 위치시키고, 할당 연산자 =를 사용해 값 "Denver"를 새로운 키 "Colorado"에 할당해준다.

딕셔너리 안의 각 키는 단일 값으로만 할당될 수 있다. 만약 기존 키에 새로운 값이 주어지면, 파이썬은 기존 값을 덮어 쓴다.

>>> capitals["Texas"] = "Houston"
>>> capitals
{'California': 'Sacramento', 'New York': 'Albany', 'Texas': 'Houston', 'Colorado': 'Denver'}

딕셔너리에서 항목을 지우기 위해서는 del 키워드를 키와 사용할 수 있다.

>>> del capitals["Texas"]
>>> capitals
{'California': 'Sacramento', 'New York': 'Albany', 'Colorado': 'Denver'}

딕셔너리 키의 존재 확인하기

만약 딕셔너리에서 값에 접근하기 위해 존재하지 않는 키를 사용하면 파이썬은 KeyError를 일으킨다.

>>> capitals["Arizona"]
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
	capitals["Arizona"]
KeyError: 'Arizona'

in 키워드를 사용해서 딕셔너리 안의 키가 존재하는지 확인할 수 있다.

>>> "Arizona" in capitals
False
>>> "California" in capitals
True

in 키워드는 키의 존재여부만 체크한다는 점을 꼭 기억해야한다. "Sacramento"는 존재하는 키 "California"의 값이지만, 키가 아니기 때문에 False가 반환된다.

>>> capitals
{'California': 'Sacramento', 'New York': 'Albany', 'Colorado': 'Denver'}
>>> "Sacramento" in capitals
False

딕셔너리 반복하기

리스트와 튜플과 마찬가지로 딕셔너리는 반복 가능한 객체(iterable)이다. 하지만 딕셔너리를 반복하는 것은 리스트나 튜플을 반복하는 것과는 다르다.

딕셔너리를 반복할 때는 딕셔너리의 키를 사용해서 반복한다.

>>> for key in capitals:
... 	print(key)
...
California
New York
Colorado

만약 키와 값을 사용해 반복문을 작성하고 싶다면 다음과 같이 작성해야 한다.

>>> for state in capitals:
	print(f"The capital of {state} is {capitals[state]}")
    
The capital of California is Sacramento
The capital of New York is Albany
The capital of Colorado is Denver

하지만, 파이썬에서는 items() 라고 불리는 딕셔너리 메서드를 사용해 더 간결하게 작성할 수 있다. items() 메서드를 사용하면 키-값 쌍의 튜플을 담고있는 리스트스러운 객체를 반환한다.

>>> capitals.items()
dict_items([('California', 'Sacramento'), ('New York', 'Albany'),
('Colorado', 'Denver')])

items() 메서드로 반환되는 객체는 사실 리스트는 아니다. dict_items라고 불리는 특별한 타입을 가진다.

>>> type(capitals.items())
<class 'dict_items'>

items() 메서드는 딕셔너리의 키와 값을 동시에 반복하기 위해 사용될 수 있다. 위 예제에서 작성되었던 반복문을 아래처럼 다시 작성할 수 있다.

>>> for state, capital in capitals.items():
... 	print(f"The capital of {state} is {capital}")
The capital of California is Sacramento
The capital of New York is Albany
The capital of Colorado is Denver

capitals 딕셔너리에 items() 메서드를 사용함으로써, 반복문의 각 반복은 state 이름과, 키에 상응하는 capital을 담는 튜플을 생산한다. 이 튜플을 statecapital할당하면, 튜플의 구성요소가 두 변수 statecapital언패킹 된다.


딕셔너리 키와 불변성

딕셔너리 captials의 각 키는 문자열이다. 하지만 딕셔너리 키는 반드시 다 같은 자료형을 사용해야 한다와 같은 규칙은 존재하지 않는다. 예를 들어, 다음과 같이 딕셔너리 capitals에 정수형 키를 추가할 수도 있다.

>>> capitals[50] = "Honolulu"
>>> capitals
{'California': 'Sacramento', 'New York': 'Albany',
'Colorado': 'Denver', 50: 'Honolulu'}

유효한 딕셔너리 키를 제정하는 단 한 가지 규제가 존재하는데, 바로 불변 객체(immutable)만 허용된다는 점이다. 즉, 리스트와 같은 가변객체는 딕셔너리의 키가 될 수 없다.

>>> capitals[[1, 2, 3]] = "Bad"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
딕셔너리에서 유효한 키의 종류는 다음과 같다.
Image from Python Basics
키와는 달리, 딕셔너리의 값은 유효한 모든 파이썬 타입을 사용할 수 있다!

중첩 딕셔너리

파이썬에서 리스트 안에 리스트를, 튜플 안에 튜플을 중첩할 수 있는 것처럼 중첩 딕셔너리 또한 생성할 수 있다. 기존 딕셔너리에서 주 이름과 주도를 매핑하는 대신, 각 주 이름을 주도와 주화(state flower)를 담고 있는 딕셔너리에 매핑하는 딕셔너리를 생성해보자.

>>> states = {
... 	"California": {
... 		"capital": "Sacramento",
... 		"flower": "California Poppy"
... 	},
... 	"New York": {
... 		"capital": "Albany",
... 		"flower": "Rose"
... 	},
... 	"Texas": {
... 		"capital": "Austin",
... 		"flower": "Bluebonnet"
... 	},
... }

다음과 같이 각 키의 값은 딕셔너리이다.

>>> states["Texas"]
{'capital': 'Austin', 'flower': 'Bluebonnet'}

텍사스의 주화를 다음과 같이 얻을 수 있다.

>>> states["Texas"]["flower"]
'Bluebonnet'

Reference