딕셔너리
1. 딕셔너리란?
파이썬의 딕셔너리(Dictionary)는 마치 일상생활에서 사용하는 사전 같은 자료형입니다. 사전에는 단어와 그 단어의 뜻이 함께 들어있죠. 파이썬의 딕셔너리도 이와 비슷하게, 키(key)
와 그에 해당하는 값(value)
이 함께 들어있습니다.
예를 들면, {'name': 'licat', 'city': 'Jeju', 'job': 'Developer'}
에서 'name'
은 키이고, 'licat'
은 그에 해당하는 값입니다. 이 딕셔너리의 길이는 3개의 키-값 쌍으로 이루어져 있어요.
이는 어떨때 쓰일까요? 하나의 사람이 등록시에 주민번호와 학번, 제품번호등을 같이 입력하게 된다면 바로바로 필요한 값을 찾을수 있겠죠? 이렇게 저장하게 되면 엑셀처럼 값을 저장하는것이 가능하게 됩니다.
딕셔너리는 이런 키-값 쌍의 형태로 정보를 저장하므로, 특정 정보를 빠르게 찾아내거나 접근할 수 있습니다. 또한 딕셔너리는 키를 기준으로 데이터를 찾기 때문에 키는 유일해야 합니다. 즉, 중복된 키를 가질 수 없어요. 그렇지만 값은 중복될 수 있습니다. 만약 중복된 키를 사용하면, 그 키에 해당하는 이전 값은 사라지게 됩니다.
이런 특성 덕분에, 딕셔너리는 파이썬에서 정보를 효율적으로 관리하고 조작하는 데 매우 유용한 자료형입니다.
딕셔너리는 시퀀스 자료형이 아닙니다. 따라서 인덱싱이나 슬라이싱은 가능하지 않습니다. 다만 순서는 있는 자료형입니다. 파이썬 3.6 버전부터 삽입 순서가 보장되었는데요. 이 변경사항은 2016년에 발표되었습니다. 따라서 딕셔너리가 순서가 없다는 말은 3.5 이하 버전에서는 맞지만 3.6 이상 버전에서는 틀린 얘기입니다.
1.1 딕셔너리 생성 방법
딕셔너리는 중괄호({}
)를 사용하여 생성하며, 키와 값의 쌍으로 구성됩니다. 키와 값 사이에는 콜론(:
)을 사용하여 구분하고, 각 키-값 쌍은 쉼표(,
)로 구분합니다.
# 형태
# { key1: value1, key2: value2 }
# 형태
# { key1: value1, key2: value2 }
person = {'name': 'licat', 'city': 'Jeju', 'job': 'Developer'}
person = {'name': 'licat', 'city': 'Jeju', 'job': 'Developer'}
1.1.1 빈 딕셔너리 생성
빈 딕셔너리는 중괄호 {}
또는 dict()
를 사용하여 생성할 수 있습니다.
1.1.2 키는 중복될 수 없다
키는 딕셔너리 내에서 유일해야 합니다. 만약 중복된 키로 값을 할당하면 마지막으로 할당된 값만 유지됩니다.
위의 예에서 볼 수 있듯이, 'one'이라는 키에 여러 값을 차례대로 할당했지만 마지막 값인 하나3
만 유지되었습니다.
1.1.3 튜플 리스트로 생성
키와 값을 갖는 튜플의 리스트로 딕셔너리를 생성할 수 있습니다.
1.1.4 키워드 인자로 생성
키워드 인자를 사용하여 딕셔너리를 생성할 수 있습니다.
1.1.5 다양한 자료형을 값으로 넣기
다양한 자료형을 값으로 사용할 수 있으며, 키로는 문자열, 숫자, 튜플 등 불변의 자료형을 사용할 수 있습니다.
이렇게 다양한 방법으로 딕셔너리를 생성할 수 있습니다. 상황에 맞는 방법을 선택하여 사용하면 됩니다.
1.2 딕셔너리 수정 및 중첩
1.2.1 딕셔너리 항목 변경 및 추가
딕셔너리의 항목 값을 변경하려면, 해당 키를 이용하여 값을 할당하면 됩니다. 존재하지 않는 키를 이용하여 값을 할당하면, 새로운 키-값 쌍이 추가됩니다.
아래와 같이 항목을 추가할 수 있습니다.
1.2.2 중첩된 딕셔너리
딕셔너리는 다양한 자료형을 값으로 사용할 수 있기 때문에, 딕셔너리 안에 또 다른 딕셔너리를 넣을 수 있습니다. 이를 중첩된 딕셔너리라고 합니다.
중첩된 딕셔너리는 특히 JSON 형식의 데이터를 다룰 때 자주 사용됩니다.
이렇게 딕셔너리는 데이터를 구조화하고 효율적으로 관리하는데 매우 유용한 자료형입니다. 키를 통해 빠르게 값을 검색하거나 수정할 수 있기 때문에 많은 프로그램에서 활용됩니다.
1.3 zip
함수와 dict
를 사용한 딕셔너리 생성
앞서 built-in function 챕터에서 살펴본 zip
은 두 리스트를 서로 짝짓는 함수입니다. dict
는 그 짝을 딕셔너리로 바꿔줍니다.
예를 들어
이 코드는 keys
의 첫 번째 값 'name'과 values
의 첫 번째 값 'licat'을 짝짓고 나머지도 순서대로 짝짓습니다. 그리고 dict
로 그 짝을 딕셔너리로 바꿔줍니다.
# 출력
{'name': 'licat', 'city': 'Jeju', 'job': 'Developer'}
# 출력
{'name': 'licat', 'city': 'Jeju', 'job': 'Developer'}
zip
과 dict
를 이렇게 같이 쓰면 두 리스트를 딕셔너리로 쉽게 합칠 수 있습니다.
2. 딕셔너리의 구조 및 특징
이제 딕셔너리의 구조 및 특징을 면밀하게 살펴봅시다.
2.1 딕셔너리의 메모리 구조(3.10버전 이하)
딕셔너리의 구현은 내부적으로 해시 테이블을 사용합니다. 해시 테이블은 데이터의 검색, 추가, 삭제를 평균적으로 O(1)의 시간 복잡도로 처리할 수 있는 특징을 가집니다.
2.1.1 해시테이블
my_dict = {'name': 'licat', 'age': 10}
my_dict = {'name': 'licat', 'age': 10}
이 딕셔너리의 메모리 구조는 다음과 같이 설명될 수 있습니다:
- 'name' 및 'age'라는 키는 해시 함수를 통해 해시 테이블의 특정 위치로 변환됩니다.
- 해당 위치에는 키와 값이 저장됩니다. 예를 들어 'name': 'licat'이라는 키-값 쌍이 저장됩니다.
파이썬 3.10 이하버전에서의 해시테이블을 시각화하면 아래와 같이 생겼습니다.
배열에서 name을 검색하고 싶을 경우 모든 key값을 비교해야 하므로 O(N)의 시간이 걸리지만 해시 테이블을 이용하게 된다면 해시 함수를 이용해서 해시 값을 한번만 계산하면 되므로 O(1)만에 찾을 수 있게 됩니다.
알아두어야 할 것
해시 테이블은 효율적인 검색을 위해 설계되었지만, 버킷(Bucket)을 추가적으로 할당해주어야 하므로 메모리 공간을 약간 더 사용하는 경향이 있습니다. 이는 빠른 검색 시간을 위한 trade-off입니다.
2.2 딕셔너리의 메모리 구조 (Python 3.11 이상)
Python 3.11부터 딕셔너리의 내부 구조가 크게 개선되었습니다. 이 변경으로 메모리 사용량이 줄어들고 성능이 향상되었습니다.
2.2.1 구조의 변경
이전 버전과 이후 버전들의 포인터가 어떻게 변했는지는 유로 파이썬에 잘 나와 있습니다. 3.11버전에 가장 눈여겨 볼만한 변화는 딕셔너리 포인터 구조의 변화입니다. 이로 인해 메모리 사용량이 줄어들고 성능이 향상되었습니다.
3.10버전에 비해 3.11은 10% ~ 60% 속도 향상을 보여주며, 특히 딕셔너리의 경우 10% ~ 25%의 속도 향상을 보여줍니다. 버전업이 될 때마다 이러한 메모리 구조는 변하니 얼마나 빨라지는지 체크해주는 공식문서를 참고하시면 좋습니다. ChatGPT와 같은 LLM은 할루시네이션이 있어 얼마나 빨라지는지에 대해 정확한 답변을 해주지는 못합니다.
3.11버전이 얼마나 빨라졌는지에 관하여3. 딕셔너리의 메서드
딕셔너리의 메서드를 dir()
메서드를 이용해서 확인해봅시다.
3.1 clear
clear
메서드는 딕셔너리의 모든 키-값 쌍을 삭제합니다.
3.2 copy
copy
메서드는 딕셔너리의 얕은 복사본을 반환합니다.
3.3 fromkeys
fromkeys
메서드를 사용하면 시퀀스 자료형을 기반으로 딕셔너리를 생성할 수 있습니다.
key가 iterable한 객체가 가능하다 해서 아래처럼 iterable한 객체를 value로 넣어 생성할 수 있을 것이라 기대하면 안됩니다.
3.4 get
딕셔너리에서는 존재하지 않는 키를 직접 접근하면 오류가 발생합니다. 딕셔너리에서 에러없이 값을 추출하려면 get
메서드를 활용합니다. 만약 해당 키가 없을 경우에는 None을 반환하지만 두번째 인자에 값을 넣어 기본값을 반환할 수도 있습니다.
my_list.get('key', 'default(선택)')
# key값을 찾을건데, 없으면 default값에 들어간 값으로 알려줘 라는 뜻입니다.
my_list.get('key', 'default(선택)')
# key값을 찾을건데, 없으면 default값에 들어간 값으로 알려줘 라는 뜻입니다.
만약 아래와 같은 코드로 작성이 되어 있다고 한다면 이는 get 메서드를 수정하여 수정할 수 있습니다.
def 요일변경(n):
return {
1: '월요일',
2: '화요일',
3: '수요일',
4: '목요일',
5: '금요일',
6: '토요일',
7: '일요일',
}[n]
요일변경(3) # 출력: 수요일
# 요일변경(8) # error
def 요일변경(n):
return {
1: '월요일',
2: '화요일',
3: '수요일',
4: '목요일',
5: '금요일',
6: '토요일',
7: '일요일',
}[n]
요일변경(3) # 출력: 수요일
# 요일변경(8) # error
def 요일변경(n):
return {
1: '월요일',
2: '화요일',
3: '수요일',
4: '목요일',
5: '금요일',
6: '토요일',
7: '일요일',
}.get(n, '')
요일변경(3)
print(요일변경(8))
print(요일변경(8))
def 요일변경(n):
return {
1: '월요일',
2: '화요일',
3: '수요일',
4: '목요일',
5: '금요일',
6: '토요일',
7: '일요일',
}.get(n, '')
요일변경(3)
print(요일변경(8))
print(요일변경(8))
3.5 items
딕셔너리의 키와 값을 쌍으로 추출하려면 items
메서드를 사용합니다. 반환되는 결과는 dict_items
객체입니다. 이 객체는 인덱싱이 안되니 인덱싱을 하고 싶다면 list로 변환 후 사용해주세요.
person = {'name': 'licat', 'city': 'Jeju', 'job': 'Developer'}
print(person.items())
person = {'name': 'licat', 'city': 'Jeju', 'job': 'Developer'}
print(person.items())
dict_items([('name', 'licat'), ('city', 'Jeju'), ('job', 'Developer')])
dict_items([('name', 'licat'), ('city', 'Jeju'), ('job', 'Developer')])
또한, 딕셔너리에서 특정 키의 존재 여부를 확인하려면 in
연산자를 활용합니다.
만약 특정 값이 존재하는지 확인하고 싶다면 values 메서드를 사용하고 반환된 객체에서 in 연산자를 사용해야 합니다.
3.6 keys
keys()
메서드는 딕셔너리의 모든 키를 추출하려고 할 때 사용합니다. 반환되는 결과는 dict_keys
객체입니다. 이 객체는 리스트와 유사하게 작동하지만, 리스트의 메서드를 모두 지원하는 것은 아닙니다. 하지만 반복문과 함께 사용하여 키들을 순회할 수 있습니다.
dict_keys
객체를 리스트로 변환하려면, list()
함수를 사용하면 됩니다. 예를 들어, list(person.keys())
는 ['name', 'city', 'job']
을 반환합니다. 이를 통해 리스트의 메서드를 활용하여 추가적인 처리나 변경을 수행할 수 있습니다.
dict.keys()를 사용할 이유가 있는지 점검해보세요.
대부분의 경우, 딕셔너리의 키를 순회할 때 dict.keys()
를 사용할 필요가 없습니다. 왜냐하면 딕셔너리는 이미 순회 가능한 객체이기 때문입니다. 딕셔너리를 순회하면 기본적으로 키를 순회하게 됩니다. 따라서 dict.keys()
를 사용할 필요가 없는 경우가 많습니다.
for key in person:
print(key)
for key in person:
print(key)
3.7 pop
pop
메서드는 주어진 키의 값을 반환하고, 해당 키-값 쌍을 딕셔너리에서 삭제합니다.
아래와 같이 아무것도 없는 값을 pop하게 되면 error가 출력됩니다.
3.8 popitem
popitem
메서드는 딕셔너리의 마지막 키-값 쌍을 반환하고, 그 항목을 삭제합니다.
3.9 setdefault
setdefault
메서드는 주어진 키에 대한 값을 반환합니다. 키가 없으면 기본값을 설정하고 반환합니다.
아래처럼 이미 dict에 있는 값일 경우 반영되지 않습니다.
3.10 update
기존 딕셔너리에 추가 데이터를 병합하려면 update
메서드를 사용합니다.
3.11 values
values()
메서드는 딕셔너리에서 값들만 추출하려 할 때 사용됩니다. 이 메서드의 반환값은 dict_values
객체입니다. 이 객체도 dict_keys
와 마찬가지로 리스트와 유사하게 작동하지만, 리스트의 메서드를 모두 지원하는 것은 아닙니다. 그럼에도 불구하고, dict_values
객체는 반복문과 함께 사용하여 순회할 수 있습니다.
dict_values
객체를 리스트로 변환하려면, 마찬가지로 list()
함수를 사용하면 됩니다. 예를 들어, list(person.values())
는 ['licat', 'Jeju', 'Developer']
을 반환합니다.