파이썬 기초 강좌 #10 딕셔너리 (Dictionary)

23 분 소요

딕셔너리는 리스트와 함께 파이썬에서 가장 많이 쓰이는 데이터 스트럭쳐 중 하나입니다. 딕셔너리의 가장 큰 특징은 key, value 매핑을 사용하는 데이터 스트럭쳐라는 점입니다. 그리고 string, list, tuple 등과 같은 시퀀스 (sequence) 데이터 타입과는 다르게 순서가 없는 데이터타입입니다. 이러한 특징에 대해서는 뒤에서 자세히 살펴보도록 하겠습니다.

먼저 딕셔너리를 정희하는 방법을 알아보겠습니다. 딕셔너리는 컬리브레이스를 사용하여 정의합니다. 컬리브레이스를 사용하여 빈 딕셔너리를 정의해 보겠습니다.

>>> my_dict = {}
>>> my_dict
{}

정의된 오브젝트의 데이터 타입을 확인해보겠습니다.

>>> type(my_dict)
<class 'dict'>

딕셔너리가 정의된 것을 알 수 있습니다. 딕셔너리의 아이템은 key, value 형식을 가지며 콜론으로 구분됩니다. 그리고 아이템과 아이템의 사이는 컴마로 구분됩니다.

컬리 브레이스를 사용하여 간단한 딕셔너리를 정의해보겠습니다.

>>> person = {
...     'first_name': 'Sanghee',
...     'last_name': 'Lee',
...     'age': 25
... }
>>> person
{'first_name': 'Sanghee', 'last_name': 'Lee', 'age': 25}

딕셔너리를 정의하는 또 다른 방법은 클래스 컨스트럭쳐를 사용하는 것입니다. 컨스트럭쳐를 사용하여 딕셔너리를 정의해 보겠습니다.

>>> person_2 = dict(
...     first_name='Sanghee',
...     last_name='Lee',
...     age=25
... )
>>> person_2
{'first_name': 'Sanghee', 'last_name': 'Lee', 'age': 25}

컨스트럭쳐로 정의할 때는 key와 value사이에 콜론을 사용하는 것이 아니라 변수에 데이터를 할당하듯이 이퀄싸인을 사용합니다. 그리고 키가 변수와 같은 역할을 하기 때문에 따옴표로 감싸면 안됩니다. 만약 키를 따옴표로 감싸면 다음과 같은 신텍스 에러가 발생합니다.

>>> my_dict = dict('key'='value')
File "<stdin>", line 1
my_dict = dict('key'='value')
^
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
다음과 같이 이퀄싸인 대신 콜론을 사용하여도 신텍스 에러가 발생합니다.
 
