파이썬 기초 강좌 #9 튜플 (Tuple)

14 분 소요

오늘의 강좌에서는 튜플에 대해서 공부하겠습니다.

튜플은 지난 강좌에서 배운 리스트와 아주 비슷한 데이터 타입입니다. 그래서 그런지 대부분의 분들이 리스트와 튜플에 차이점은 아시지만, 언제 리스트를 사용해야하고 언제 튜플을 사용해야하는지를 모르시는 분들이 많더라구요.

지금부터 리스트와 튜플의 차이를 알아보고, 리스트 대신 언제 튜플을 사용해야 하는지 알아보도록 하겠습니다.

먼저 튜플을 정의하는 방법을 알아보겠습니다.

리스트는 브라켓을 사용하여 정의 하지만, 튜플은 괄호를 사용하여 정의합니다. 예제를 보면서 설명을 드리겠습니다.

괄호를 사용하여 튜플 하나를 정의해 보겠습니다.

>>> t1 = (1, 2, 3)
>>> t1
(1, 2, 3)

타입 함수를 사용하여 데이터 타입을 확인해보도록 하죠.

>>> type(t1)
<class 'tuple'>

데이터 타입이 튜플인 것을 확인했습니다.

튜플을 정의할 때 괄호를 생략할 수도 있습니다.

괄호를 사용하지 않고 정의를 해보겠습니다.

>>> t2 = 1, 2, 3
>>> t2
(1, 2, 3)

다시 데이터 타입을 확인해 보겠습니다.

>>> type(t2)
<class 'tuple'>

괄호를 사용했을 때와 같이 튜플이 만들어진 것을 확인하였습니다.

리스트와는 다르게 튜플을 정의할 때는 주의할 점이 하나 있습니다. 튜플안에 정의되는 아이템이 하나일 때는 아이템뒤에 컴마를 꼭 입력하셔야 합니다.

먼저 하나의 아이템을 가진 리스트를 정의해보겠습니다.

>>> l = ['one']
>>> l
['one']

리스트는 이렇게 문제없이 정의가 됐는데, 튜플의 경우는 어떻게 되는지 확인해보죠.

>>> t = ('one')
>>> t
'one'

뭔가 이상하지 않나요? 괄호가 보이지 않네요???

이 오브젝트의 데이터타입을 확인해 보겠습니다.

>>> type(t)
<class 'str'>

튜플이 아니고 스트링 오브젝트가 만들어졌네요. 앞에서 말씀드린대로 아이템을 하나만 가진 튜플을 정의할 때는 다음 아이템이 없어도 마지막에 콤마를 입력하셔야합니다.

>>> t = ('one',)
>>> t
('one',)

이번에는 괄호가 보이는 것을 알 수 있습니다. 실제로 튜플이 정의가 되었는지 확인해 보겠습니다.

>>> type(t)
<class 'tuple'>

앞에서 튜플은 리스트와 아주 비슷한 데이터 타입이라고 말씀 드렸는데요, 어떤 점이 비슷한지 확인해보겠습니다.

튜플은 리스트와 같이 순서를 가진 시퀀스 타입입니다. 따라서 리스트와 같이 인덱스를 사용할 수도 있고, 슬라이싱을 할 수도 있습니다.

튜플을 하나 정의한뒤 인덱싱을 사용하여 첫번째 아이템을 출력해보겠습니다.

>>> t = 1, 2, 3, 4, 5
>>> t[0]
1

이번에는 슬라이싱을 사용하여 첫 3개의 아이템을 출력해보겠습니다.

>>> t[:3]
(1, 2, 3)

튜플 또한 리스트와 같은 이터레이터 오브젝트이 때문에 for loop을 사용할 수 있습니다.

iter 함수를 사용하여 tuple이 이터레이터 오브젝트인지 확인해 보겠습니다.

>>> iter(t)
<tuple_iterator object at 0x000002DBC4985640>

이터레이터 오브젝트인 것을 확인 했으니, for loop을 사용해 보겠습니다.

>>> for i in t:
...     print(i)
...
1
2
3
4
5

이 번에는 튜플과 리스트의 다른 점에 대해서 알아보겠습니다.

첫 번째 다른 점입니다.

리스트는 이미 정의된 아이템의 값을 변경할 수 있지만, 튜플은 한 번 정의된 아이템의 값을 변경할 수 없습니다. C, C++, Go 등과 같은 언어에서 사용되는 컨스턴스와 비슷하다고 보시면 됩니다.

