본문 바로가기
[파이썬]/객체

[파이썬][객체] 제너레이터

by sung min_Kim 2023. 11. 17.
제너레이터

값을 차례대로 반환하며, 상태를 유지하는 함수


· 제너레이터를 사용하는 이유는 ?


 제너레이터는 이터레이터를 더 쉽게 생성할 수 있는 방법을 제공하는 특별한 종류의 함수이다.
 이터레이터와 마찬가지로 '지연 계산' 방식을 사용함으로써, 메모리를 효율적으로 관리하며, 필요한 시점에 요소를 생성하고 반환한다.

 '지연 계산'에 대해서는 이전 글에 자세히 기재해 두었으니, 참고하여 보시길 바란다.
2023.11.17 - [[파이썬]/객체] - [파이썬][객체] 이터레이터

 

[파이썬][객체] 이터레이터

HTML 삽입 미리보기할 수 없는 소스 이터레이터 반복 가능한 객체를 생성하는 파이썬 객체 · 이터레이를 사용하는 이유는 ? 이터레이터를 사용하는 주된 이유는 '지연 계산'을 가능하게 하기 위

sungmin93.tistory.com

 


. 제너레이터와 이터레이터의 차이점

 

  1.  생성 방법 : 이터레이터는 클래스를 정의하고 '__iter__()'와 '__next__()' 메소드를 구현해야 한다. 반면에 제너레이터는 함수를 정의하고, 'yield'문을 사용하기만 하면 된다. 이로 인해 제너레이터는 이터레이터보다 훨씬 간단하게 생성할 수 있다.


  2. 상태 유지 : 이터레이터는 'self'를 통해 상태를 유지한다. 이는 이터레이터가 객체 자신인 클래스의 인스턴스를 반환함으로써 가능하다. 반면에 제너레이터는 로컬 변수와 실행 상태를 자동으로 유지한다. 이로 인해 제너레이터는 이터레이터보다 상태 관리가 간단하다.


  3. 메모리 사용량 : 이터레이터와 제너레이터 모두 '지연 계산' 방식을 사용하여 메모리를 효율적으로 관리한다. 따라서 둘 다 대용량 메모리를 다루는 데에 유용하다. 그러나 제너레이터는 함수를 사용하기 때문에 이터레이터보다 메모리 사용량이 더 적을 수 있다.


결과적으로, 제너레이터는 이터레이터의 특성을 가지면서도 이터레이터를 더 쉽고 간단하게 생성할 수 있는 방법을 제공한다. 

 


· 기본 형태

 

def generator():
	for i in range(3):
		yield i

 

  • yield : 'yield' 문을 사용하여 파이썬에게 해당 함수가 제너레이터 객체임을 알리고, 해당 객체의 로컬 변수(i)와 실행 상태를 저장한다. 따라서 제너레이터 객체는 메모리에 남아있는 상태로 존재하며, 위치를 기억함으로써 해당 객체를 호출할 때마다 다음의 값을 하나씩 순차적으로 반환한다.

 

res_gen = generator()
print(res_gen)       # <generator object generator at 0x7c8671a77c30> 출력
print(type(res_gen)) # <class 'generator'> 출력

 
 제너레이터를 호출한 결과, 제너레이터 타입은 '객체(object)'로서 메모리에 할당되어 있는 것을 확인할 수 있다.

 


· 동작 원리

 

def num_generator(max_num):
    num = 0
    while num < max_num:
        yield num
        num += 1


 0부터 4까지 숫자를 출력하는 제너레이터 객체이다. 'yield' 문을 사용하여 해당 함수가 제너레이터 객체임을 파이썬에게 알린다. 이로써 해당 객체는 반복가능객체(max_num)의 각 요소(로컬변수, num)의 위치와 실행 결과를 기억하고, 호출이 끝난 이후에도 메모리 상에 남아있어 참조할 수 있는 상태가 된다.


