[BB-PY] 2. 시퀀스

본 글은 파이썬 공부를 위해 전문가를 위한 파이썬를 정리한 글입니다.

들어가며

  • 초보자를 위한 프로그래밍 환경을 개발하기 위해 10년간 진행한 연구 프로젝트인 ABC 언어의 특징을 물려받음
  • 시퀀스를 이해하면 코드를 새로 구현할 필요가 없이 시퀀스의 공통 인터페이스를 따라 기존 혹은 향후 구현될 시퀀스 자료형을 적절히 지원하고 활용할 수 있게 API를 정의할 수 있음

내장 시퀀스 개요

컨테이너 시퀀스(container sequence)

  • 서로 다른 자료형의 항목들을 담을 수 있는 list, tuple, collections.deque
  • 모든 자료형 객체에 대한 참조를 담고 있음

균일 시퀀스(flat sequence)

  • 단 하나의 자료형만 담을 수 있는 str, bytes, bytearray, memeryview, array.array
  • 객체에 대한 참조 대신 자신의 메모리 공간에 각 항목의 값을 직접 담음
  • 균일 시퀀스가 메모리를 더 적게 사용하지만, 기본적인 자료형만 저장

가변 시퀀스

  • list, bytearray, array.array, collections.deque, memoryview

불변 시퀀스

  • tuple, str, bytes

지능형 리스트(listcomp)와 제너레이터 표현식(genexp)

지능형 리스트와 가독성

# for 문 사용
chars = 'abcd'
codes = []
for char in chars:
  codes.append(ord(char))

# 지능형 리스트
chars = 'abcd'
codes = [ord(char) for char in chars]
  • 생성된 리스트를 사용하지 않을 거라면 지능형 리스트 구문을 사용하지 말아야함
  • 지능형 리스트 구문이 두 줄 이상이 넘어가는 경우에는 코드를 분할하거나 for 문을 이용해 작성하는 것이 나음
  • 지능형 리스트 구문은 코드는 짧게 만들어야함
  • Python 2.x의 경우 지능형 리스트 안의 for 문에서 할당한 변수는 주변 범위에서 다른 변수와 함께 설정되므로 외부 변수에 영향을 미쳤으나, Python 3에서는 해결됨
  • 지능형 자료형과 제너레이터 표현식은 함수와 같이 고유한 지역 범위를 가지며(지역 변수), 외부의 변슈를 참조할 수 있음

지능형 리스트와 map()/filter() 비교

# filter()
chars = 'abcd'
over_b = list(filter(lambda x: x > 98, map(ord, chars)))

# 지능형리스트
chars = 'abcd'
over_b = [ord(char) for char in chars if ord(s) > 98]
  • map()filter() 함수를 이용해서 수행할 수 있는 작업은 기능적으로 문제가 있는 파이썬 람다를 억지로 끼워 넣지 않고도 지능형 리스트를 이용해서 모두 구현할 수 있음
listcomp        : 0.020 0.021 0.016
listcomp + func : 0.025 0.023 0.025
filter + lambda : 0.023 0.021 0.022
filter + func   : 0.019 0.023 0.024
  • map()filter()를 조합한 방법이 지능형 리스트보다 빠르지 않음

데카르트 곱

colors = ['black', 'white']
sizes = ['S', 'M', 'L']
tshirts = [(color, size) for color in colors for size in sizes]
  • 지능형 리스트는 두 개 이상의 반복 간으한 자료형의 데카르트 곱을 나타내는 일련의 리스트를 만들 수 있음

제너레이터 표현식

chars = 'abcd'
tuple(ord(char) for char in chars)

import array
array.array('I', (ord(char) for char in chars))
  • 제너레이터 표현식을 사용하며 다른 생성자에 전달할 리스트로 통째로 많들지 않고 반복자 프로토콜을 이용해서 항목을 하나씩 생성하여 메모리를 적게 사용
  • 지능형 리스트와 동일한 구문을 사용하지만, 대괄호 대신 괄호를 사용
  • 함수에 보내는 인자가 하나라면 표현식의 괄호를 생략해도 되며, 두 개 이상이라면 괄호를 넣어야 함

튜플은 단순한 불변 리스트가 아니다

튜플은 불변 리스트로 사용할 수도 있지만 필드명이 없는 레코드로 사용할 수도 있다.

레코드로서의 튜플

lax_coordinates = (33.9425, -118.408056)
traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]
for passport in sorted(traveler_ids):
  print('%s/%s' % passport)
  • 튜플은 레코드를 담음
  • 튜플의 각 항목은 레코드의 필드 하나를 의미하며 항ㅊ목의 위치가 의미를 결정
  • 튜플을 레코드로 사용하는 경우, 튜플 안에서 항목의 위치가 항목의 의미를 나타내므로 튜플을 정렬하면 정보가 파괴

