2.37. 네임드 튜플과 데크(deque)¶
리스트, 튜플, 딕셔너리, 세트는 대부분의 데이터 요구를 처리합니다. collections 모듈의 다른 세 가지 컨테이너는 내장 자료형이 어설프게 처리하는 특정 문제에 들어맞습니다.
2.37.1. namedtuple – 클래스 없는 타입 레코드¶
일반 튜플은 값을 위치로 저장합니다. 작고 수명이 짧은 2- 또는 3-튜플에는 괜찮지만, 그 이상이 되면 point[0] 과 point[1] 은 무엇을 담고 있는지에 대해 거짓말을 하기 시작합니다. collections.namedtuple() 은 필드에 이름이 있는 새로운 튜플 서브클래스를 반환합니다:
>>> from collections import namedtuple
>>> Reading = namedtuple('Reading', ('temp', 'humidity', 'ts'))
>>> r = Reading(22.5, 41.0, 137204)
>>> r.temp
22.5
>>> r.humidity
41.0
>>> r[0]
22.5
fields 인수는 이름 문자열의 시퀀스입니다(또는 CPython에서는 공백으로 구분된 하나의 문자열. MicroPython은 더 엄격합니다 – 튜플이나 리스트를 전달하세요).
클래스 대신 namedtuple 을 쓰는 이유는?
튜플이기 때문입니다. 반복, 언패킹, 동등성 비교, 해싱, 딕셔너리 키로의 사용이 모두 거저 동작합니다.
불변(immutable)이기 때문입니다.
r.temp = ...로 재할당하면AttributeError가 발생하는데, 이는 레코드 타입에 정확히 원하는 동작입니다.동일한 필드를 가진 클래스 인스턴스보다 RAM을 덜 사용하기 때문입니다 – 튜플의 저장소는
__dict__없이 연속적입니다.
동등한 클래스와 비교하면 namedtuple 선언은 한 줄입니다. 절충점은 필드가 읽기 전용이라는 것입니다 – 판독값을 “변경”하려면 새로 만들어야 합니다.
2.37.2. deque – 크기가 제한된 링 버퍼¶
리스트는 끝 에서는 빠르지만(append / pop) 시작 에서는 느립니다(insert(0, ...) / pop(0) 둘 다 다른 모든 요소를 이동시킵니다). collections.deque 는 양쪽 끝 모두에서 빠릅니다 – 머리(head)와 꼬리(tail) 포인터로 인덱싱되는 링 버퍼이므로, 데크가 몇 개의 항목을 담고 있든 양쪽 어느 쪽에서든 append와 pop이 동일한 고정된 작업량으로 실행됩니다.
MicroPython에서 생성하려면 초기 이터러블 과 최대 길이가 그 순서대로 모두 필요합니다:
>>> from collections import deque
>>> events = deque((), 5)
>>> for i in range(8):
... events.append(i)
>>> list(events)
[3, 4, 5, 6, 7]
크기가 제한된 데크가 가득 차면 모든 append 는 가장 오래된 항목을 버립니다. 이로 인해 데크는 “마지막 N개의 샘플”, “마지막 N개의 로그 줄”, 또는 영원히 커질 필요가 없는 모든 롤링 윈도우에 이상적입니다.
MicroPython의 데크가 노출하는 메서드는 의도적으로 최소한입니다:
append(x)– 오른쪽에 추가.appendleft(x)– 왼쪽에 추가.extend(iterable)– 이터러블의 각 항목을 추가.pop()– 오른쪽 끝을 제거하고 반환. 비어 있으면IndexError발생.popleft()– 왼쪽 끝을 제거하고 반환.
CPython의 데크에서 빠진 주목할 만한 것들: clear, count, index, remove, reverse, rotate, maxlen 속성, __contains__ 가 없습니다. 반복과 첨자 인덱싱은 동작합니다:
>>> events[0]
3
>>> for e in events:
... print(e)
전형적인 사용 예: 추세 변화를 감지하기 위해 마지막 몇 개의 센서 판독값을 유지하기:
history = deque((), 10)
def push(reading):
history.append(reading)
if len(history) == 10 and history[-1] > 2 * history[0]:
print('reading is climbing')
2.37.3. OrderedDict – 순서가 동등성의 일부일 때¶
일반 dict 는 MicroPython 1.13과 CPython 3.7 이후로 삽입 순서를 보존해 왔습니다. 이는 사람들이 예전에 collections.OrderedDict 를 찾던 가장 흔한 이유를 해결합니다.
일반 딕셔너리가 주지 못하는 것을 OrderedDict 가 여전히 제공하는 것:
OrderedDict의 동등성은 순서를 고려합니다. 두 일반 딕셔너리는 삽입 순서와 관계없이 동일한 키/값 쌍을 가지면 언제든 같다고 비교됩니다. 두OrderedDict인스턴스는 쌍이 같은 순서일 때만 같습니다:>>> from collections import OrderedDict >>> OrderedDict([('a', 1), ('b', 2)]) == OrderedDict([('b', 2), ('a', 1)]) False >>> {'a': 1, 'b': 2} == {'b': 2, 'a': 1} True
OrderedDict는 키 순서를 실제로 신경 쓰는 형식(TOML, 일부 YAML 소비자)으로 설정을 직렬화할 때, 또는 코드 독자에게 순서가 중요하다는 명확한 문서적 힌트를 주고 싶을 때 유용합니다.
일상적인 코드에서는 내장 dict 를 선호하세요. 순서가 동등성의 일부라는 의미론이나 문서적 가치가 실제로 무언가를 얻게 해줄 때만 OrderedDict 를 찾으세요.
이 세 컨테이너는 각각 하나의 좁은 용도에 들어맞습니다. 네임드 튜플은 손으로 만든 레코드 클래스를 대체하고, 데크는 크기가 제한된 큐에 대해 리스트를 대체하며, 정렬된 딕셔너리는 삽입 순서를 계약의 일부로 만듭니다.