num_gen = num_generator(5)
for i in num_gen:
    print(i) # 0 ... 4 출력

 
 위의 제너레이터 객체를 호출하면 외부인자(max_num)에 5의 값이 대입되고, 참조 변수(num_gen)에 해당 객체가 반환한 0부터 4까지의 값을 갖게 된다. 이를 for문을 통해 각 요소를 출력하면 제너레이터 객체로부터 넘겨받은 값을 순서대로 출력하는 것을 볼 수 있다. 


 'next()' 함수를 이용하여 값을 출력하여 보면 한 번 호출할 때마다, 다음의 값을 하나씩 반환하는 것을 직관적으로 확인할 수 있다.

num_gen = num_generator(5)

print(next(num_gen)) # 0 반환
print(next(num_gen)) # 1 반환
print(next(num_gen)) # 2 반환
print(next(num_gen)) # 3 반환
print(next(num_gen)) # 4 반환

 

- 위의 제너레이터 함수를 이터레이터 함수로 변환하기 (코드의 간결성 비교)
class num_iterator:

  def __init__(self, max_num):
    self.num = 0
    self.max_num = max_num
  def __iter__(self):
    return self
        
  def __next__(self):
    if self.num < self.max_num:
      result = self.num
      self.num += 1
      return result
    else:
      raise StopIteration
num_iter = num_iterator(5)
print(type(num_iter)) # <class '__main__.num_iterator'> 출력

for i in num_iter:    # 0 ... 4 출력
	print(i)


 위의 제너레이터 함수를 이터레이터 함수로 표기해 보았다. 이터레이터와 비교하여 제너레이터의 코드가 얼마나 간결한지를 확인할 수 있다.


- 제너레이터와 이터레이터를 사용했을 경우의 메모리 사용량 확인하기

 

  • 이터레이터 객체 정의
from memory_profiler import profile

%load_ext memory_profiler

class Ex_iterator:
    def __init__(self, limit):
        #반복 범위를 지정할 값(반복의 끝값)
        self.limit = limit
        # 반복 시작값
        self.current = 0
        
    def __iter__(self):
        return self
        
    def __next__(self):
        if self.current < self.limit:
            self.current += 1  
            return self.current
        else:
            raise StopIteration

 

 

  • 제너레이터 객체 정의 및 각 각의 객체 호출 
# 제너레이터를 사용한 경우
@profile
def generator(limit):
  for i in range(0, limit):
    yield i
    
# 이터레이터를 사용한 경우
@profile
def iterator(limit):
    data = Ex_iterator(limit)
    for item in data:
        pass

 

 

  • 메모리 사용량 출력(300만 번 반복 동작 수행)
limit = 3000000
try:
    %memit generator(limit)
    
    %memit iterator(limit)
except:
    pass

 

 

  • 메모리 사용량 및 증가 폭 확인
    - 제너레이터와 이터레이터 모두 '지연 계산' 방식을 사용하여, 메모리 사용량이 없다시피 나타났다. 대용량 데이터를 처리하는데 매우 유용하다는 것을 다시금 확인할 수 있다.
ERROR: Could not find file C:\Users\vosej\AppData\Local\Temp\ipykernel_1360\2303716115.py
peak memory: 67.02 MiB, increment: 0.00 MiB

ERROR: Could not find file C:\Users\vosej\AppData\Local\Temp\ipykernel_1360\2303716115.py
peak memory: 67.02 MiB, increment: 0.00 MiB

 


제너레이터에 대하여 알아보았다.

제너레이터는 이터레이터와 달리 'yield' 키워드를 통해, '지연 계산' 방식을 사용하여, 로컬 변수와 실행 결과를 유지한다. 

이로 인해 제너레이터는 이전의 상태를 '기억'하고 있으며, 호출될 때 다음의 요소를 하나씩 반환한다.

이 방식을 통해 메모리를 효율적으로 관리하며, 이는 대용량 데이터를 다루는 상황에서 제너레이터의 중요성을 이해하는 데 도움이 될 것이다.

'[파이썬] > 객체' 카테고리의 다른 글

[파이썬][객체] 이터레이터  (0) 2023.11.17