☆IT 개발 프로그램☆/Phthon

[파이썬 Collections API] 파이썬3의 자료구조 컨테이너 모듈

호기심을 품고사는 중 2020. 6. 4. 15:01

개요

 

파이썬의 collections 모듈은, 파이썬에 내장된 일반 자료형의(dict, list, set, tuple) 컨테이너 타입을 조금 더 발전시킨 형태의 구현체이다. 파이썬 2.7 까지는 네임드튜플, 디큐, 카운터, 순서형 딕셔너리, 기본 딕셔너리의 다섯 개의 컨테이너를 구현하고 있었으나 파이썬 3부터는 체인맵, 유저 딕셔너리, 유저 리스트, 유저 스트링 등의 자료구조가 추가되었다.

 


 

바로가기

 

1. namedtuple() 네임드 튜플

2. deque 큐

3. Counter 카운터

4. OrderedDict 순서형 딕셔너리

5. defaultDict 기본형 딕셔너리

6. ChainMap 체인맵

 

 

자료형 요약
namedtuple() 다수의 필드 정보를 가지는 데이터를 저장할 때, 클래스의 객체를 생성하여 저장하는 대신 튜플의 서브클래스를 만들어 저장하는 형태. 객체형태보다 메모리 소모가 적으므로 공식 메뉴얼에서 추천하는 방법 
deque 양 쪽으로 데이터의 append, pop이 가능한 큐
ChainMap 여러 맵의 단일 뷰를 만들어주는 클래스. 서로 다른 데이터를 가지는 맵들을 체인처럼 묶어줌으로써 하나의 단일 딕셔너리처럼 취급할 수 있다.
Counter 딕셔너리(dict)의 서브클래스로, 객체의 개수를 세어주는 모듈. 객체를 해시화하여 카운트하므로, 해싱이 가능한 객체여야 한다.
OrderedDict 딕셔너리(dict)의 서브클래스로, 추가된 엔트리의 순서를 기억하는 딕셔너리
defaultdict 딕셔너리(dict)의 서브클래스로, 존재하지 않는 key을 호출해도 에러를 내지 않는 딕셔너리
UserDict dict 객체를 감싸는 Wrapper 역할을 수행하는 클래스
UserList list 객체를 감싸는 Wrapper 역할을 수행하는 클래스
UserString string 객체를 감싸는 Wrapper 역할을 수행하는 클래스

 


 

 

namedtuple

다수의 필드 정보를 가지는 데이터를 저장할 때, 클래스의 객체를 생성하여 저장하는 대신 튜플의 서브클래스를 만들어 저장하는 형태. 

 

사용 예시

## Syntax
from collections import namedtuple

Book = namedtuple('Book', ['title', 'auther', 'price'])

book1 = Book('Millenium', 'Steve larsson', 12000)


## test

print("%s, %s, %s" % (book1.title, book1.auther, book1.price))
## expected: Millenium, Steve larsson, 12000

print(book1)
## expected: Book(title='Millenium', auther='Steve larsson', price=12000)

 

네임드 튜플에 Book이라는 타입 이름을 가지는 서브클래스를 생성하였다. Book은 title, auther, price라는 각각의 필드를 가진다. Book이라는 클래스를 새로 생성하여 사용하는 대신, 네임드 튜플이라는 자료형에 저장하여 사용하고 있다.

 

csv나 sqlite3 모듈을 임포트 할 때 유용하게 사용될 수 있다.

EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')

import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
    print(emp.name, emp.title)

import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
    print(emp.name, emp.title)

 

csv 파일에서 불러온 각각의 엔티티는 EmployeeRecord라는 오브젝트로 맵핑하여 저장한다. 각각의 칼럼은 name, age, title, department, paygrade 라는 필드 타입으로 치환되었다.

 

sqlite3 쪽도 마찬가지이다. 쿼리를 실행하여 얻은 각각의 엔티티가 EmployeeRecord라는 객체로 맵핑되었고, 각 칼럼은 필드로 맵핑하여 저장하였다.

 


 

 

 

deque

덱 : 양쪽으로 데이터의 삽입과 삭제가 가능한 큐

 

사용 예시

from collections import deque

q = deque('abc')

for elm in q:
    print(elm)
    
# expected : a b c

q.append('d')
q.appendleft('e')

q

# expected : deque(['e', 'a', 'b', 'c', 'd'])

q.pop()

# expected : 'd'

q.popleft()

# expected : 'e'


메소드 DESC
append(*) 덱의 오른쪽으로 단일 데이터를 삽입한다.
appendleft(*) 덱의 왼쪽으로 단일 데이터를 삽입한다.
pop(*) 덱의 오른쪽에서 단일 데이터를 삭제한다.
popleft(*) 덱의 왼쪽에서 단일 데이터를 삭제한다.

 

from collections import deque

q = deque('abc')

q.extend('xyz')

q

# expected : deque(['a', 'b', 'c', 'x', 'y', 'z'])

q.extendleft('xyz')

q

# expected : deque(['z', 'y', 'x', 'a', 'b', 'c', 'x', 'y', 'z'])

q.clear()

q

# expected : deque([])
메소드 DESC
extend(*) 덱의 오른쪽으로 다수 데이터를 삽입한다.
extendleft(x) 덱의 왼쪽으로 다수 데이터를 삽입한다.
clear() 덱의 데이터를 모두 삭제한다

 


 

 

Counter

객체를 해쉬화하여 그 개수를 세어주는 모듈. 배열의 아이템의 발생빈도를 카운트하여 저장한다.

 

사용 예시

