코린이의 소소한 공부노트

데이터 타입, 연산자 (3) 리스트, 튜플 본문

Python

데이터 타입, 연산자 (3) 리스트, 튜플

무지맘 2021. 5. 11. 11:12

앞서 본 기본 데이터 타입들은 한 변수에 하나의 값을 담을 수 있었다.

그럼 여러 값을 쓰려면 여러 개의 변수를 선언해야 하네..

변수 이름 짓는 것도 한계가 있다!

그렇다면 여러 값을 담을 수 있는 데이터 타입은 없냐? 아니다 있다!

이런 데이터 타입을 컬렉션(collection) 타입이라고 한다.

 

오늘 볼 것은

1. 리스트

2. 튜플이다.

 


1. 리스트(list)


리스트는 우리가 익히 생각하는 그 리스트와 같은 것이다.

대괄호 [] 안에 여러 값들을 쉼표 ,로 구분 지어두면 그게 바로 리스트가 된다.

emp_list = [] # 빈 리스트
emp_list
>> []

list_ex1 = [1, 2, 'ㄱ', 'ㄴ', [5]]
list_ex1       # 리스트 안에 리스트를 넣을 수도 있음
>> [1, 2, 'ㄱ', 'ㄴ', [5]]

위에서 보다시피 빈 리스트도 생성 가능하고, 타입이 다른 값을 섞어서 리스트를 만들어도 되고, 리스트 안에 또 다른 리스트를 넣어도 상관없다.

str_to_list = '무지는 귀여워!'
list_ex2 = list(str_to_list)
print(list_ex2)
>> ['무', '지', '는', ' ', '귀', '여', '워', '!']

list함수를 써서 리스트가 아닌 것도 리스트로 변환시켜준다.

num_to_list = 1
list(num_to_list)
>> TypeError: 'int' object is not iterable

숫자는 안 되는 모양이다.

tup_to_list = (1, 2, 3, 4) # 이것은 튜플. 아래에서 설명 예정
list(tup_to_list)
>> [1, 2, 3, 4]

곧 만날 튜플도 리스트로 변환이 가능한 것을 알 수 있다.

he_said = '나는.. 수줍음을 많이 타.. 어떻게 말을 하지..?'
she_said = '어떻게 하긴! 이렇게! 자신있게! 말하라고!'
print(he_said.split('..'))
>> ['나는', ' 수줍음을 많이 타', ' 어떻게 말을 하지', '?']

print(she_said.split('!'))
>> ['어떻게 하긴', ' 이렇게', ' 자신있게', ' 말하라고', '']

'데이터 타입, 연산자 (2) 문자열'에서 봤던 예시다. split함수를 사용하면 그 결과를 리스트로 반환해준다. 왜 결괏값이 대괄호 []에 둘러싸여 출력됐는지 이제 이해할 수 있다!

 

리스트도 실컷 만들었겠다.. 이제 써먹어봅시다!

리스트는 문자열과 마찬가지로 인덱스가 존재한다. 양수 인덱스도, 음수 인덱스도 인덱스 허용 범위 내에서 얼마든지 사용 가능하다! 

immut_str = '문자열은 수정 불가능해' # String is immutable
mut_list = list(immut_str)
print(mut_list)
>> ['문', '자', '열', '은', ' ', '수', '정', ' ', '불', '가', '능', '해']

mut_list[0] = '리'  # 귀찮게
mut_list[1] = '스'  # 한 글자씩
mut_list[2] = '트'  # 바꾸는
mut_list[3] = '는'  # 수고를
mut_list[-4] = '!!' # 하는중..
print(mut_list)                   # List is mutable
>> ['리', '스', '트', '는', ' ', '수', '정', ' ', '!!', '가', '능', '해']

immut_str[-1] = '?' # 문자열은 수정 될까?
>> TypeError: 'str' object does not support item assignment

리스트는 얼마든지 인덱스를 이용해서 해당 부분을 수정할 수 있다. 하지만 문자열은 안된다.

리스트는 문자열처럼 슬라이싱도 가능하다!

