[Python] 파이썬 시퀀스

728x90

1. 시퀀스

파이썬에서 시퀀스는 여러 요소를 순서대로 나열한 데이터 구조를 의미합니다.

 

이러한 시퀀스는 저장하는 방식에 따라 컨테이너와 플랫으로 나눌 수 있고,

 

변경가능여부에 따라 가변(mutable)자료형과 불변(immutable)자료형으로도 구분할 수 있습니다.

 

여기서 컨테이너에는 list, tuple, collections, deque 등이 들어가며, 플랫에는 str, bytes,bytearray,array.array, memoryview 등의 자료형이 들어갑니다.

 

가변 자료형에는 list, bytearray,array.array, memoryview, deque가 있으며 불변자료형에는 tuple, str, bytes 등이 있습니다.

 

2. 제너레이터

아주 많은 양의 데이터를 리스트에 담아 변수로 만들면 메모리 용량을 아주 많이 차지하게 됩니다. 

그러나 제너레이터를 사용하면 메모리를 절약할 수 있습니다. 왜냐하면 제너레이터는 해당 인덱스의 값과 다음 위치값만 메모리에 저장하기 때문입니다.

 

아래 예제로 리스트와 제너레이터의 차이를 알아보겠습니다.

 

import sys
# 제너레이터는 한번에 한개의 항목을 생성한다(메모리 유지를 하지않는다.)

chars = '+_)(*&^%$#@!'

list_a = [ord(i) for i in chars]
print(list_a) #출력:[43, 95, 41, 40, 42, 38, 94, 37, 36, 35, 64, 33]
generator_a = (ord(i) for i in chars)
print(type(generator_a)) #출력: <class 'generator'>

print("__sizeof__() 메서드로 호출한 리스트의 메모리 크기:",list_a.__sizeof__()) # 출력: __sizeof__() 메서드로 호출한 리스트의 메모리 크기: 168
print("sys.getsizeof 메서드로 호출한 리스트의 메모리 크기:",sys.getsizeof(list_a)) #출력: sys.getsizeof 메서드로 호출한 리스트의 메모리 크기: 184
print("__sizeof__() 메서드로 호출한 제너레이터의 메모리 크기:",generator_a.__sizeof__()) # 출력: __sizeof__() 메서드로 호출한 제너레이터의 메모리 크기: 96
print("sys.getsizeof 메서드로 호출한 제너레이터의 메모리 크기:",sys.getsizeof(generator_a)) # 출력: sys.getsizeof 메서드로 호출한 제너레이터의 메모리 크기: 112

# next 함수를 호출할 때마다 다음 값으로 넘어감
print(next(generator_a)) #출력:43
print(next(generator_a)) #출력:95

for s in generator_a:
    print(s, end=" ") # 출력: 41 40 42 38 94 37 36 35 64 33 -> 앞에서 next함수를 호출했기 때문에 그 다음부터 호출됨

 

참고) __sizeof__() 메서드와 sys.getsizeof()메서드의 결과값이 차이나는 이유는 아래사이트에 확인하시면 됩니다.

 

https://www.tutorialspoint.com/difference-between-sizeof-and-getsizeof-method-in-python

 

위에서 보다시피 next함수를 호출할때마다 값이 계속 변경되는 것을 볼 수 있습니다.

그러나 메모리 저장용량은 변하지 않습니다. 

 

아래 예제를 통해 증명하겠습니다.

import sys

chars = '+_)(*&^%$#@!'

generator_a = (ord(i) for i in chars)

# next 함수를 호출할 때마다 다음 값으로 넘어감
print(next(generator_a)) #출력:43
print(next(generator_a)) #출력:95

for s in generator_a:
    print(s, end=" ") # 출력: 41 40 42 38 94 37 36 35 64 33 -> 앞에서 next함수를 호출했기 때문에 그 다음부터 호출됨
    
print()
print("__sizeof__()로 호출한 제너레이터 메모리 크기: ",generator_a.__sizeof__()) #출력: __sizeof__()로 호출한 제너레이터 메모리 크기:  96
print("sys.getsizeof() 메서드로 호출한 제너레이터 메모리 크기: ",sys.getsizeof(generator_a)) #출력: sys.getsizeof() 메서드로 호출한 제너레이터 메모리 크기:  112

 

이번에는 내부 요소의 값을 늘려보겠습니다.

import sys

chars2 = "sdklasfdjlkaasdasdjasdasfadsfadsgdfhashasdghfd"

list_b = [ord(i) for i in chars2]

generator_b = (ord(i) for i in chars2)

print(list_b) # 출력: [115, 100, 107, 108, 97, 115, 102, 100, 106, 108, 107, 97, 97, 115, 100, 97, 115, 100, 106, 97, 115, 100, 97, 115, 102, 97, 100, 115, 102, 97, 100, 115, 103, 100, 102, 104, 97, 115, 104, 97, 115, 100, 103, 104, 102, 100]

print("__sizeof__() 메서드로 호출한 리스트의 크기: ",list_b.__sizeof__()) #출력: __sizeof__() 메서드로 호출한 리스트의 크기:  456
print("sys.getsizeof() 메서드로 호출한 리스트의 크기: ",sys.getsizeof(list_b)) #출력: sys.getsizeof() 메서드로 호출한 리스트의 크기:  472

print("__sizeof__() 메서드로 호출한 제너레이터의 크기: ",generator_b.__sizeof__()) #출력: __sizeof__() 메서드로 호출한 제너레이터의 크기:  96
print("sys.getsizeof() 메서드로 호출한 제너레이터의 크기: ",sys.getsizeof(generator_b)) #출력: sys.getsizeof() 메서드로 호출한 제너레이터의 크기:  112

 

위 예제를 확인해보면 내부요소가 늘어남으로써 리스트가 차지하는 메모리용량은 늘어난 반면 제너레이터의 메모리용량은 동일한것을 확인할 수 있습니다. 따라서 제너레이터를 사용하면 메모리를 효율적으로 사용할 수 있습니다.

 

3. 가변형 vs 불변형

가변형과 불변형을 구분하기 위해 튜플과 리스트로 예제를 들어보겠습니다.

 

tuple_a = (10,20,30) # 불변형
list_a = [10,20,30] # 가변형

print(id(tuple_a),tuple_a) #출력: 2685997577472 (10, 20, 30)
print(id(list_a),list_a) # 출력: 2685996473728 [10, 20, 30]

tuple_a = tuple_a*2
list_a = list_a*2

print(id(tuple_a),tuple_a) #출력: 2685995504256 (10, 20, 30, 10, 20, 30)
print(id(list_a),list_a) #출력: 2685995161664 [10, 20, 30, 10, 20, 30]

tuple_a *=2
list_a *=2
print(id(tuple_a),tuple_a) #출력: 2685995077344 (10, 20, 30, 10, 20, 30, 10, 20, 30, 10, 20, 30)
print(id(list_a),list_a) #출력:2685995161664 [10, 20, 30, 10, 20, 30, 10, 20, 30, 10, 20, 30]

# 불변 자료형은 매번 id를 재할당 하기 때문에 id값이 계속 변경되는 반면에 가변자료형은 id값이 변하지 않는다.