개요
이번 포스트에서 다룰 내용은 딕셔너리를 좀더 fancy하게 다루는 방법들을 알아보려고 한다. 물론 기본 문법(for loop, if, ..)들로도 충분히 원하는 딕셔너리에 대한 데이터 전처리가 가능하지만, 가능한 가독성이 좋고 Python에서 지원하는 문법들을 적극 활용하여 딕셔너리를 다룰 수 있는 방법들이 있다.
딕셔너리 컴프리헨션 (Dictionary Comprehension)
# 컴프리헨션 적용 전
squares = {}
for x in range(10):
squares[x] = x ** 2
# 컴프리헨션 적용 후 ({키: 밸류 for 원소 in 반복할 iterable})
squares = {x: x ** 2 for x in range(10)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
- 위 코드는 Python에서 지원하는 Comprehension 문법을 딕셔너리에 적용한 것이다.
- 물론 for 문을 통해서도 동일한 역할을 수행할 수 있게 코드를 작성할 수 있지만, 라인 수 및 보일러 플레이트를 줄이고 시간 복잡도 측면에서도 상수 수준이지만 2~3배 연산 시간을 절약할 수 있는 장점이 있다.
기본값 설정 (defaultdict, setdefault)
d = {}
keys = ['a', 'b', 'c']
for key in keys:
d[key] = []
- 위 예시는 빈 딕셔너리에 리스트를 기본값으로 키-밸류 쌍을 할당하는 기본적인 코드이다.
- 위 코드에서 문제점은 런타임 도중 정해진 키 값 이외에 키로 딕셔너리에서 참조하려고 하면 KeyError 오류를 발생시키는 문제이다. 만약 예측할 수 없는 로직에서는 동적으로 참조하려는 키 값에 대해 없는 경우에 대해서도 기본값을 반환시켜 준다면 애플리케이션이 비정상 종료하는 참사를 막을 수 있다.
d.setdefault('d', [])
d.setdefault('e', []).append(1)
key_f = d.setdefault('f', [])
key_f.append(2)
print(d) # {'a': [], 'b': [], 'c': [], 'd': [], 'e': [1], 'f': [2]}
print(d.setdefault('e', [])) # [1]
- 이럴 때에는 dict 클래스의 setdefault 메소드를 사용하면 된다. d 딕셔너리 또한 dict 타입의 객체이기 때문에 내장된 메소드를 호출할 수 있다.
- setdefault의 파라미터는 참조하려는 키와 해당 키에 대한 기본값을 전달하면 된다. 만약 해당 키 값에 해당하는 밸류가 있을 경우 그대로 반환한다.
- 하지만, 참조하려는 키가 딕셔너리에 존재하지 않으면 인자로 전달한 기본값을 그대로 반환하게 되는데, 이 기본값은 call by object reference에 의해 전달된다.
- Python에서는 call by reference 대신 객체의 참조(메모리 주소)가 전달된다.
- 그래서, d.setdefault('e', []).append(1) 라인은 아래와 같이 동작한다.
- d.setdefault('e', [])는 새로운 비어있는 리스트 객체를 생성하고, 해당 객체의 메모리 주소를 setdefault의 두 번째 인자로 전달한다.
- setdefault 함수는 해당 빈 리스트 객체의 주소를 반환하고, 이어서 .append(1) 함수를 호출하여 리스트에 1을 추가한다.
- 마치 독립적인 리스트에 1을 추가한 것 같지만, 실제로는 딕셔너리 d가 참조하고 있는 리스트에 1을 추가한 것이기 때문에 print(d)를 보면 결과값에 'e': [1]이 있는 것이다.
from collections import defaultdict
d = defaultdict(int)
print(d['a']) # 0
d = defaultdict(list)
print(d['a']) # []
class Dummy:
def __init__(self, name = 'default'):
self.name = name
d = defaultdict(Dummy)
print(d['a'].name) # default
- setdefault 메소드를 통해 참조와 동시에 기본값을 할당받는 방법도 있지만, collections 패키지의 defaultdict 클래스를 활용한 방법도 있다. defaultdict 클래스는 정확히는 dict 타입은 아니고 dict 클래스를 상속받는 하위 클래스다.
- 위 코드처럼 기본으로 초기화하고 싶은 클래스를 인자로 전달하면 딕셔너리에 없는 키로 참조할 때, 새로운 객체를 초기화하여 반환한다.
def constant_factory(value):
return lambda: value
d = defaultdict(constant_factory('<missing>'))
print(d['a']) # <missing>
- 클래스가 아니더라도 인자로 callable을 받을 수 있기 때문에 위 코드처럼 팩토리 메소드를 인자로 전달할 수도 있다. (Python의 함수형 프로그래밍 - 함수를 일급객체로 인식)
딕셔너리 병합 및 업데이트
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
dict1.update(dict2)
print(dict1) # {'a': 1, 'b': 3, 'c': 4}
- dict 클래스 내의 update 메소드를 호출하여 타 딕셔너리의 키-밸류를 호출한 dict 객체에 병합시킬 수 있다.
- 물론 키 값이 겹칠 경우, 밸류는 오버라이딩 된다. ('b': 2 -> 3)
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
dict3 = dict1 | dict2
print(dict3) # {'a': 1, 'b': 3, 'c': 4}
- Python 3.9 버전 이후로는 합집합 연산자(|)를 사용하여 병합할 수도 있다.
'[Language] - Python' 카테고리의 다른 글
| 데코레이터 (Decorator) (0) | 2021.11.09 |
|---|---|
| Python 객체의 생명 주기와 메모리 (0) | 2021.08.02 |
| [Tip] win32com 모듈을 사용한 엑셀 제어 (0) | 2021.04.20 |
| 제너레이터 (Generator) (0) | 2021.04.14 |
| 함수형 프로그래밍 (0) | 2021.03.25 |
