Intro
Python 프로그래밍 언어는 풍부한 표준 라이브러리를 자랑한다. 그 중에서도 collections 모듈은 다양한 특수 컨테이너 데이터타입을 제공하여 개발자들의 코딩 경험을 한층 더 풍성하게 만든다. 이 모듈은 기본 내장 컨테이너(list, tuple, dict, set)를 확장하거나 대체할 수 있는 특별한 데이터 구조들을 포함하고 있다.
namedtuple(): 이름 있는 필드를 가진 튜플 서브클래스
namedtuple()은 튜플의 서브클래스를 생성하는 팩토리 함수다. 이 함수를 사용하면 인덱스뿐만 아니라 이름으로도 필드에 접근할 수 있는 튜플을 만들 수 있다.
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(11, y=22)
print(p[0], p.x) # 11 11
print(p[1], p.y) # 22 22
namedtuple은 데이터베이스 레코드나 CSV 파일의 행을 표현할 때 특히 유용하다. 일반 튜플에 비해 코드의 가독성을 크게 향상시키며, 필드 이름을 통해 데이터의 의미를 명확히 할 수 있다.
deque: 양방향 큐
deque(double-ended queue)는 양쪽 끝에서 빠르게 추가와 삭제를 할 수 있는 리스트류 컨테이너다. 리스트와 달리 양쪽 끝에서의 연산이 O(1)의 시간 복잡도를 가진다.
from collections import deque
d = deque(['task1', 'task2', 'task3'])
d.append('task4') # 오른쪽에 추가
d.appendleft('task0') # 왼쪽에 추가
print(d) # deque(['task0', 'task1', 'task2', 'task3', 'task4'])
d.pop() # 오른쪽에서 제거
d.popleft() # 왼쪽에서 제거
print(d) # deque(['task1', 'task2', 'task3'])
deque는 큐와 스택의 기능을 모두 갖추고 있어 다양한 알고리즘 구현에 활용될 수 있다. 특히 너비 우선 탐색(BFS)이나 캐시 구현 등에 적합하다.
Counter: 요소의 개수를 세는 딕셔너리
Counter는 해시 가능한 객체를 세는 데 사용되는 딕셔너리의 서브클래스다. 요소를 키로, 그 개수를 값으로 저장한다.
from collections import Counter
c = Counter('gallahad')
print(c) # Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1})
c = Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])
print(c) # Counter({'blue': 3, 'red': 2, 'green': 1})
Counter 객체는 딕셔너리의 모든 메서드를 지원하면서도, 요소의 개수를 세는 데 특화된 추가적인 메서드들을 제공한다. 예를 들어, most_common() 메서드는 가장 흔한 요소들을 내림차순으로 반환한다.
OrderedDict: 순서를 기억하는 딕셔너리
OrderedDict는 키의 삽입 순서를 기억하는 딕셔너리의 서브클래스다. Python 3.7 이후로는 일반 딕셔너리도 삽입 순서를 유지하지만, OrderedDict는 여전히 유용한 추가 기능을 제공한다.
from collections import OrderedDict
od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3
print(list(od.keys())) # ['a', 'b', 'c']
# 순서를 고려한 동등성 비교
od1 = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
od2 = OrderedDict([('b', 2), ('a', 1), ('c', 3)])
print(od1 == od2) # False
OrderedDict는 특히 순서가 중요한 데이터를 다룰 때 유용하다. 예를 들어, JSON 데이터의 순서를 유지해야 하는 경우에 적합하다.
defaultdict: 기본값을 지정할 수 있는 딕셔너리
defaultdict는 존재하지 않는 키에 접근할 때 미리 지정한 기본값을 반환하는 딕셔너리의 서브클래스다. 이를 통해 KeyError 예외를 방지하고 코드를 간결하게 만들 수 있다.
from collections import defaultdict
dd = defaultdict(list)
dd['key'].append(1)
dd['key'].append(2)
print(dd) # defaultdict(<class 'list'>, {'key': [1, 2]})
dd = defaultdict(int)
dd['key'] += 1
print(dd) # defaultdict(<class 'int'>, {'key': 1})
defaultdict는 그래프나 트리 구조를 표현할 때 특히 유용하다. 예를 들어, 인접 리스트로 그래프를 구현할 때 각 노드의 이웃 목록을 쉽게 관리할 수 있다.
ChainMap: 여러 딕셔너리를 하나로 묶기
ChainMap은 여러 개의 딕셔너리나 매핑을 하나의 업데이트 가능한 뷰로 묶는다. 내부적으로는 매핑의 목록을 유지하고, 이를 차례로 검색하여 연산을 수행한다.
from collections import ChainMap
baseline = {'music': 'bach', 'art': 'rembrandt'}
adjustments = {'art': 'van gogh', 'opera': 'carmen'}
cm = ChainMap(adjustments, baseline)
print(cm['music']) # 'bach'
print(cm['art']) # 'van gogh'
print(cm['opera']) # 'carmen'
UserDict, UserList, UserString: 사용자 정의 컨테이너 생성
이 세 클래스는 각각 dict, list, str을 래핑하는 클래스다. 이들을 상속받아 커스텀 컨테이너 타입을 쉽게 생성할 수 있다.
from collections import UserDict
class MyDict(UserDict):
def __setitem__(self, key, value):
super().__setitem__(key.upper(), value)
d = MyDict({'name': 'John', 'age': 30})
d['city'] = 'New York'
print(d) # {'NAME': 'John', 'AGE': 30, 'CITY': 'New York'}
이러한 래퍼 클래스들은 기본 타입을 직접 상속받는 것보다 안전하고 쉽게 커스터마이징할 수 있게 해준다.
collections 모듈의 활용
collections 모듈의 데이터 구조들은 다양한 상황에서 유용하게 활용될 수 있다. 몇 가지 실제 사용 예를 살펴보자.
1. 로그 분석에서의 Counter 활용
웹 서버 로그에서 가장 많이 방문한 IP 주소를 찾는 경우를 생각해보자.
from collections import Counter
def analyze_log(log_file):
ip_counts = Counter()
with open(log_file, 'r') as f:
for line in f:
ip = line.split()[0] # IP 주소가 각 줄의 첫 번째 필드라고 가정
ip_counts[ip] += 1
return ip_counts.most_common(5) # 상위 5개 IP 반환
top_ips = analyze_log('access.log')
for ip, count in top_ips:
print(f"IP: {ip}, 방문 횟수: {count}")
이 예제에서 Counter는 IP 주소의 출현 빈도를 쉽게 계산하고, most_common() 메서드를 통해 가장 빈번한 방문자를 찾아낸다.
2. 캐시 구현에서의 OrderedDict 활용
LRU(Least Recently Used) 캐시를 구현할 때 OrderedDict를 활용할 수 있다.
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key):
if key not in self.cache:
return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False)
cache = LRUCache(2)
cache.put(1, 1)
cache.put(2, 2)
print(cache.get(1)) # 1
cache.put(3, 3) # 2가 제거됨
print(cache.get(2)) # -1 (없음)
OrderedDict의 move_to_end() 메서드를 사용하여 최근 접근한 항목을 끝으로 이동시키고, popitem(last=False)로 가장 오래된 항목을 제거한다.
3. 그래프 표현에서의 defaultdict 활용
그래프를 인접 리스트로 표현할 때 defaultdict를 사용하면 코드를 간결하게 만들 수 있다.
from collections import defaultdict
def create_graph():
graph = defaultdict(list)
edges = [('A', 'B'), ('B', 'C'), ('B', 'D'), ('C', 'D'), ('D', 'A')]
for start, end in edges:
graph[start].append(end)
return graph
graph = create_graph()
print(graph)
# defaultdict(<class 'list'>, {'A': ['B'], 'B': ['C', 'D'], 'C': ['D'], 'D': ['A']})
# 노드 'B'의 이웃 출력
print(graph['B']) # ['C', 'D']
defaultdict(list)를 사용함으로써, 존재하지 않는 키에 대해 자동으로 빈 리스트를 생성하여 KeyError를 방지한다.
4. 설정 관리에서의 ChainMap 활용
애플리케이션의 설정을 관리할 때 ChainMap을 사용하여 여러 소스의 설정을 통합할 수 있다.
import os
from collections import ChainMap
def get_config():
defaults = {'debug': False, 'port': 8000}
cli_args = {'port': 9000}
env_vars = {k: v for k, v in os.environ.items() if k in defaults}
return ChainMap(cli_args, env_vars, defaults)
config = get_config()
print(f"Debug mode: {config['debug']}")
print(f"Port: {config['port']}")
이 예제에서 ChainMap은 명령줄 인자, 환경 변수, 기본값의 우선순위를 유지하면서 설정을 통합한다.
Outro
collections 모듈은 Python 프로그래머에게 강력하고 유연한 데이터 구조 도구를 제공한다. 이 모듈의 다양한 클래스들은 특정 문제를 해결하는 데 최적화되어 있으며, 코드의 가독성과 성능을 향상시킬 수 있다.
- namedtuple()은 구조화된 데이터를 다룰 때 유용하고,
- deque는 양방향 큐가 필요할 때 이상적이다.
- Counter는 요소 계수에 적합하며,
- OrderedDict는 순서가 중요한 매핑에 유용하다.
- defaultdict는 기본값이 필요한 딕셔너리에 적합하고,
- ChainMap은 여러 매핑을 결합할 때 매우 유용하다.
이러한 특수 컨테이너들을 적절히 활용하면, 더 효율적이고 표현력 있는 코드를 작성할 수 있다. 또한, 이들은 표준 라이브러리의 일부이므로 추가적인 설치 없이 즉시 사용할 수 있어 개발자들에게 큰 장점을 제공한다.
collections 모듈을 통해 데이터 구조를 효과적으로 관리하고 활용하는 방법을 익힌다면, Python 프로그래밍의 생산성을 크게 높일 수 있다. 다양한 예제를 통해 각 데이터 구조의 특성과 사용 사례를 이해하고, 실제 프로젝트에 적용해보는 것이 중요하다.
'프로그래밍 > 파이썬' 카테고리의 다른 글
파이썬의 동시성 프로그래밍: asyncio와 코루틴 활용하기 (0) | 2024.12.07 |
---|---|
파이썬 - 시퀀스(Sequence) 타입 심화 (0) | 2024.12.01 |
파이썬 - 컨텍스트 매니저와 with 문 (0) | 2024.12.01 |
파이썬 - 메모리 관리와 가비지 컬렉션 (0) | 2024.12.01 |
파이썬 - 네임드 튜플(Named Tuple)과 데이터 클래스(Data Class) 비교 (0) | 2024.12.01 |