```shell
>>> my_dict = dict(key: 'value')
File "<stdin>", line 1
my_dict = dict(key: 'value')
^
SyntaxError: invalid syntax

💡 오늘의 팁

딕셔너리를 정의할 때 주의할 점 하나가 있는데요, 딕셔너리를 저장할 변수의 이름으로 빌트인 클래스 이름인 dict를 사용하면 dict안에 정의된 타입 오브젝트가 딕셔너리 오브젝트로 오버라이트되고, 딕셔너리 컨스트럭쳐를 사용할 수 없게되니 주의하시기 바랍니다.

딕셔너리의 아이템 데이터를 참조하거나 변경하고자 할 때는 각 아이템의 key를 사용하시면 됩니다. 에이지 key를 사용하여 25 라는 데이터를 출력해보겠습니다.

>>> person['age']
25

이번에는 에이지 데이터를 변경해보겠습니다.

>>> person['age'] = 30
>>> person
{'first_name': 'Sanghee', 'last_name': 'Lee', 'age': 30}

age value가 30으로 변경된 것을 볼 수 있습니다. 새로운 아이템을 추가하거나, 기존의 아이템을 삭제할 수도 있습니다.

아이템을 하나 추가하겠습니다.

>>> person['job'] = 'programmer'
>>> person
{'first_name': 'Sanghee', 'last_name': 'Lee', 'age': 30, 'job': 'programmer'}

del 빌트인 함수를 사용하여 추가한 아이템을 삭제하겠습니다.

>>> del person['job']
>>> person
{'first_name': 'Sanghee', 'last_name': 'Lee', 'age': 30}

추가했던 아이템이 삭제된 것을 볼 수 있습니다.

딕셔너리의 데이터를 참조하거나 삭제 할 때 딕셔너리안에 존재하지 않는 key를 사용하면 키에러가 발생하게되니 주의하시기 바랍니다.

존재하지 않는 key를 사용하여 데이터를 참조해보겠습니다.

>>> person['address']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'address'

이 번에는 존재하지 않는 key를 사용하여 데이터를 삭제해보겠습니다.

>>> del person['address']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'address'

딕셔너리의 key로 사용 가능한 데이터타입은 int, float, str, tuple입니다.

각 데이터 타입을 key로 사용해보겠습니다.

>>> my_dict = {
...     1: 'int',
...     1.23: 'float',
...     '123': 'string',
...     (1, 2, 3): 'tuple'
... }
>>> my_dict
{1: 'int', 1.23: 'float', '123': 'string', (1, 2, 3): 'tuple'}

하지만 리스트와 딕셔너리 타입은 key로 사용할 수 없습니다. 리스트를 key로 사용해 보겠습니다.

>>> d = {[1, 2, 3]: 'list'}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list

이번에는 딕셔너리를 key로 사용해보겠습니다.

>>> d = {{1: 1}: 'dict'}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'

이 번에는 딕셔너리 value에 어떤 데이터를 저장할 수 있는지 알아보도록 하겠습니다.

딕셔너리의 value에는 리스트안에 모든 데이터 타입을 저장할 수 있었던거와 같이 모든 데이터 타입을 저장할 수 있습니다.

코드를 실행하여 확인해보겠습니다.

>>> my_dict = {
...     'int': 1,
...     'float': 1.23,
...     'str': 'string',
...     'list': [1, 2, 3],
...     'tuple': (1, 2, 3),
...     'dict': {'one': 1, 'two': 2}
... }
>>> my_dict
{'int': 1, 'float': 1.23, 'str': 'string', 'list': [1, 2, 3], 'tuple': (1, 2, 3), 'dict': {'one': 1, 'two': 2}}

모든 데이터 타입이 저장된 것을 확인하였습니다.

딕셔너리안에 저장된 리스트의 아이템이나 딕셔너리의 아이템에 엑세스 할 때는 "my_dict['key1']['key2']" 과 같은 신텍스를 사용하시면 됩니다.

리스트 아이템과 딕셔너리의 아이템의 value를 참조해보겠습니다.

>>> my_dict['list'][0]
1
>>> my_dict['dict']['one']
1

앞에서 딕셔너리는 순서가 없는 데이터라고 말씀드렸습니다. 리스트와 같은 시퀀스 데이터 타입은 정의된 순서대로 데이터가 저장이 됩니다. 항상 같은 순서를 지키기 때문에 인덱스를 사용할 수 있고, 루프를 사용하여 항상 같은 순서대로 데이터에 엑세스할 수 있습니다. 하지만 딕셔너리는 그렇지 않습니다. 아이템의 순서가 보장되지 않으며, 새로운 아이템을 추가해도 마지막 위치가 아닌 다른 위치에 저장될 수 있습니다. 하지만 파이썬 3.7 버전 부터는 딕셔너리도 리스트와 같이 정의된 순서를 기억하고 새로운 아이템을 추가했을 때 마지막 위치에 저장되게 되었습니다.

먼저 3.7 미만의 버전에서의 딕셔너리를 확인해보겠습니다. 먼저 파이썬 버전을 확인해보겠습니다.

>>> import sys
>>> print(sys.version)
3.5.6 |Anaconda, Inc.| (default, Aug 26 2018, 16:05:27) [MSC v.1900 64 bit (AMD64)]

버전이 3.5.6 인것을 확인했습니다.

딕셔너리를 하나 생성한뒤 딕셔너리에 저장된 데이터를 프린트해보겠습니다.

>>> my_dict = {
...     'key_1': 1,
...     'key_2': 2,
...     'key_3': 3,
... }
>>> my_dict
{'key_3': 3, 'key_2': 2, 'key_1': 1}

저장된 순서와는 다르게 출력되는 것을 볼 수 있습니다.

이 번에는 새로운 아이템을 추가한 뒤 데이터를 출력해보겠습니다.

>>> my_dict['key_4'] = 4
>>> my_dict
{'key_1': 1, 'key_4': 4, 'key_2': 2, 'key_3': 3}

💡 오늘의 팁

파이썬 버전 3.5 이하의 딕셔너리는 순서가 없기 때문에 새로 추가하는 아이템이 어떤 위치에 추가가 될지 알 수가 없습니다.

추가한 아이템이 마지막에 추가되지 않은 것을 볼 수 있습니다.

이번에는 다시 3.7 이상의 버전에서의 딕셔너리를 확인해보겠습니다.

>>> import sys
>>> print(sys.version)
3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)]
>>> my_dict = {
...     'key_1': 1,
...     'key_2': 2,
...     'key_3': 3,
... }
>>> my_dict
{'key_1': 1, 'key_2': 2, 'key_3': 3}

아이템이 정의한 순서대로 출력되는 것을 알 수 있습니다.

이번에는 새로운 아이템을 추가해보겠습니다.

>>> my_dict['key_4'] = 4
>>> my_dict
{'key_1': 1, 'key_2': 2, 'key_3': 3, 'key_4': 4}

추가한 아이템이 마지막 위치에 저장된 것을 볼 수 있습니다.

이번에는 딕셔너리 클래스가 가지고 있는 클래스 메소드의 사용방법에 대해서 알아보도록 하겠습니다.

먼저 헬프 함수로 딕셔너리 클래스 안에는 어떤 메소드가 정의되어 있는지 확인해보겠습니다.

>>> help(dict)
Help on class dict in module builtins:
 
class dict(object)
|  dict() -> new empty dictionary
|  dict(mapping) -> new dictionary initialized from a mapping object's
|      (key, value) pairs
|  dict(iterable) -> new dictionary initialized as if via:
|      d = {}
|      for k, v in iterable:
|          d[k] = v
|  dict(**kwargs) -> new dictionary initialized with the name=value pairs
|      in the keyword argument list.  For example:  dict(one=1, two=2)
|
|  Methods defined here:
|
|  __contains__(self, key, /)
|      True if the dictionary has the specified key, else False.
|
|  __delitem__(self, key, /)
|      Delete self[key].
|
|  __eq__(self, value, /)
|      Return self==value.
|
|  __ge__(self, value, /)
|      Return self>=value.
|
|  __getattribute__(self, name, /)
|      Return getattr(self, name).
|
|  __getitem__(...)
|      x.__getitem__(y) <==> x[y]
|
|  __gt__(self, value, /)
|      Return self>value.
|
|  __init__(self, /, *args, **kwargs)
|      Initialize self.  See help(type(self)) for accurate signature.
|
|  __ior__(self, value, /)
|      Return self|=value.
|
|  __iter__(self, /)
|      Implement iter(self).
|
|  __le__(self, value, /)
|      Return self<=value.
|
|  __len__(self, /)
|      Return len(self).
|
|  __lt__(self, value, /)
|      Return self<value.
|
|  __ne__(self, value, /)
|      Return self!=value.
|
|  __or__(self, value, /)
|      Return self|value.
|
|  __repr__(self, /)
|      Return repr(self).
|
|  __reversed__(self, /)
|      Return a reverse iterator over the dict keys.
|
|  __ror__(self, value, /)
|      Return value|self.
|
|  __setitem__(self, key, value, /)
|      Set self[key] to value.
|
|  __sizeof__(...)
|      D.__sizeof__() -> size of D in memory, in bytes
|
|  clear(...)
|      D.clear() -> None.  Remove all items from D.
|
|  copy(...)
|      D.copy() -> a shallow copy of D
|
|  get(self, key, default=None, /)
|      Return the value for key if key is in the dictionary, else default.
|
|  items(...)
|      D.items() -> a set-like object providing a view on D's items
|
|  keys(...)
|      D.keys() -> a set-like object providing a view on D's keys
|
|  pop(...)
|      D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
|
|      If key is not found, default is returned if given, otherwise KeyError is raised
|
|  popitem(self, /)
|      Remove and return a (key, value) pair as a 2-tuple.
|
|      Pairs are returned in LIFO (last-in, first-out) order.
|      Raises KeyError if the dict is empty.
|
|  setdefault(self, key, default=None, /)
|      Insert key with a value of default if key is not in the dictionary.
|
|      Return the value for key if key is in the dictionary, else default.
|
|  update(...)
|      D.update([E, ]**F) -> None.  Update D from dict/iterable E and F.
|      If E is present and has a .keys() method, then does:  for k in E: D[k] = E[k]
|      If E is present and lacks a .keys() method, then does:  for k, v in E: D[k] = v
|      In either case, this is followed by: for k in F:  D[k] = F[k]
|
|  values(...)
|      D.values() -> an object providing a view on D's values
|
|  ----------------------------------------------------------------------
|  Class methods defined here:
|
|  __class_getitem__(...) from builtins.type
|      See PEP 585
|
|  fromkeys(iterable, value=None, /) from builtins.type
|      Create a new dictionary with keys from iterable and values set to value.
|
|  ----------------------------------------------------------------------
|  Static methods defined here:
|
|  __new__(*args, **kwargs) from builtins.type
|      Create and return a new object.  See help(type) for accurate signature.
|
|  ----------------------------------------------------------------------
|  Data and other attributes defined here:
|
|  __hash__ = None

이 중에서 가장 많이 쓰이는 메소드 몇가지의 사용 방법에 대해서 알아보겠습니다.

먼저 딕셔너리의 정의된 아이템을 삭제하는 clear 메소드를 사용해보겠습니다. 딕셔너리를 하나 정의한 뒤, 클리어 메소드를 사용하여 모든 아이템을 삭제해보겠습니다.

>>> my_dict = {
...     'one': 1,
...     'two': 2,
...     'three': 3
... }
>>> my_dict
{'one': 1, 'two': 2, 'three': 3}
>>> my_dict.clear()
>>> my_dict
{}

모든 아이템이 삭제된 것을 확인하였습니다.

다음은 get 메소드입니다. get 메소드는 딕셔너리의 key가 존재하면 key에 대한 value를 리턴하고, key가 존재하지 않으면 None을 리턴합니다. 이 메소드는 존재하지 않는 key를 참조하여 key 에러가 발생하는 것을 막기위해서 많이 사용됩니다.

먼저 딕셔너리를 정의한 뒤 get 메소드를 사용하여 데이터를 출력해보겠습니다.

>>> my_dict = {
...     'key': 'value'
... }
>>> my_dict.get('key')
'value'

이번에는 존재하지않는 key를 get 메소드에 전달하겠습니다.

>>> my_dict.get('no_key')
>>>

파이썬 콘솔이나 주피터 노트북은 리턴되는 값이 없거나 None이면 어떤 것도 출력하지 않습니다.

프린트 함수를 사용하여 정말 None 이 리턴되는지 확인해보겠습니다.

>>> print(my_dict.get('no_key'))
None

다음은 items 메소드입니다. 파이썬 딕셔너리는 for loop 에서 사용할 수 있는 이터레이터 오브젝트입니다. 딕셔너리를 하나 정의하고 iter 함수로 확인을 해보겠습니다.

>>> my_dict = {
...     'one': 1,
...     'two': 2,
...     'three': 3
... }
>>>
>>> iter(my_dict)
<dict_keyiterator object at 0x000002136D0E9B30>

이터레이터 오브젝트인 것을 확인했습니다.

for loop을 사용하여 데이터를 출력해보겠습니다.

>>> for i in my_dict.items():
...     print(i)
...
('one', 1)
('two', 2)
('three', 3)

각 아이템이 괄호안에 저장된 것을 봐서는 각 아이템은 튜플로 저장이 되는 것을 볼 수 있습니다. 실제 아이템의 데이터 타입을 확인해 보겠습니다.

>>> for i in my_dict.items():
...     print(type(i))
...
<class 'tuple'>
<class 'tuple'>
<class 'tuple'>

일반적으로 items 메소드를 사용하여 루핑을 돌때는 다음 예제와 같이 key의 값을 저장하는 변수와 value의 값을 저장하는 변수를 정의한뒤 각각의 데이터를 언패킹하여 사용합니다.

>>> for k, v in my_dict.items():
...     print('key: {}, value: {}'.format(k, v))
...
key: one, value: 1
key: two, value: 2
key: three, value: 3

다음은 keys 메소드입니다. 이 메소드는 딕셔너리의 정의된 모든 key를 리스트형식으로 리턴합니다. 이 메소드는 key의 정렬된 순서대로 데이터에 엑세스해야하는 경우 많이 사용됩니다.

먼저 딕셔너리를 정의한 뒤 for loop을 사용해보겠습니다.

>>> for i in my_dict:
...     print(my_dict[i])
...
2
1
3

데이터가 순서없이 출력되는 것을 볼 수 있습니다.

이번에는 키를 정렬하여 순서대로 출력하여 보겠습니다.

>>> my_keys = my_dict.keys()
>>> my_keys
dict_keys(['b', 'a', 'c'])

sorted 함수를 사용하여 순서대로 정열을 하겠습니다.

>>> my_keys = sorted(my_keys)
>>> my_keys
['a', 'b', 'c']

정열된 key를 사용하여 아이템을 출력해보겠습니다.

>>> for i in my_keys:
...     print(my_dict[i])
...
1
2
3

숫자가 순서대로 출력되었습니다.

다음에 확인할 메소드는 values 메소드입니다. 이 메소드는 딕셔너리의 모든 value를 리스트 형식으로 리턴합니다.

values 메소드를 사용하여 모든 value를 출력해보겠습니다.

>>> my_dict.values()
dict_values([2, 1, 3])

다음은 update 메소드입니다. 이 메소드는 두 딕셔너리를 하나로 조인해주는 아주 편리한 메소드입니다.

두개의 딕셔너리를 정의한 뒤 하나의 딕셔너리로 조인하여 보겠습니다.

>>> dict_a = {'key_a': 'value_a'}
>>> dict_b = {'key_b': 'value_b'}
>>> dict_a.update(dict_b)
>>> dict_a
{'key_a': 'value_a', 'key_b': 'value_b'}

두개의 딕셔너리가 하나로 조인되었습니다.

오늘 강좌는 여기서 마치도록 하겠습니다. 이 강좌는 기초강좌이기 때문에 딕셔너리에 대한 기초적인 부분만 설명드렸습니다만, 다른 강좌를 통해서 실제 프로그래밍을 할 때 딕셔너리를 어떻게 활용하는지에 대해서 설명드리도록 하겠습니다.

다음 강좌에서는 리스트와 딕셔너리보다는 많이 쓰여지지 않지만 잘 알아두면 정말 편리한 세트에 대해서 공부하도록하겠습니다.