튜플 언패킹(tuple unpacking)

lax_coordinates = (33.9425, -118.408056)
latitude, longitude = lax_coordinate

b, a = a, b

t = (20, 8)
divmod(*t)
  • 반복 가능한 객체라면 적용 가능
  • 반복형 언패킹(iterable unpacking)이라고 하기도 함
  • 병렬 할당(parallel assignment)할 때 유용
  • *을 사용하여 언패킹 할 수 있음
  • _와 같은 더미 변수를 플레이스홀더로 사용해서 관심 없는 부분은 언패킹할 때 무시

초과 항목을 잡기 위해 * 사용하기

  • 함수 매개변수에 *를 연결해서 초과된 인수를 가져오는 방법과 같이 병렬 할당에 이를 적용할 수 있음
  • 병렬 할당의 경우 *는 단 하나의 변수에만 적용할 수 있음

내포된 튜플 언패킹

  • 언패킹할 표현식을 받는 튜플은 (a, b, (c, d))처럼 다른 튜플을 내포할 수 있으며, 파이썬은 표현식이 내포된 구조체에 일치하면 제대로 처리

명명된 튜플

from collections import namedtuple
City = namedtuple('City', 'name country population coordinate')
tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
tokyo.population
  • collections.namedtuple() 함수는 필드명과 클래스명을 추가한 튜플의 서브클래스를 생성
  • _fileds, _make(iterable), _asdict() 등 추가 속성 및 메서드를 가지고 있음

불변 리스트로서의 튜플

  • 튜플은 항목을 추가하거나 삭제하는 기능 및 __reserved()__ 메서드를 제외하고는 리스트가 제공하는 메서드를 모두 지원

슬라이싱

  • 파이썬에서 제공하는 list, tuple, str, 그리고 모든 시퀀스형은 슬라이싱 연산을 지원

슬라이스와 범위 지정시에 마지막 항목이 포함되지 않는 이유

l = [10, 20, 30, 40, 50, 60]
l[:2]
l[2:]
  • 중단점만 이용해서 슬라이스나 범위를 지정할 때 길이를 계산하기 쉬움
  • 시작점과 중단점을 모두 지정할 때도 길이가 계산하기 쉬움
  • 인덱스를 기준으로 겹침 없이 시퀀스를 분할하기 쉬움

슬라이스 객체

s = 'bicycle'
s[::3]
s[::-1]

text = """
0.....6..............
1234  TEST1
5678  TEST2
"""

NUM = slice(0,6)
DESCRIPTION = slice(6,20)
line_items = text.split('\n')[2:]
for item in line_items:
    print(item[NUM], item[DESCRIPTION])
  • a:b:c 표기법은 인덱스 연산을 수행하는 [] 안에서만 사용할 수 있으며, slice(a, b, c) 객체를 생성
  • 파이썬은 seq[start:stop:step] 표현식을 평가하기 위해 seq.__getitem__(slice(start, stop, step))을 호출
  • 슬라이스 객체는 슬라이스에 이름을 붙일 수 있게 해주기에 유용

다차원 슬라이싱과 생략 기호

  • [] 연산자는 콤마로 구분해서 여러 개의 인덱스나 슬라이스를 가질 수 있음
    • NumPy 등 외부 패키지에서는 a[i, j] 구문으로 2차원 numpy.ndarray 배열의 항목이나 a[m:n, k:l] 구믄으로 2차원 슬라이스를 가져올 때 사용
    • 내장된 시퀀스형은 1차원이므로 단 하나의 인덱스나 슬라이스만을 지원하고 튜플은 지원하지 않음
  • 세 개의 마침표(...)로 표현된 생략 기호는 파이썬에 의해 하나의 토큰으로 인식
  • ...Ellipsis 객체의 별명으로 하나의 ellipsis 클래스의 객체
  • 생략 기호 객체는 f(a, ..., z)처럼 함수의 인수나 , a[i:...]처럼 슬라이스의 한 부분으로 전달
  • 표준 라이브러리에서는 Ellipsis나 다차원 인덱스 및 슬라이스를 사용하지 않음

슬라이스에 할당하기

l = list(range(10))
l[2:5] = [20, 30]
del l[5:7]
l[3::2] = [11, 22]
# l[2:5] = 100
l[2:5] = [100]
  • 할당문의 왼쪽에 슬라이스 표기법을 사용하거나 del 문의 대상 객체로 지정함으로써 가변 시퀀스를 연결하거나, 잘라 내거나, 값을 변경할 수 있음
  • 할당문의 대상이 슬라이스인 경우, 항목 하나만 할당하는 경우에도 할당문 오른쪽에는 반복 가능한 객체가 와야 함

