본문 바로가기
프로그래밍/파이썬

파이썬 - 표준 라이브러리 collections 모듈

by ennak 2024. 12. 2.
반응형

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 프로그래밍의 생산성을 크게 높일 수 있다. 다양한 예제를 통해 각 데이터 구조의 특성과 사용 사례를 이해하고, 실제 프로젝트에 적용해보는 것이 중요하다.

반응형