2장 데이터 구조체
2.1. 내장 시퀀스 개요
- 파이썬 표준 라이브러리는 C로 구현된 다음과 같은 시퀀스 형을 제공한다.
- 저장할 수 있는 자료형에 따른 분류
- 컨테이너 시퀀스
- 서로 다른 자료형의 항목들을 담을 수 있음
- list, tuple, collections.deque
- 객체에 대한 참조를 담고 있음
- 균일 시퀀스
- 단 하나의 자료형만 담을 수 있음
- str, bytes, bytearray, memoryview, array.array
- 객체에 대한 참조 대신 자신의 메모리 공간에 각 항목의 값을 직접 담음
- 컨테이너 시퀀스
- 가변성에 따른 분류
- 가변 시퀀스
- list, bytearray, array.array, collections.deque, memoryview
- 불변 시퀀스
- tuple, str, bytes
- 가변 시퀀스
- 저장할 수 있는 자료형에 따른 분류
2.2. 지능형 리스트와 제너레이터 표현식
2.2.1. 지능형 리스트와 가독성
2.2.2. 지능형 리스트와 map()/filter() 비교
- map()과 filter() 함수를 이용해서 수행할 수 있는 작업은 지능형 리스트를 이용해서 모두 구현할 수 있다.
2.2.3. 데카르트 곱
- 예: 두 가지 색상과 세 가지 크기의 티셔츠 리스트 만들기
colors = ['black', 'white'] sizes = ['S', 'M', 'L'] tshirts = [(color, size) for color in colors for size in sizes] print(tshirts)
2.2.4. 제너레이터 표현식
- 시퀀스형을 초기화하려면 지능형 리스트를 사용할 수도 있지만, 다른 생성자에 전달할 리스트를 통째로 만들지 않고 iterator protocol을 이용해서 항목을 하나씩 생성하는 제너레이터 표현식이 메모리를 더 적게 사용한다.
- 제너레이터 표현식은 대괄호 대신 괄호를 사용한다.
symbols = "!@#$%" print(tuple(ord(symbol) for symbol in symbols)) # (33, 64, 35, 36, 37) import array print(array.array('I', (ord(symbol) for symbol in symbols))) # array('I', [33, 64, 35, 36, 37])
- 제너레이터 표현식으로 데카르트 곱 계산
- 제너레이터 표현식은 한 번에 하나의 항목을 생성하며, 6개의 티셔츠 종류를 담고 있는 리스트를 만들지는 않는다.
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
for tshirt in ("%s %s" % (c, s) for c in colors for s in sizes):
print(tshirt)
# black S
# black M
# black L
# white S
# white M
# white L
2.3. 튜플은 단순한 불변 리스트가 아니다
2.3.1. 레코드로서의 튜플
- 튜플의 각 항목은 레코드의 필드 하나를 의미하며, 항목의 위치가 의미를 결정한다.
- 튜플을 레코드로 사용하는 경우, 튜플 안에서의 항목의 위치가 의미를 나타내므로, 정렬하면 그 정보가 파괴된다.
2.3.2. 튜플 언패킹
- 병렬 할당(parallel assignment)
b, a = a, b
a, b = (33, 24)
- 함수를 호출할 때 인수 앞에 *를 붙여 튜플을 언패킹할 수도 있음
t = (20, 8) quotient, remainder = divmod(*t) print(quotient, remainder) # 2 4
- 초과 항목을 잡기 위해 * 사용하기
a, b, *rest = range(5) print(a, b, rest) # 0 1 [2,3,4]
*head, b, c, d = range(5)
print(head, b, c, d)
# [0, 1] 2 3 4
2.3.3. 내포된 튜플 언패킹
- 언패킹할 표현식을 받는 튜플은 (a, b, (c,d))처럼 다른 튜플을 내포할 수 있으며, 파이썬은 표현식이 내포된 구조체와 일치하면 제대로 처리한다.
2.3.4. 명명된 튜플
- collections.namedtuple()
from collections import namedtuple City = namedtuple('City', 'name country population coordinates') tokyo = City('Tokyo', 'JP', 36.933, (35.68, 139.69)) print(tokyo) # City(name='Tokyo', country='JP', population=36.933, coordinates=(35.68, 139.69)) print(tokyo.population) # 36.933
2.3.5. 불변 리스트로서의 튜플
- 튜플은 항목 추가, 삭제,
__reversed__()
메서드를 제외하고 리스트가 제공하는 메서드를 모두 지원한다.
2.4. 슬라이싱
2.4.1. 슬라이스와 범위 지정 시에 마지막 항목이 포함되지 않는 이유
- 슬라이스와 범위 지정 시에 마지막 항목을 포함하지 않는 것의 장점
- range(3)이나 my_list[:3]처럼 중단점만 이용해서 슬라이스나 범위를 지정할 때 길이를 계산하기 쉬움
- 시작점과 중단점을 모두 지정할 때도 길이를 계산하기 쉬움
- 슬라이싱 인덱스를 기준으로 겹침 없이 시퀀스를 분할하기 쉬움 (my_list[:x], my_list[x:]
2.4.2. 슬라이스 객체
- 파이썬은
seq[start:stop:step]
을 표현하기 위해seq.__getitem__(slice(start, stop, step))
을 호출함.
2.4.3. 다차원 슬라이싱과 생략 기호
[]
연산자는 콤마로 구분해서 여러 개의 인덱스나 슬라이스를 가질 수 있음....
: a[i:…] 처럼 슬라이스의 한 부분으로 전달할 수 있음
2.4.4. 슬라이스에 할당하기
l = list(range(10))
l[2:5] = [20, 30]
print(l)
# [0, 1, 20, 30, 5, 6, 7, 8, 9]
2.5. 시퀀스에 덧셈과 곱셈 연산자 사용하기
- 덧셈: 피연산자 두 개가 같은 자료형이여야 하며, 동일한 자료형의 시퀀스가 새로 만들어진다.
- 곱셈: 하나의 시퀀스를 여러 번 연결하여 새로운 시퀀스를 만든다.
- 주의: 시퀀스 내 가변 항목이 있는 경우 곱셈 적용에 주의해야 한다.
2.5.1. 리스트의 리스트 만들기
- 리스트 내 리스트를 초기화할 때는 지능형 리스트를 사용하는 것이 가장 좋다.
# 동일한 리스트에 대한 세 개의 참조를 가진 리스트가 되어버림 weird_board = [["_"] * 3] * 3 print(weird_board) # [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
- 잘못된 코드
# 동일한 리스트에 대한 세 개의 참조를 가진 리스트가 되어버림 weird_board = [["_"] * 3] * 3 print(weird_board) # [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] weird_board[1][2] = "O" print(weird_board) # [['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']] # 동일한 행이 board list에 추가되는 것과 같이 작동함 row = ['_'] * 3 weird_board = [] for i in range(3): board.append(row
2.6. 시퀀스의 복합 할당
+=
- 특수 메서드
__iadd__()
를 통해 구현됨. 구현되어 있지 않을 때는__add__()
가 호출됨. - 가변 시퀀스의 경우 값이 변경됨.
- 특수 메서드
*=
- 특수 메서드
__imul__()
를 통해 구현됨.
- 특수 메서드
2.6.1. += 복합 할당 퀴즈
t = (1, 2, \[30, 40\])
t\[2\] += \[50, 60\]
# TypeError 'tuple' object does not support item assignment 발생
# t는 변경됨
print(t)
(1, 2, \[30, 40, 50, 60\])
2.7. list.sort()와 sorted() 내장 함수
list.sort()
- 사본을 만들지 않고 리스트 내부에서 변경해서 정렬 (None 반환)
sorted()
- 새로운 리스트를 생성해 반환
2.8. 정렬된 시퀀스를 bisect로 관리하기
- bisect()는 이진 탐색 알고리즘을 이용해 시퀀스를 검색하고, insort()는 정렬된 시퀀스 안에 항목을 삽입한다.
2.8.1. bisect()로 검색하기
- bisect(haystack, needle)은 정렬된 시퀀스인 haystack 안에서 오름차순 정렬 상태를 유지한 채로 needle을 추가할 수 있는 위치를 찾아낸다.즉, 해당 위치 앞에는 needle보다 같거나 작은 항목이 온다.
def grade(score, breakpoints=[60, 70, 80, 90], grades="FDCBA"): i = bisect.bisect(breakpoints, score) return grades[i] [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]] # ['F', 'A', 'C', 'C', 'B', 'A', 'A']
2.8.2. bisect.insort()로 삽입하기
- insort(seq, item)은 seq을 오름차순으로 유지한 채로 item을 seq에 삽입한다.
2.9. 리스트가 답이 아닐 때
2.9.1. 배열
- 리스트 안에 숫자만 들어있다면 배열(array.array)가 더 효율적이다. (C의 배열만큼 가볍다.)
- 천 만 개의 실수를 가진 배열을 생성, 저장, 로딩하는 방법
from array import array
from random import random
floats = array('d', (random() for i in range(10**7)))
fp = open("floats.bin", "wb")
floats.tofile(fp)
fp.close()
floats2 = array('d')
fp = open("floats.bin", "rb")
floats2.fromfile(fp, 10**7)
fp.close()
print(floats == floats2)
# True
2.9.2. 메모리 뷰
- 공유 메모리 시퀀스 형으로서 bytes를 복사하지 않고 배열의 슬라이스를 다룰 수 있게 해줌
- memory.cast(): 바이트를 이동시키지 않고 여러 바이트로 된 데이터를 읽거나 쓰는 방식을 바꿀 수 있게 해줌. 또 다른 memoryview 객체를 반환하며 언제나 동일한 메모리를 공유
- 16비트 정수 배열에서 바이트 하나를 변경하는 방법
import array
numbers = array.array("h", [-2, -1, 9, 1, 2])
memv = memoryview(numbers)
print(len(memv)) # 5
print(memv[0]) # -2
memv_oct = memv.cast("B")
print(memv_oct.tolist()) # [254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
memv_oct[5] = 4
print(numbers) # array('h', [-2, -1, 1024, 1, 2])
2.9.3. Numpy와 Scipy
- numpy 2차원 연산
import numpy
a = numpy.arange(12)
print(a.shape)
# (12,)
a.shape = 3, 4
print(a) # shape 변경
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a[2, 1]) # 2행 1열
# 9
print(a[:, 1]) # 1열
# [1 5 9]
print(a.transpose()) # 전치행렬 생성
# [[ 0 4 8]
# [ 1 5 9]
# [ 2 6 10]
# [ 3 7 11]]
2.9.4. 덱 및 기타 큐
- collection.deque 클래스는 큐의 양쪽 어디에서든 빠르게 삽입 및 삭제할 수 있도록 설계된 양방향 큐
- 최대 길이를 설정할 경우 새로운 항목을 추가할 때 반대쪽 항목을 버린다.
from collections import deque
dq = deque(range(10), maxlen=10)
print(dq)
# deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
dq.rotate(3)
print(dq)
# deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
dq.rotate(-1)
print(dq)
# deque([8, 9, 0, 1, 2, 3, 4, 5, 6, 7], maxlen=10)
dq.appendleft(-1)
print(dq)
# deque([-1, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
2.10. 요약
- 파이썬 시퀀스는 가변형 or 불변형, 또는 균일 시퀀스 or 컨테이너 시퀀스로 분류할 수 있다.
- 지능형 리스트와 제너레이터 표현식은 시퀀스를 생성하고 초기화하는 강력한 표기법이다.
- 튜플은 이름 필드를 가진 레코드 및 불변 리스트로 사용할 수 있다.
- numpy에서는 다차원 슬라이싱과 생략기호 표기도 가능하다.
- seq * n으로 표현되는 반복 연결은 주의해서 사용하면 가변 항목을 담은 리스트의 리스트를 초기화할 수도 있다.
- sort(), sorted()로 리스트를 정렬할 수 있으며 key를 직접 설정할 수 있다.
- 정렬된 시퀀스의 순서를 유지하면서 항목을 추가하려면 bisect.insort()를 사용한다.
- array.array, numpy, scipy를 통해 대형 데이터셋의 수치 연산을 수행할 수 있다.
활용 사례
Numpy
https://colab.research.google.com/drive/1p9owsdDRcPmkJh-sWGFbDEleln2bJeg9#scrollTo=ej9jY01Vz_CM
728x90
'Python' 카테고리의 다른 글
[전문가를 위한 파이썬] 6장 일급 함수 디자인 패턴 내용 정리 (0) | 2024.06.02 |
---|---|
[전문가를 위한 파이썬] 5장 일급 함수 내용 정리 (0) | 2024.05.29 |
[전문가를 위한 파이썬] 4장 텍스트와 바이트 내용 정리 (0) | 2024.05.19 |
[전문가를 위한 파이썬] 3장 딕셔너리와 집합 내용 정리 (0) | 2024.05.15 |
[전문가를 위한 파이썬] 1장 내용 정리 (0) | 2024.05.01 |