본문 바로가기

코딩/Python

[Python/파이썬] Generator(제너레이터) , Iterator(이터레이터)

반응형

 

 

  파이썬 코딩에서 효율적인 코드란 어떤 코드를 말하는 것일까? 파이썬에서 효율적인 코드란, 간결한 문법과 효율적인 메모리 사용이 가능한 코드를 말하는 것이라고 생각한다. 효율적인 코드 작성을 위해서는 파이썬의 'Generator', 'Iterator'를 사용하는 것도 한 방법이다. 이번 포스팅에서는 파이썬 Generator(제너레이터), Iterator(이터레이터)에 대해 간단히 정리해보려고 한다.

 

 

1. Iterator(이터레이터)

 

  이터레이터는 반복가능한 객체를 말한다. for문이나 while 등 loop문을 통해 객체를 반복하여 어떤 결과를 출력해낼 수 있다. 반복이 가능한 객체라고 하면 예를 들어서, 파이썬의 대표적인 자료형 list, tuple, dictionary를 예로 들 수 있을 것 같다. 이러한 자료형들은 Iterable Object(반복가능한 객체)라고 표현하는데 Iterator(이터레이터)와는 또다른 개념이다.

 

  아래 코드 및 결과를 보면 이해할 수 있다.

 

 

<코드>

data = [1,2,3,4,5]
print("list type : ",type(data))

data_it = iter(data)
print("iter(list) type : ", type(data_it))

 

<결과>

list type :  <class 'list'>
iter(list) type :  <class 'list_iterator'>

 

 

  위 결과를 보면 알 수 있지만, 리스트의 타입은 class 'list'지만 iterator를 만드는 함수 iter(list)의 타입을 출력해보면 class 'list_interator'라고 출력된다. 결과를 통해 Iterable Object(반복가능 자료형 : list, tuple, dictionary)와 이터레이터(Iterator)는 다른 것이라고 이해할 수 있다. 다르게 말하면 Iterable Object 속에 list, tuple, dictionary, Iterator가 속해있다고 이해하는게 좀 더 쉬운 설명인 것 같다.

 

  그럼 아래 코드를 통해 Iterator의 사용법을 좀더 이해해보도록 하자. Iterator의 객체들은 내부 __next__() 메서드를 통해 객체의 각 요소를 출력할 수 있다.  한가지 특징으로 요소의 개수를 넘어__next__()를 호출하면 아래 결과에서 보듯이 "StopIteration" 오류를 확인할 수 있다.

 

 

<코드>

#리스트 정의
data = [1,2,3,4,5]

#data 리스트를 iterator화(iter 함수 사용)
data_it = iter(data)
print("iter(list) type : ", type(data_it))

#iterator의 내부 메서드 __next__() 사용
print(data_it.__next__())
print(data_it.__next__())
print(data_it.__next__())
print(data_it.__next__())
print(data_it.__next__())
print(data_it.__next__())

 

<결과>

iter(list) type :  <class 'list_iterator'>
1
2
3
4
5
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-20-3b3dc40c6507> in <module>()
      9 print(data_it.__next__())
     10 print(data_it.__next__())
---> 11 print(data_it.__next__())

StopIteration:

 

 

  이터레이터의 다음 요소를 출력하는 __next__( )는 next( )로 대신할 수도 있다. 아래처럼 말이다.

 

 

<코드>

data = [1,2,3,4,5]

data_it = iter(data)
print("iter(list) type : ", type(data_it))

print(next(data_it))
print(next(data_it))
print(next(data_it))
print(next(data_it))
print(next(data_it))

  

  iter(data) 즉, iterator가 반복가능한 객체이므로 당연히 우리가 for문으로 list를 출력하듯이 for, while 등 반복문을 사용하여 출력할 수 있다.   위 결과는 리스트를 iterator로 만들었지만 반복형 자료형인 tuple이나 dictionary도 동일하게 이터레이터로 만들 수 있다.

 

 

 

2. Generator(제너레이터)

 

  Generator(제너레이터)를 설명하기에 앞서 Iterator(이터레이터)를 설명했던 이유는 제너레이터가 이터레이터와 관련이 있기 때문이다. 제너레이터는 이터레이터를 생성해 주는 함수라고 생각하면 된다. 제너레이터는 일반 함수랑 다른 점이 있는데 아래 코드를 보며 이해해보도록 하자.

 

 

<코드>

