상세 컨텐츠

본문 제목

[python]List와 Tuple의 차이점

Python

by 메타샤워 2023. 11. 14. 15:42

본문

리스트와 튜플의 가장큰 차이점

1. 리스트는 동적인 배열이다. 수정이 가능하며, 저장 용량을 늘리거나 줄일 수도있다.

2. 튜플은 정적인 배열이다. 일단 생성이 되면, 배열의 크기뿐 아니라 그 안의 데이터도 변경할수 없다.

3. 튜플은 파이썬 런타임에서 캐싱하므로 사용할때 마다 커널에 메모리를 요청 하지 않아도 된다.

 

튜플은 변치 않는 특정 대상의 여러 속성을 표현하고

리스트는 이질적인 객체들의 모음이다.!!

 

예를들어보자 전화번호의 구성은 튜플이다. 전화번호는 잘 변하지 않고, 전화번호끼리 더하거나 빼는 연산이 필요없다.

하지만 내가 가지고 있는 쿠폰의 종류는 리스트이다. 쿠폰이 사용되어 리스트에서 빠져야 할수도 있고, 새로운 쿠폰이들어와 리스트에 추가해야 할수도 있다. 

 

리스트와 튜플은 모두 다른 타입을 섞어서 쓸수 있다는점을 기억하자 

 

lis = [1, '1', {'a':1}]
tup = (1, '1', {'a':1})

 

 

이것 때문에 최적화 과정에서 약간의 손해를 볼수도 있다. 이 손해는 데이털르 모두 같은 타입으로 통일하는 방법으로 제거 해야 하므로, 리스트나 튜플을 쓸때는 같은 타입으로 만들어 쓰는것이 효율적이다.

 

 

리스트 : 동적 배열

리스트는 생성한 후에도 필요할때마다 내용을 변경할 수 있다.

num = [ 1, 2, 3, 4, 5]
num[2] = 2 * num[1] # 값 수정
num
>> [ 1, 2, 4, 4, 5]

 

 그리고 리스트에 새로운 데이터를 추가하면 그 크기가 커진다.

len(num)
>> 5
num.append(6) # 값 추가
num
>> [1, 2, 4, 4, 5, 6]
len(num)
>> 6

 

이것은 동적 배열이 배열의 크기를 변경하는 resize 연산을 지원하기 때문에 가능하다. 크기가 N인 꽉찬 리스트에 새로운 항목을 추가하면 파이썬은 원래 담고 있던 N개와 새로 추가한 항목까지 모두 담기에 충분한 크기의 새로운 리스트를 생성한다.

 

지만 N+1 크기를 할당해서 생성하는게 아니라 나중을 위해 여유분으로 N 보다 더 큰 M 만큼 메모리를 할당한다 (M > N) (ㅠㅠ). 할당을 마치면 이전 리스트의 데이터는 모두 새로운 리스트로 값 복사를 하고 이전 리스트는 삭제 후 메모리에 반환된다.

 

M 만큼 크기에 여유를 두는 이유는 리스트에 값을 한 번 추가하면 그 뒤로 여러번 더 추가할 확률이 높기 때문에 메모리의 할당과 복사 요청 횟수를 줄이기 위함니다.

 

* 메모리 복사는 비용이 많이 들기 때문에 리스트가 계속계속 증가할 경우에는 꽤 중요한 사항이다.

 

 

튜플 : 정적 배열

리스트와 달리 튜플은 한번 생성되면 내용을 바꾸거나 크기를 변경할 수 없다.

t = (1,2,3,4,5)
t[0]=5
>>> Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

 

크기를 변경할 순 없어도 두 튜플을 하나의 새로운 튜플로 합칠 수는 있다. 리스트의 resize 연산과 비슷하지만 리스트와 달리 여유공간을 더많이 할당하지는 않는다. ( M ❌ )

 

tuple1 = (1,2,3,4)
tuple2 = (5,6,7,8)
tuple1 + tuple2
>>> (1,2,3,4,5,6,7,8)

 

리스트의 append() 에서는 O(1)만에 이루어지지만 ( 리스트의 마지막 주소와 크기를 알고있기 때문에 O(1)이 메모리 연산을 통해 가능함)

튜플에서는 O(n) 이 시간이 걸린다. 여유공간이 부족할 때만 할당과 복사가 일어나는 리스트와 달리 튜플에서는 새로운 항목을 추가 할때마다 할당과 복사가 일어나기 때문이다. 

 

튜플은 append()와 같이 기존 튜플이 차지하고 있는 메모리 안에 새로운 항목을 추가 연산을 지원하지는 않는다. 두 튜플을 합치면 항상 새로운 튜플을 위한 메모리가 새로 할당된다. ( 물론 기존 튜플도 여전히 남아있다. ) 

 

튜플은 크기 변경을 위한 여유공간을 할당하지 않기 때문에 자원을 더 적게 사용하는 장점이있다. 리스트는 append() 수행할시에 실제 값의 크기보다 더 여유있는 메모리를 할당하기 때문에 데이터가 정적일때는 튜플이 리스트보다 더 가볍고 좋다.

 

append()를 사용하지 않는다 하더라도 리스트는 같은 데이터를 저장하는 튜플보다 메모리를 더 많이 잡아먹는다. 리스트는 효율적인 크기 변경을 위한 상태 정보를 관리하기 때문에이 추가 정보의 크기는 작지만 수백만 개의 리스트를 사용한다면 무시할수 없다. 

(수백만개의 리스트가 아니라면 무시할 수 있는 수준이긴 하다...)

 

 

튜플이 정적이기 때문에 얻을수 있는 또다른 장점은 파이썬이 내부적으로 수행하는 리소스 캐싱이 있는데, 파이썬은 Garbage Collector를 통해 더 이상 사용되지 않는 변수에 할당된 메모리를 반환한다. 하지만 크기가 20 이하인 튜플은 즉시 회수하지 않고 나중을 위해 저장해둔다. 이 말은 같은 크기의 튜플이 나중에 다시 필요해지면 운영 체제로 부터 메모리를 새로 할당받지 않고, 기존에 할당해둔 메모리를 재사용한다는 뜻이다.

 

이것이 사소해 보일지 몰라도 그 장점이 바로 튜플의 아주 기막힌 특징중 하나이다. 운영제체를 통하지 않아도 되기 때문에 쉽고 빠르게 튜플을 생성할 수 있다. 운영체제를 거치면 시간이 약간(? 보다 더 많이) 더 걸린다.

 

숫자로 비교해보자.

import timeit
s = "lis =  [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]"
timeit.timeit(stmt=s, number=1000000)
>>> 0.08575737499998581

t = "tup = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)"
timeit.timeit(stmt=t, number=1000000)
>>> 0.01574720899998283

 

 

튜플이 리스트보다 약 5배 더 빠르다.

관련글 더보기