doit_slice = [1, 2, 3, 4, 5, 6, 7, 8 ,9]
doit_slice[3:6]     # 3번째부터 5번째까지 3개 슬라이스
>> [4, 5, 6]

doit_slice[1:8:3]   # 1번째부터 8번째까지 3개씩 건너뛰어 슬라이스
>> [2, 5, 8]

문자열 슬라이싱에서는 설명 안 했던 부분이라 추가 설명을 해보자면, 대괄호 [] 안에 start:end:step 순서대로 명시하면 그대로 잘라준다. 물론 생략 가능하다. start는 생략하면 0번, end는 생략하면 끝번, step는 생략하면 1이다.


이제부터 설명할 것은 리스트의 함수들이다.

 

첫 번째, appendextend이다. append는 리스트의 끝에 값을 추가하는 것이고, extend는 리스트를 연장하는 것이다. 두 개가 같아 보이겠지만, 예시를 보면 차이를 확실히 알 수 있다.

a = [1, 2, 3]
b = [4, 5, 6]
# 내가 만들고 싶은 것은 [1, 2, 3, 4, 5, 6]
a.append(b) # a리스트의 끝에 b리스트를 추가
a           # 제대로 됐나 확인해볼까?
>> [1, 2, 3, [4, 5, 6]]

append를 쓰면 b리스트 전체가 a리스트의 마지막 원소가 됐음을 확인할 수 있다.

a = [1, 2, 3]
b = [4, 5, 6]
# 내가 만들고 싶은 것은 [1, 2, 3, 4, 5, 6]
a.extend(b) # a리스트의 끝에 b리스트를 연장
a           # 이번에는 과연?
>> [1, 2, 3, 4, 5, 6]

이번에는 의도한 대로 잘 나온 것을 확인할 수 있다. append는 추가하는 것이 마지막 원소가 되는 것이고(추가하는 것이 무엇이든 길이는 1 증가), extend는 추가된 변수의 길이만큼 연장된다고 생각하면 되겠다.

a = [1, 2, 3]
b = [4, 5, 6]
# 내가 만들고 싶은 것은 [1, 2, 3, 4, 5, 6]
a += b      # a+b를 a에 대입
a
>> [1, 2, 3, 4, 5, 6]

extend는 += (더해서 대입)으로도 표현할 수 있다!

 

두 번째, remove함수이다. remove는 원하는 값을 제거해준다.

a = [1, 2, 3, 4, 5, 3, 3, 3]
a.remove(3)   # 3이 여러개일 때 가장 앞의 값만 제거
print(a)
>> [1, 2, 4, 5, 3, 3, 3]

a.remove(6)   # 리스트에 없는 값을 지우려고하면 에러
>> ValueError: list.remove(x): x not in list

 

세 번째, pop이다. pop은 원하는 인덱스의 값을 제거해준다. 그리고 remove는 값을 제거하고 아무것도 반환하지 않지만, pop은 제거한 값을 반환해준다.

a = [1, 2, 3, 4, 5, 4, 3, 2, 1]
b = a.pop()     # 인덱스 안쓰면 가장 마지막 값을 제거 후 반환
print(b)
>> 1

print(a)        # 맨 뒤 1이 없어진 리스트가 됨
>> [1, 2, 3, 4, 5, 4, 3, 2]

c = a.remove(b) # a 리스트에서 1을 제거
print(c)
>> None         # remove는 제거만 하지 반환하지 않음. 그래서 c에 아무것도 할당 안됨

print(a)
>> [2, 3, 4, 5, 4, 3, 2]

 

네 번째, index함수이다. 원하는 값을 넣어주면 해당 값의 인덱스를 반환해준다.

a = [9, 7, 5, 3, 1]
b = a.index(1)  # 1은 4번째에 위치
print(b)
>> 4

c = a.index(b)  # 4라는 값은 a 리스트에 없는데..
>> ValueError: 4 is not in list

 

다섯 번째, in 키워드이다. 리스트 내에 해당 값이 있는지 확인하는 키워드로, True나 False를 반환한다.

a = [2, 3, 4, 5, 6]
b = 1
c = 4
print(b in a) # a에 1이 있나?
>> False

print(c in a) # a에 4가 있나?
>> True