먼저 리스트를 정의한 뒤 아이템의 값을 변경해 보겠습니다.

>>> my_list = [1, 2, 3]
>>> my_list[0] = 'one'
>>> my_list
['one', 2, 3]

이 번에는 튜플을 정의하고 첫 번째 아이템의 값을 변경해보겠습니다.

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

튜플 오브젝트는 아이템 어싸인먼트를 지원하지 않는다는 타입에러가 발생했습니다.

두 번째 다른 점입니다.

리스트는 새로운 아이템을 추가하거나, 기존의 아이템을 삭제할 수 있습니다. 하지만 튜플은 정의되는 순간 메모리상의 길이가 고정되어 버리기 때문에 새로운 아이템을 추가하거나 기존의 아이템을 삭제할 수 없습니다.

먼저 리스트를 정의한 뒤 새로운 아이템을 추가해보겠습니다.

>>> my_list = [1, 2, 3]
>>> my_list.append(4)
>>> my_list
[1, 2, 3, 4]

새로운 아이템 4가 추가되었습니다.

추가한 아이템을 다시 삭제해 보겠습니다.

>>> my_list.remove(4)
>>> my_list
[1, 2, 3]

새로 추가한 아이템이 삭제되었습니다.

remove 메소드를 사용하실 때는 삭제할 아이템의 인덱스를 사용하는 것이 아니라, 삭제하고 싶은 아이템의 값을 입력해야한다는 것을 기억하시기 바랍니다. 만약에 인덱스를 사용하고 싶으시면 remove 메소드 대신에 pop 메소드를 사용하시기 바랍니다.

이 번에는 튜플을 정의하고 새로운 아이템을 추가해보겠습니다.

