시퀀스 자료형

시퀀스(sequence)는 데이터를 순서대로 나열한 형태로 파이썬에서는 문자열, 튜플, 리스트, 바이트 배열, 바이트가 있다.


시퀀스 타입

 

멤버십 연산in 키워드를 통해 내부에 있는 데이터를 확인할 수 있다.

0 in [0, 1]
>>> True
 

 

크기: 크기를 가지며, len() 함수를 사용할 수 있다.

len([0, 1, 2])
>>> True
 

 

슬라이싱[시작:끝:스텝]을 통한 슬라이싱을 사용할 수 있다.

a = [0, 1, 2]
a[0:2]
>>> [0, 1]
 

 

반복성(iterability): 반복문에 활용할 수 있다.

for i in [0, 1, 2]:
    pass
 

문자열

문자열(str)은 "" 또는 ''로 표현되며 불변형이다.

type("abc")
>>> <class 'str'>
 

f-string은 변수를 문자열 내부에서 사용할 수 있도록 한다. 문자열 앞에 f를 붙이고 { } 내부에 변수명을 적는다. f-string 방식은 3.6버전부터 지원한다. format()이나 % 방식보다 가독성이 좋고 속도가 빠르기 때문에 f-string 방식을 권장한다.

name = "Cole"
age = 23

f"{name}:{age}"
>>> 'Cole:23'
 

파이썬 3버전부터는 유니코드(Unicode)를 사용한다. 유니코드는 문자를 정의하는 표준 코드로 공백, 특수문자, 기호 및 각종 언어를 포함한다. 영어는 물론 한글도 표현 가능하다. 유니코드는 16비트를 사용하며, 유니코드를 활용한 인코딩 방식을 UTF-8이라고 한다.

# -*- coding: utf-8 -*-
 

파일의 첫 줄에 위 주석을 입력하면 utf-8로 인코딩하게 된다. 파이썬 3부터는 더 이상 필요 없어졌지만 파이썬 2.x에서는 한글을 표기하기 위해 위 주석이 필요했다.

a = "가나다"
가나다 = 0
 
u"\uAC00"
>>> '가'
 

한글도 사용 가능한 것을 볼 수 있으며, u"\u코드" 형태로 유니코드 문자열을 만들 수 있다.


바이트 / 바이트 배열

바이트는 불변형바이트 배열은 가변형으로 0(0x00) ~ 255(0xFF) 범위의 부호 없는 8비트 정수 시퀀스로 이루어져 있다.

numbers = [0, 1, 2]

b = bytes(numbers)
b
>>> b'\x00\x01\x02'

arr = bytearray(numbers)
arr
>>> bytearray(b'\x00\x01\x02')
 

bytes()로 바이트를, bytearray()로 바이트 배열을 생성할 수 있다. 바이트와 바이트 배열 앞에는 b가 붙는다.


튜플

튜플(tuple)은 쉼표로 구분된 값으로 불변형이다.

type((0,))
>>> <class 'tuple'>
 

하나의 객체를 가질 경우, 끝에 쉼표를 붙여 튜플임을 명시해 주어야 한다.

a = 0, 1, 2
type(a)
>>> <class 'tuple'>
 

괄호 없이 사용 가능하지만 쉼표가 없어서는 안 된다.


네임드 튜플

colluections.nametuple("튜플이름", 리스트) 형태로 작성하며, 튜플 각 항목에 이름을 지정할 수 있다. 리스트 대신 튜플도 사용 가능하며 스페이스로 구분된 문자열도 가능하다.

from collections import namedtuple

Image = namedtuple("image", ["name", "width", "height"])
img = Image("Flower", 300, 200)

img
>>> image(name='Flower', width=300, height=200)

img.name
>>> 'Flower'
 

기본적으로 튜플이기 때문에 인덱싱이 가능하고, 불변형이다.


리스트

리스트(list)는 가변형의 배열이다.

type([0, 1, 2])
>>> <class 'list'>
 