시퀀스에 덧셈과 곱셈 연산자 사용하기

  • 시퀀스는 덧셈(+)과 곱셈(*)을 지원
  • 덧셈과 곱셈 연산자는 언제나 객체를 새로 만들고 피연산자를 변경하지 않음

리스트의 리스트 만들기

board = [['-'] * 3 for i in range(3)]
wired_board = [['-'] * 3] * 3 # 주의
  • 내포된 리스트를 가진 리스트를 초기화해야 하는 경우 지능형 리스트를 사용하는 것이 좋음
  • a*n과 같은 표현식을 사용할 때는 참조 값으로 n번 초기화 될 수 있으니 주의

시퀀스의 복합 할당

  • +=, *= 등의 복합 할당 연산자는 첫 번째 피연산자에 따라 특수 메서드인 __iadd_(), __imul__() 등 메서드의 구현 여부에 따라 기존 객체의 변수가 가리키는 객체의 정체성이 바뀔 수도 있고 바뀌지 않을 수도 있음
  • 가변 시퀀스에 대해서는 __iadd__() 메서들 ㄹ구현해서 +=연산자가 기존 객체의 내용을 변경하게 만드는 것이 좋음
  • 불변 시퀀스의 경우에는 이 연산을 수행할 수 없음
  • 불변 시퀀스의 경우에는 새로운 항목을 추가하는 대신 항목이 추가된 시퀀스 전체를 새로 만들어 타깃 변수에 저장하므로 반복적으로 연결 연산을 수행하는 것은 비효율적

+= 복합 할당 퀴즈

t = (1, 2, [30, 40])
t[2] += [50, 60]
#Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
#TypeError: 'tuple' object does not support item assignment
  • 가변 항목을 튜플에 넣는 것은 좋은 생각이 아님
  • 복합 할당은 원자적인 연산이 아님(일부 연산이 수행된 후 예외 발생)
  • 파이썬 바이트코드를 살펴보면 내부에서 어떤 일이 발생하고 있는지 확인하는데 유용

list.sort()sorted() 내장 함수

  • list.sort() 메서드는 사본을 만들지 않고 리스트 내부를 변경해서 정렬
    • sort() 메서드는 타깃 객체를 변경하고 새로운 리스트를 생성하지 않았음을 알려주기 위해 None을 반환
    • 파이썬 API의 관레 중 객체를 직접 변경하는 함수나 메서드는 객체가 변경되었고 새로운 객체가 생성되지 않았음을 호출자에게 알려주기 위해 None을 반환
  • sorted() 내장 함수는 새로운 리스트를 생성해서 반환
  • 키워드
    • reverse: 값이 참이면 비교 연산을 반대로 해서 내림차순으로 반환
    • key: 정렬에 사용할 키를 생성하기 위해 각 항목에 적용할 함수를 인수로 받음

정렬된 시퀀스를 bisect로 관리하기

  • bisect 모듈은 bisect()insort() 함수를 제공
  • bisect(): 이진 검색 알고리즘을 이용해서 시퀀스를 검색
  • insort(): 정렬된 시퀀스 안에 항목을 삽입

bisect()로 검색하기

import bisect
import sys

HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]
NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31]

ROW_FMT = '{0:2d} @ {1:2d}    {2}{0:<2d}'

def demo(bisect_fn):
    for needle in reversed(NEEDLES):
        position = bisect_fn(HAYSTACK, needle)  # <1>
        offset = position * '  |'  # <2>
        print(ROW_FMT.format(needle, position, offset))  # <3>

if __name__ == '__main__':

    if sys.argv[-1] == 'left':    # <4>
        bisect_fn = bisect.bisect_left
    else:
        bisect_fn = bisect.bisect

    print('DEMO:', bisect_fn.__name__)  # <5>
    print('haystack ->', ' '.join('%2d' % n for n in HAYSTACK))
    demo(bisect_fn)
# right
DEMO: bisect
haystack ->  1  4  5  6  8 12 15 20 21 23 23 26 29 30
31 @ 14      |  |  |  |  |  |  |  |  |  |  |  |  |  |31
30 @ 14      |  |  |  |  |  |  |  |  |  |  |  |  |  |30
29 @ 13      |  |  |  |  |  |  |  |  |  |  |  |  |29
23 @ 11      |  |  |  |  |  |  |  |  |  |  |23
22 @  9      |  |  |  |  |  |  |  |  |22
10 @  5      |  |  |  |  |10
 8 @  5      |  |  |  |  |8
 5 @  3      |  |  |5
 2 @  1      |2
 1 @  1      |1
 0 @  0    0