카운터에 배열을 삽입하였다. 이 배열에는 'red'가 2개, 'blue'가 3개, 'green'이 1개 들어가 있다. 카운터를 출력해보면 배열 안의 아이템들의 빈도를 계산하여 Counter({'blue': 3, 'red': 2, 'green': 1}) 을 리턴한다.

cnt = Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])

cnt

# : Counter({'blue': 3, 'red': 2, 'green': 1})

 

카운터에 자료형을 삽입할 때에는 위처럼 배열구조가 아니라, 아래처럼 맵 구조로 삽입하여도 무방하다.

c1 = Counter('gallahad')
c2 = Counter({'red': 4, 'blue': 2})
c3 = Counter(cats=4, dogs=8) 


c1
# Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1})
c2
# Counter({'red': 4, 'blue': 2})
c3
# Counter({'dogs': 8, 'cats': 4})

 

특정 아이템의 카운트만 지우고 싶다면 del 명령어를 사용한다.

cnt = Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])

cnt

# Counter({'blue': 3, 'red': 2, 'green': 1})

del cnt['red']

cnt

# Counter({'blue': 3, 'green': 1})

 

 

OrderedDict

딕셔너리(dict)의 서브클래스로, 추가된 엔트리의 순서를 기억하는 딕셔너리. 

 

비교 예시

(파이썬 버전 3.6까지) 기존의 dict와  OrderedDict에 'a', 'b', 'c', 'd'를 순서대로 삽입하였을 때 dict는 삽입 순서를 기억하지 못하고 'a', 'c', 'b',  'd'로 출력하는 반면 OrderedDict는 삽입한 순서에 따라 자료를 정렬하여 출력한다. 

from collections import OrderedDict 
  
d = {}
d['a'] = 1
d['b'] = 2
d['c'] = 3
d['d'] = 4

d
# {'a': 1, 'c': 3, 'b': 2, 'd': 4}

od = OrderedDict() 
od['a'] = 1
od['b'] = 2
od['c'] = 3
od['d'] = 4

od
# OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])

*참고로 파이썬 3.7부터는 dict 라이브러리도 삽입순서를 유지한다. 파이썬 3.7부터 OrderedDict와 dict의 차이점은 == 연산자의 민감성, move_to_end 메소드, reversed 이터레이터의 사용 여부뿐이다.  

## Python 3.7

OrderedDict([(1,1), (2,2)]) == OrderedDict([(2,2), (1,1)]) 
# False
dict([(1,1), (2,2)]) == dict([(2,2), (1,1)]) 
# True

 

defaultdict 

1) 초기화 시에 Value 형의 타입을 정의할 수 있다.

2) 딕셔너리(dict)의 서브클래스로, 존재하지 않는 key을 호출해도 에러를 내지 않는 딕셔너리이다.

 

사용 예시

 

가령 [(한식, 떡볶이), (한식, 잡채), (한식, 불고기), (양식, 햄버거)]라는 자료형들을 가지고 있을 때, 음식 분류형을 키값으로 하고 그에 대응되는 음식 이름들을리스트로 가지는 딕셔너리가 만들고 싶다고 하자. 이 때, defaultdict(list)로 초기화하여 사용한다. 그러면, 위의 자료형들은 변환 시 [(한식, [떡볶이, 잡채, 불고기], (양식, [햄버거])] 의 리스트 딕셔너리로 대응될 것이다.

from collections import defaultdict

s = [('한식', '떡볶이'), ('한식', '불고기'), ('한식', '닭갈비'), ('양식', '햄버거'), ('양식', '파스타')]
d = defaultdict(list)
for k, v in s:
     d[k].append(v)

sorted(d.items())
# [('양식', ['햄버거', '파스타']), ('한식', ['떡볶이', '불고기', '닭갈비'])]

d['일식']
# []

 

딕셔너리에 존재하지 않는 key값인 '일식'을 호출하면, keyError를 내는 대신 list 타입의 default null 값인 []를 리턴한다.

 

from collections import defaultdict

d = defaultdict(int)
d['a'] = 1
d['b'] = 2

d['z']
# 0

 

그러면 defaultdict(int)로 초기화한 딕셔너리에서 존재하지 않는 key값을 호출한다면? int 타입의 default null 값인 0을 리턴한다.

 


 

 

 

 

ChainMap

여러 맵의 단일 뷰를 만들어주는 클래스. 서로 다른 데이터를 가지는  맵 여러 개를 합쳐서 중복을 제외하고 하나의 단일 딕셔너리로 만들어 주는 자료구조이다. 맵들을 체인처럼 묶어준다는 뜻에서 체인맵이라고 부른다.

 

사용 예시

from collections import ChainMap

a = {'a': 'A', 'c': 'C1'}
b = {'b': 'B', 'c': 'C2'}

m = ChainMap(a, b)

m
# ChainMap({'a': 'A', 'c': 'C1'}, {'b': 'B', 'c': 'C2'})

list(m.keys())
# ['b', 'c', 'a']

list(m.values())
# ['B', 'C1', 'A']

 

체인맵에 각각 다른 데이터를 가지는 두 맵 a, b를 넣어 m이라는 통합 맵을 만들었다. key 값은 unique 해야 하므로, 두 맵이 중복된 key값을 가질 시에는 먼저 주입한 맵의 데이터를 사용한다. 중복 키 'c'에 대해서는 a가 더 먼저 주입되었으므로 value값으로 'C1'이 사용된다.

 

m = ChainMap(b, a)

list(m.values())
# ['B', 'C2', 'A']

물론 체인맵에 b를 먼저 주입한다면 중복 키의 value는 b에서 가져와 삽입되어 'C2'가 된다.