리스트는 [ ] 내부에 쉼표로 구분된 요소들을 갖는다.

list()함수는 해당 배열을 리스트 자료형으로 변환한다.

a = range(5)
a
>>> range(0, 5)

b = list(a)
b
>>> [0, 1, 2, 3, 4]
type(b)
>>> <class 'list'>
 

 

리스트 컴프리핸션(list comprehension)은 리스트 내부에 반복문과 조건문을 작성하는 방식이다. [항목 반복문 조건문] 순서로 작성하며, 가독성을 위해 코드가 간단할 때만 사용한다.

a = [x * 2 for x in range(5)]
a
>>> [0, 2, 4, 6, 8]

b = [x for x in a if x > 2]
b
>>> [4, 6, 8]

리스트 각 연산의 시간 복잡도는 아래와 같다. n은 리스트의 항목 개수, k는 연산 항목 수이다.

인덱싱 접근
O(1)
인덱싱 할당
append()
pop()
pop(i)
O(n)
insert(i, obj)
remove()
del
in 키워드
슬라이스 삭제
reverse()
슬라이스 조회, 연결
O(k)
슬라이스 할당
O(n+k)
sort()
O(n logn)

리스트 내부 요소를 검색의 시간 복잡도는 O(n)이기 때문에, 빠른 검색이 필요하다면 리스트보다 컬렉션 타입이 적절하다.


복사

 

얕은 복사

가변성 객체는 얕은 복사를 하게 된다. 얕은 복사란 하나의 객체가 변경되면 다른 객체도 같이 변경되는 것을 뜻한다. 시퀀스 자료형 중 가변성 객체인 리스트는 얕은 복사가 적용된다. 그 외 셋과 딕셔너리 자료형도 얕은 복사가 적용된다.

a = ["a", "b"]
b = a
b[0] = "v"

b
>>> ['v', 'b']

a
>>> ['v', 'b']
 

 

id를 이용해 메모리 주솟값을 확인해 보면 a와 b 모두 같은 메모리를 사용 중인 것을 볼 수 있다. 즉, a와 b는 사실상 하나의 객체이다.

id(a)
>>> 2443584294016

id(b)
>>> 2443584294016
 

따라서, 두 개의 다른 객체로 복사하고 싶다면 깊은 복사를 활용해야 한다.

 

깊은 복사

깊은 복사는 다른 메모리 주소를 사용하는 새로운 객체를 복사하는 것을 의미한다. 얕은 복사와 다르게 하나의 객체가 변해도 다른 객체에 영향을 주지 않는다.

 

리스트는 list(), 인덱싱, copy()를 활용해 깊은 복사를 할 수 있다.

a = [0, 1]
b = list(a)
 
a = [0, 1]
b = a[:]
 
a = [0, 1]
b = a.copy()
 

언패킹

파이썬은 *을 이용해 시퀀스를 개별 요소로 쪼갠 후, 할당하고 남은 값을 리스트로 받을 수 있다. 튜플, 리스트, 문자열 모두 적용된다.

a, *b = (1, 2, 3, 4)

a
>>> 1
b
>>> [2, 3, 4]
 

함수가 몇 개의 인수를 받을지 예상하기 어려울 때, *을 주로 사용한다.

def test(*args):
 

enumerate

enumerate(시퀀스)는 (인덱스, 요소)를 반환한다. 요소와 인덱스가 모두 필요한 상황에서 유용하다.

letters = ["A", "B", "C"]
for i, letter in enumerate(letters):
    print(i, letter)

>>> 0 A
    1 B
    2 C
 

+ 추가

"".join(["abc", "def", "ghi"])

"abc" + "def" + "ghi"

문자열을 합칠 때 + 보다 join 사용을 권장한다.

 

("abc", "def", "ghi")

["abc", "def", "ghi"]

변경하지 않는 시퀸스는 리스트보다 튜플 사용을 권장한다. (튜플이 리스트보다 메모리를 적게 사용한다.)