print(6 in a) # a에 6이 있나?
>> True

 

여섯 번째, 정렬 함수이다. 정렬 함수는 sortsorted 2가지이고, 오름차순 또는 내림차순으로 정렬 가능하다.

a = [3, 5, 2, 6, 1]
a.sort()             # 리스트를 오름차순으로 정렬. 반환 값은 없음
print(a)
>> [1, 2, 3, 5, 6]

a.sort(reverse=True) # 리스트를 내림차순으로 정렬
print(a)
>> [6, 5, 3, 2, 1]

sort는 리스트를 자체 정렬을 하기 때문에 리스트 내부에 변화가 생긴다.

a = [3, 5, 2, 6, 1]
b = sorted(a, reverse=True) # 리스트를 복사해서 내림차순으로 정렬후 반환
print(b)
>> [6, 5, 3, 2, 1]

print(a)                    # 복사해서 반환한 것이기 때문에 a에는 영향이 없음
>> [3, 5, 2, 6, 1]

sorted는 리스트를 복사한 후 정렬하기 때문에 원래 리스트에는 변화가 없다.

a = [3, 5, 2, 6, 1]
type(a.sort())
>> NoneType

type(sorted(a))
>> list

sort는 내부 자체 정렬만 하는 함수라 결괏값이 없어서 Nonetype이고, sorted는 복사한 리스트를 정렬 후 반환해주므로 타입이 list로 나온다.


2. 튜플(tuple)


튜플도 리스트처럼 여러 값을 담을 수 있는 데이터 타입이다. 소괄호 ()로 값을 감싸면 된다.

a = (1, 2, 3)
type(a)
>> tuple

b = 10, 20   # tuple unpacking, 괄호 생략하고 튜플 선언 가능
type(b)
>> tuple

print(b)     # 튜플이기 때문에 선언 당시에 생략한 괄호가 함께 출력됨
>> (10, 20)

c, d = 3, 4  # 위의 b를 선언하는 것과 차이가 있음
type(c)      # 튜플 내에서 c=3이기 때문에 정수라고 출력됨
>> int

print(c, d)  # 튜플로 변수 선언 및 할당을 했지만 각각은 정수이므로 괄호 없이 출력됨
>> 3 4      

리스트와 차이점이 있다면 수정이 불가능(immutable)하다는 것이다.

a = [1, 2, 3]
b = (1, 2, 3)
# 리스트는 수정 가능
a[0] = 10
print(a)
>> [10, 2, 3]

# 튜플은 수정 불가능
b[0] = 100
>> TypeError: 'tuple' object does not support item assignment

튜플에도 리스트에 썼던 멤버 함수들을 쓸 수 있을까?

append, extend, remove, pop sort은 AttributeError: 'tuple' object has no attribute '함수 이름' 형식으로 에러가 뜬다. 튜플은 수정이 불가능하므로 어찌 보면 너무도 당연한 결과이다.

하지만 index, in, sorted은 튜플을 수정하는 기능이 아니므로 사용 가능하다! index와 in은 납득이 되는데, sorted는 의외인가? 잘 생각해보면 sorted는 복사한 리스트를 정렬한 후 반환하는 거라 원래 것이 수정되는 게 아니므로 충분히 튜플에도 쓸 수 있다.

a = (3, 5, 2, 6, 1)
b = sorted(a)
print(b)
>> [1, 2, 3, 5, 6]

a가 튜플이어도 이것을 복사해서 리스트로 변환한 다음 정렬을 해서 나온 결과 리스트를 b에 할당하기 때문에 b의 출력 값은 리스트가 되는 것이다.


<요약>

 

1. 리스트 [] - 수정 가능한 컬렉션 타입

- append: 맨 뒤에 원소로 추가

- extend: 리스트 연장, +=

- remove: 값 제거

- pop: 해당 인덱스 값 제거

- index: 해당 값의 인덱스 반환

- in: 내부에 값이 있나 확인, 결과는 bool

- sort / sorted: 내부 정렬 / 복사 후 정렬, 반환

2. 튜플 () - 수정 불가능한 컬렉션 타입

- index, in, sorted 사용 가능