>>> my_tuple = 1, 2, 3
>>> my_tuple.append(4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'append'

튜플 오브젝트는 어펜드 에트리뷰트를 가지고 있지 않다고하는 에러 메세지가 출력됐습니다. 튜플은 아이템 추가, 삭제가 안되므로 append나 remove 함수를 가지고 있지 않습니다.

그렇다면 튜플 클래스에는 어떤 메소드가 정의되어 있는지 헬프 함수로 확인해보겠습니다.

>>> help(tuple)
Help on class tuple in module builtins:
 
class tuple(object)
|  tuple(iterable=(), /)
|
|  Built-in immutable sequence.
|
|  If no argument is given, the constructor returns an empty tuple.
|  If iterable is specified the tuple is initialized from iterable's items.
|
|  If the argument is a tuple, the return value is the same object.
|
|  Built-in subclasses:
|      asyncgen_hooks
|      UnraisableHookArgs
|
|  Methods defined here:
|
|  __add__(self, value, /)
|      Return self+value.
|
|  __contains__(self, key, /)
|      Return key in self.
|
|  __eq__(self, value, /)
|      Return self==value.
|
|  __ge__(self, value, /)
|      Return self>=value.
|
|  __getattribute__(self, name, /)
|      Return getattr(self, name).
|
|  __getitem__(self, key, /)
|      Return self[key].
|
|  __getnewargs__(self, /)
|
|  __gt__(self, value, /)
|      Return self>value.
|
|  __hash__(self, /)
|      Return hash(self).
|
|  __iter__(self, /)
|      Implement iter(self).
|
|  __le__(self, value, /)
|      Return self<=value.
|
|  __len__(self, /)
|      Return len(self).
|
|  __lt__(self, value, /)
|      Return self<value.
|
|  __mul__(self, value, /)
|      Return self*value.
|
|  __ne__(self, value, /)
|      Return self!=value.
|
|  __repr__(self, /)
|      Return repr(self).
|
|  __rmul__(self, value, /)
|      Return value*self.
|
|  count(self, value, /)
|      Return number of occurrences of value.
|
|  index(self, value, start=0, stop=9223372036854775807, /)
|      Return first index of value.
|
|      Raises ValueError if the value is not present.
|
|  ----------------------------------------------------------------------
|  Class methods defined here:
|
|  __class_getitem__(...) from builtins.type
|      See PEP 585
|
|  ----------------------------------------------------------------------
|  Static methods defined here:
|
|  __new__(*args, **kwargs) from builtins.type
|      Create and return a new object.  See help(type) for accurate signature.

가장 윗 부분에 튜플 클래스는 빌트인 이뮤터블 시퀀스라는 중요한 설명이 있고, 가장 아래부분을 보면 카운트와 인덱스 메소드만 정의되어 있는 것을 볼 수 있습니다.

이 두 메소드를 사용해보겠습니다. 먼저 카운트 메소드를 사용하여 튜플아이템안의 같은 값을 가지고 있는 아이템이 몇개인지 확인해보겠습니다.

>>> t = 1, 2, 2, 2, 3
>>> t.count(2)
3

2의 값을 가진 아이템이 3개 있다는 것을 확인하였습니다.

이번에는 인덱스 메소드로 3의 값을 가진 아이템의 인덱스를 확인해보겠습니다.

>>> t.index(3)
4

3의 값을 가진 아이템의 인덱스는 4인 것을 확인했습니다.

튜플과 리스트의 차이점 3번째입니다.

튜플은 리스트보다 적은 메모리를 사용합니다. 먼저 같은 아이템을 리스트와 튜플에 저장하고 각각 사용하고 있는 메모리의 크기를 확인해보겠습니다.

my_list = ['one', 1, 1.2345, True]
my_tuple = 'one', 1, 1.2345, True

먼저 리스트가 사용하고 있는 메모리 크기를 확인해보겠습니다.

>>> import sys
>>> sys.getsizeof(my_list)
120

이번에는 튜플이 사용하고 있는 메모리 크기를 확인해보겠습니다.

>>> sys.getsizeof(my_tuple)
72

아이템이 몇개 되지도 않는데 많은 차이가 나는 것을 볼 수 있습니다. 만약에 아이템의 숫자가 몇백만개라고 했을 때, 차이는 엄청나겠죠?

네 번째 다른점입니다.

튜플은 리스트 보다 더 빨리 생성됩니다. 튜플은 메모리상에 하나의 블럭으로 저장되지만, 리스트는 두개의 블럭에 각각 오브젝트에 대한 정보와 데이터가 저장되기 때문에 튜플 보다 생성 속도가 느립니다.

실제 튜플과 리스트의 오브젝트 생성 속도를 비교해 보겠습니다. 먼저 리스트가 생성되는 시간을 측정해보겠습니다.

# 아래 코드는 ipython 이나 jupyter notebook 에서 실행하셔야 합니다.
In [1]: %timeit my_list = [1, 2, 3, 4, 5]
46.9 ns ± 0.933 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

이번에는 튜플이 생성되는 시간을 측정해보겠습니다.

# 아래 코드는 ipython 이나 jupyter notebook 에서 실행하셔야 합니다.
In [2]: %timeit my_tuple = 1, 2, 3, 4, 5
15.5 ns ± 0.65 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

튜플이 3배 정도 빨리 생성되는 것을 알 수 있습니다.

다섯 번째 다른 점입니다.

튜플은 리스트에 비해서 인덱스를 사용하여 아이템에 엑세스하는 속도가 더 빠릅니다.

실제 리스트와 튜플의 인덱싱 속도를 측정해보겠습니다.

In [3]: %timeit my_list[0]
37.7 ns ± 1.93 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [4]: %timeit my_tuple[0]
36.3 ns ± 1.16 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

리스트와 튜플의 인덱싱 속도는 거의 비슷한 것을 볼 수 있습니다.

마지막으로 지금까지 배운 튜플의 장정과 단점을 정리해보고, 리스트를 대신하여 언제 튜플을 사용해야하는지 알아보도록 하겠습니다.

튜플은 리스트에 비해 적은 메모리를 사용하고, 생성시간이 빠릅니다. 그리고 인덱스를 사용하여 아이템에 엑세스하는데 걸리는 시간이 리스트에 비해 짧다는 것을 배웠습니다. 하지만 튜플은 아이템의 값을 변경하거나, 아이템을 추가 또는 삭제할 수가 없습니다. 그리고 sorted 함수를 사용하여 아이템을 정렬할 수 없습니다.

이제는 리스트를 대신하여 언제 튜플을 사용해야 하는지 아시겠죠?

오브젝트를 정의한 뒤, 아이템의 값을 변경해야 하거나, 새로운 아이템을 추가해야 하거나, 아이템을 순서대로 정렬해야 하거나 할 때는 리스트를 사용하시면 됩니다. 반대로 한 번 정의된 데이터가 변경되서는 안될 경우와 데이터를 변경할 필요가 없을 때는 항상 메모리를 적게 사용하고 퍼포먼스가 더 좋은 튜플을 사용하시면 됩니다.

이 번 강좌는 여기서 마치고 다음 강좌에서는 아주 중요한 데이터 타입인 딕셔너리에 대해서 공부하도록 하겠습니다.