Python 내장 모듈인 collections에 대해 알아보자. collections모듈은 기본 컨테이너 객체의 편리성을 더 확장시키는 특별한 컨테이너 데이터 타입들을 모아놓은 모듈이다. 즉, 기본 컨테이너 타입의 확장판. 각종 데이터들의 전처리를 위한 유용한 기능들이 있다.
추가적으로 파이썬에서 컨테이너란, 데이터의 종류에 무관하게 저장이 가능한 객체를 말한다. 해당되는 객체와 아닌 것을 비교해보면 쉬운데 list, dictionary, tuple, dict, str등이 컨테이너 타입이고 반대로 int, float 등은 데이터의 종류가 고정되어 있는 Literal한 타입이다. 실제로 컨테이너 객체 클래스들은 컨테이너 안에 어떤 값이 포함되어 있는지 확인할 수 있는 __contains__ 던더 메소드를 가지고 있고 우리가 흔히 쓰는 in 키워드를 이용해 호출할 수 있다.
다시 collections 모듈로 넘어가서 collections모듈에서 사용빈도가 높은 Counter클래스는 딕셔너리를 확장시킨 서브클래스로 해셔블한 객체들의 아이템 발생빈도를 카운트해 딕셔너리 형태로 저장한다. 또 Counter객체의 특별한 점은 객체끼리 연산이 가능하다는 점이다. 아래에서 더 자세히 알아보자.
Counter(iterable or mapping)
해서블한 객체들의 수를 세기 위한 딕셔너리 서브 클래스. 요소(element)는 key, 그 요소의 개수는 value로 저장한다. 또한 딕셔너리를 상속받은 서브클래스이므로 딕셔너리의 메소드를 대부분 사용할 수 있다. 반대로 딕셔너리와 다른 점을 꼽자면 첫째로 Counter내에 존재하지 않는 값을 인덱싱하면 딕셔너리처럼 KeyError를 발생시키는 게 아니라 0을 반환한다는 점이다. 두번째로는 딕셔너리의 update 메소드는 값을 교체하지만 Counter의 update 메소드는 값을 추가한다는 점이다. 예제에서 자세히 알아보자.
from collections import Counter
# 1
c = Counter('Great barrier reef!')
print(c)
print(c['r'])
# 2
c_2 = Counter({'a': 1, 'b': 2, 'c': 3})
c_3 = Counter(b=3, c=4, d=5)
print(c_2 + c_3)
c_2.subtract(c_3)
print(c_2)
print(list((c_2).elements()))
# 3
c = Counter('Great barrier reef!')
print(c.most_common(3))
print(list(c.elements()))
# 4
dict_ = dict({'a': 1, 'b': 2, 'c': 3})
c_4 = Counter({'a': 1, 'b': 2, 'c': 3})
dict_.update({'a': 10})
c_4.update({'a': 10})
print(dict_)
print(c_4)
# 1
Counter({'r': 5, 'e': 4, 'a': 2, ' ': 2, 'G': 1, 't': 1, 'b': 1, 'i': 1, 'f': 1, '!': 1})
5
# 2
Counter({'c': 7, 'b': 5, 'd': 5, 'a': 1})
Counter({'a': 1, 'b': -1, 'c': -1, 'd': -5})
['a']
# 3
[('r', 5), ('e', 4), ('a', 2)]
['G', 'r', 'r', 'r', 'r', 'r', 'e', 'e', 'e', 'e', 'a', 'a', 't', ' ', ' ', 'b', 'i', 'f', '!']
# 4
{'a': 10, 'b': 2, 'c': 3}
Counter({'a': 11, 'c': 3, 'b': 2})
# 1
- 대표적인 컨테이너 객체 str을 Counter에 넣었을 때 각 string이 많은 순으로 출력됐다.
- 또한 기존 딕셔너리처럼 키값으로 요소에 접근할 수 있다.
# 2
- Counter객체의 특별한 점은 연산이 가능하다는 것이다.
- c_2와 c_3 두 객체를 더했을 때 각 요소의 개수가 더해진 값이 출력됐다.
- c_2에 c_3를 뺐(subtract)을 때 각 요소의 개수를 뺀 값이 출력됐다. 음수 값을 포함하고 있다는 게 주목할만 하다.
- 그냥 c_2 - c_3을 진행했다면 음수값을 제외한 Counter({'a':1}]가 출력된다.
- 현재 c_2는 음수 값을 포함하고 있는데 여기서 elements 메소드를 호출하면 음수는 무시하고 모든 요소를 출력한다.
# 3
- Counter의 most_common메소드는 요소의 값이 많은 순으로 잘라서 출력할 수 있다
- elements메소드는 Counter의 모든 요소를 개수만큼 출력하는데 출력되는 순서는 원본 데이터의 순서다. 보시다시피 'G'부터 먼저 출력됐다.
# 4
- dict의 update와 Counter의 update는 조금 다른데, dict의 update는 값 자체를 바꾸지만, Counter의 update는 값을 추가해준다.
deque(iterable[, maxlen])
"double-ended queue"의 약자로 후입선출인 스택과 선입선출인 큐를 합쳐 앞뒤로 데이터를 처리할 수 있는 양방향 자료형이다. 데크(deck)라고 읽는다. list객체는 pop, insert 등의 연산을 할 때 O(n)의 시간복잡도가 발생하는 반면, deque는 append와 pop으로 양쪽에 데이터를 넣고 뺄 때 O(1)의 시간복잡도가 발생하므로 시간적인 이점이 있다.
최대길이인 maxlen을 지정하면 maxlen 길이 이상의 값을 추가할 때 반댓쪽 끝의 값이 삭제된다.
from collections import deque
d = deque([1, 2, 3, 4, 5])
# 1
d.append(10)
d.appendleft(-10)
print(d)
# 2
d.pop()
d.popleft()
print(d)
# 3
d.clear()
print(d)
# 4
d = deque([1, 2, 3, 4, 5])
d_2 = deque([6, 7, 8, 9, 10])
d.extendleft(d_2)
print(d)
deque([-10, 1, 2, 3, 4, 5, 10])
deque([1, 2, 3, 4, 5])
deque([])
deque([10, 9, 8, 7, 6, 1, 2, 3, 4, 5])
# 1
- append로 deque 오른쪽에 10, appendleft로 왼쪽에 -10을 추가했다.
# 2
- pop으로 deque 오른쪽을 삭제하고, popleft로 deque 왼쪽을 삭제했다
# 3
- clear메소드는 deque의 모든 요소를 제거하고 길이가 0인 상태로 만든다.
# 4
- extend메소드는 오른쪽에 요소를 하나씩 붙여 확장하는데, extendleft는 왼쪽에 값을 하나씩 붙여서 확장시키므로 역순으로 출력되는 결과를 얻었다.
이외에도 deque는 count, insert, reverse 등 iterable한 객체에서 사용할 수 있는 메소드들도 지원하지만, 양쪽 끝에서 접근하는 연산에 대해서는 O(1)이고 중간을 포함한 연산은 O(n)의 시간복잡도를 가지므로 중간의 값을 포함한 연산을 쓸 예정이라면 그냥 list를 쓰는 게 낫다.
ChainMap(*maps)
여러 dict객체를 모아서 하나의 dict객체로 맵핑해 검색할 수 있다. Python 3.3버전에서 추가된 기능.
ChainMap객체에 key를 검색하면 Chainmap의 첫번째 딕셔너리부터 탐색하다가 값이 있으면 그 값을 리턴한다.
from collections import ChainMap
car_inspection_2023 = {
'engine': 'Clear',
'tire': '2mm',
'rear indicator': 'Bad'
}
car_inspection_2022 = {
'engine': 'Clear',
'brake': 'Bad',
'horn': 'Clear',
'disk brake': 'Rusty'
}
car_inspection_2021 = {
'engine': 'Bad',
'gear': 'Clear',
'steering wheel': 'Clear'
}
inspection_history = ChainMap(
car_inspection_2023, car_inspection_2022, car_inspection_2021)
# 1
print(inspection_history)
# 2
print(inspection_history['brake'])
print(inspection_history['engine'])
print(inspection_history['steering wheel'])
print(inspection_history['window'])
# 1
ChainMap({'engine': 'Clear', 'tire': '2mm', 'rear indicator': 'Bad'},
{'engine': 'Clear', 'brake': 'Bad', 'horn': 'Clear', 'disk brake': 'Rusty'},
{'engine': 'Bad', 'gear': 'Clear', 'steering wheel': 'Clear'})
# 2
Bad
Clear
Clear
KeyError
# 1
- brake는 2023년 검사에서 검사하지 않았으므로 첫번째 딕셔너리에 없다. 그럼 다음 딕셔너리로 넘어가서 2022년 검사에서 brake를 발견했으므로 value를 리턴한다.
- engine은 2023년 검사에서 검사했으므로 바로 Clear를 리턴한다. 2021년의 Bad 데이터는 무시
- steering wheel은 2021년 검사에 있으므로 2021년 검사에서 리턴한다.
- 모든 딕셔너리에 없는 값을 요청하면 KeyError가 리턴된다.
예제를 만들긴 했는데 솔직히 쓸 일이 있을까 싶다
collections 모듈에는 이외에도 namedtuple, OrderedDict, UserDict등의 함수가 있지만 파이썬이 업데이트되면서 내장된 dict, list 등에 위 함수에 대한 기능까지 추가되었으므로 앞으로 거의 사용되지 않을 것 같다. 자주 쓰이는 Counter, deque를 기억하면 좋을 것 같다.
'프로그래밍 > Python' 카테고리의 다른 글
[Python] 파이썬에서 2의 보수 체계 적용기 (0) | 2023.07.12 |
---|---|
[Python] 의존성 관리툴 poetry (2) | 2023.05.30 |
[DRF] dj-rest-auth - email인증 및 User모델 필드 커스텀하기 (2) | 2023.05.16 |
[Django] 장고 기초 - (5) Query Set(2) (0) | 2023.04.27 |
[Django] 장고 기초 - (4) Query Set(1) (2) | 2023.04.22 |