# left
DEMO: bisect_left
haystack ->  1  4  5  6  8 12 15 20 21 23 23 26 29 30
31 @ 14      |  |  |  |  |  |  |  |  |  |  |  |  |  |31
30 @ 13      |  |  |  |  |  |  |  |  |  |  |  |  |30
29 @ 12      |  |  |  |  |  |  |  |  |  |  |  |29
23 @  9      |  |  |  |  |  |  |  |  |23
22 @  9      |  |  |  |  |  |  |  |  |22
10 @  5      |  |  |  |  |10
 8 @  4      |  |  |  |8
 5 @  2      |  |5
 2 @  1      |2
 1 @  0    1
 0 @  0    0
  • bisect(haystack, needle)은 정렬된 시퀀스인 haystack안에서 오름차순 정렬 상태를 유지한 채로 needle을 추가할 수 있는 위치를 찾아냄
  • 선택 인수인 lohi를 사용하면 삽힙할 때 검색할 시퀀스 영역을 좁힐 수 있음
  • bisect_right()bisct_left()의 차이는 needle과 값이 같을 때만 생김

bisect.insort()로 삽입하기

import bisect
import random

SIZE = 7

random.seed(1729)

my_list = []
for i in range(SIZE):
    new_item = random.randrange(SIZE*2)
    bisect.insort(my_list, new_item)
    print('%2d ->' % new_item, my_list)
10 -> [10]
 0 -> [0, 10]
 6 -> [0, 6, 10]
 8 -> [0, 6, 8, 10]
 7 -> [0, 6, 7, 8, 10]
 2 -> [0, 2, 6, 7, 8, 10]
10 -> [0, 2, 6, 7, 8, 10, 10]
  • 정렬은 값비싼 연산이므로 시퀀스를 일단 정렬한 후에는 정렬 상태를 유지하는 것이 좋음
  • insort(seq, item)seq를 오름차순으로 유지한 채로 itemseq에 삽입
  • bisect 함수와 마찬가지로 insort 함수도 선택적으로 lohi 인수를 받아 시퀀스 안에서 검색할 범위를 제한함

라스트가 답이 아닐 때

  • 리스트형은 융통성이 있고 사용하기 편하지만, 세부 요구사항에 따라 더 나은 자료형도 있음
  • 배열은 모든 기능을 갖춘 float객체 대신 C 언어의 배열과 마찬가지로 기계가 사용하는 형태로 표현된 바이트 값만 저장하기 때문에 효율적

배열

  • 리스트 안에 숫자만 들어 있다면 배열(array.array)이 리스트보다 효율적
  • 배열은 가변 시퀀스가 제공하는 모든 연산을 지원하며, 파일을 빠르게 저장하고 읽어올 수 있는 frombytes()tofile() 메서드도 추가로 제공
  • 배열을 생성할 때는 배열에 저장되는 각 항목의 C 기반 형을 결정하는 문자인 타입코드를 지정

메모리 뷰

  • 메모리 뷰(memoryview) 내장 클래스는 공유 메모리 시퀀스형으로 bytes를 복사하지 않고 배열의 슬라이스를 다룰 수 있게 해줌
  • 데이터셋이 커지는 경우 유용
  • array 모듈과 비슷한 표기법을 사용하는 memoryview.cast() 메서드는 바이트를 이동시키지 않고 C 언어의 형변환 연산자처럼 여러 바이트로 된 데이터를 읽거나 쓰는 방식을 바꿀 수 있게 해줌
  • memoryview.cast()는 또 다른 momoryview 객체를 반환하며 언제나 동일한 메모리를 공유

NumPy와 SciPy

  • 고급 배열 및 행렬 연산을 제공해서 과학 계산 애플리케이션에서 파이썬을 널리 사용하게 함

덱 및 기타 큐

  • 덱(collectios.queue) 클래스는 큐의 양쪽 어디에서든 빠르게 삽입 및 삭제할 수 있도록 설계된 스레드 안전한 양방향 큐
  • 덱은 최대 길이를 설정해서 제한된 항목만 유지할 수 있으며, 가득찬 후에는 새로운 항목을 추가할 때 반대쪽 항목을 버림
  • 덱이 양쪽 긑에 추가나 제거하는 연산에 최적화되어 있기 때문에 덱의 중간 항목을 삭제하는 연산은 그리 빠르지 않음
  • 기타 queue, mulitprocessing, asyncio, heapq 등이 구현되어 있음

SmileCat

SmileCat
How do you define yourself?

[Openlayers] Render Event 정리

## 이벤트### [Map](http://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html)| event | module | note || :--: | -- | -- || [postcompose](...… Continue reading