#일반 함수 정의
def TestFunction(n):
  sum = 0
  temp = 0
  for i in range(1, n+1):
     temp = i + sum
     sum += temp
     print(i,"번째 합 : ")
     print(sum)
  return sum

#제너레이터 정의
def TestGenerator(n):
  sum = 0
  temp = 0
  for i in range(1, n+1):
     temp = i + sum
     sum += temp
     print(i, "번째 합 : ")
     yield(sum)

 

 

  위 처럼 일반 함수 TestFunction과 제너레이터인 TestGenerator를 똑같은 결과를 출력해주는 코드로 작성하였다. 2개 함수의 차이점은 TestFunction은 return값이 있다는 것이고, TestGenerator는 return 대신에 yield가 있다는 것이다.

 

  그리고 2개의 함수 매개변수를 5로 하고 차이점을 확인해보자.

 

 

Case1. TestFunction(5)

 

 

<결과>

1 번째 합 : 
1
2 번째 합 : 
4
3 번째 합 : 
11
4 번째 합 : 
26
5 번째 합 : 
57
57

 

마지막의 57은 최종 리턴값이다.

 

 

Case2. TestGenerator(5)

 

 

<결과>

<generator object TestGenerator at 0x7fe2f42dce50>

 

 

  yield 구문 하나로 Generator로 인식하여 이 함수는 'Generator object' 라는 것을 표시한다. 제너레이터는 이터레이터를 생성해주는 함수이다. 일반적인 함수가 return 값을 돌려줄 때, 함수가 종료된다면 제너레이터는 yield할 때, 값을 돌려주고 for문을 그대로 진행한다. 따라서, 제너레이터를 출력하려면 아래와 같이 코드를 작성한다.

 

 

<코드>

for sum in TestGenerator(5):
  print(sum)

 

<결과>

1 번째 합 : 
1
2 번째 합 : 
4
3 번째 합 : 
11
4 번째 합 : 
26
5 번째 합 : 
57

 

 

제너레이터를 표현하는 방법에는 위와 같이 함수처럼 정의하는 방법과 제너레이터 익스프레션(Generator Expression)이라는 방법도 있다. 이 방법은 리스트 컴프리헨션(List Comprehension)과 비슷한 표현식인데 아래 하단 참고링크 블로그에 잘정리되어 있으니 참고하면 좋을 것 같다.

(참고링크 : https://bluese05.tistory.com/56)

 

반응형

 

 


 

  그럼 제너레이터를 사용하는 이유는 뭘까? 리스트와 제너레이터를 비교해보자. 아래 내용은 하단의 참고링크2 티스토리 블로그의 내용을 참고하여 내 나름대로 재구성해본 것이다.

 

 

Case1. 리스트(List)

 

 

<코드>

import sys

a1 = [1,2,3,4,5]
print("a1 size : ",sys.getsizeof(a1))

a2= [1,2,3,4,5,6,7,8,9,10]
print("a2 size : ",sys.getsizeof(a2))

a3 = [1,2,3,4,5,6,7,8,10,11,12,13,14,15]
print("a3 size : ",sys.getsizeof(a3))

 

<결과>

a1 size :  112
a2 size :  152
a3 size :  184

 

 

  리스트의 경우 리스트에 저장 된 요소의 개수가 많아질 수록 차지하는 메모리 용량이 커진다. 즉, 리스트에 저장 된 개수와 메모리의 용량이 비례하는 것이다. 

 

  그럼 위 코드를 Generator 로 만들어보자.

 

 

Case2. 제너레이터(Generator)

 

 

<코드>

import sys

def Generator(n):
  for i in range(1, n+1):
    yield i

a1 = Generator(5)
print("a1 size : ",sys.getsizeof(a1))

a2 = Generator(10)
print("a2 size : ",sys.getsizeof(a2))

a3 = Generator(15)
print("a3 size : ",sys.getsizeof(a3))

 

<결과>

a1 size :  128
a2 size :  128
a3 size :  128

 

  리스트로 저장했던 정수들을 Generator로 생성하여 똑같이 메모리 사이즈만 구해본 것이다. 결과가 모두 128로 나온다. 즉, 리스트의 개수가 적을 땐 리스트가 효율적일 수 있으나 리스트에 저장된 개수가 많아질 수록 메모리 효율 측면에서는 제너레이터가 훨씬 더 좋다는 것이다.

 


 

참고링크1. Generator, Iterator 설명 : https://offbyone.tistory.com/83

참고링크2. Generator와 Iterator의 비교 :  https://bluese05.tistory.com/56

 

728x90