본문 바로가기
BASIC

제너레이터

by 자동매매 2023. 11. 28.

1. 제너레이터 사용하기

제너레이터이터레이터를 생성해주는 함수입니다.

이터레이터는 클래스에 __iter__, __next__ 또는 __getitem__ 메서드를 구현해야 하지만 제너레이터는 함수 안에서 yield라는 키워드만 사용하면 끝입니다.

그래서 제너레이터는 이터레이터보다 훨씬 간단하게 작성할 수 있습니다.

참고로 제너레이터는 발생자라고 부르기도 합니다. 이 책에서는 제너레이터를 사용하겠습니다.

 

함수 안에서 yield를 사용하면 함수는 제너레이터가 되며 yield에는 값(변수)을 지정합니다.

  • yield 값(변수)
def number_generator():
    yield 0
    yield 1
    yield 2
 
for i in number_generator():
    print(i)
0
1
2

 

1) 제너레이터 객체가 이터레이터인지 확인하기 :  __iter__, __next__ 메서드가 들어있음

>>> g = number_generator()
>>> g
<generator object number_generator at 0x03A190F0>
>>> dir(g)
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
>>> g.__next__()
0
>>> g.__next__()
1
>>> g.__next__()
2
>>> g.__next__()
Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    g.__next__()
StopIteration

 

이더레이터와 제너레이터의 차이

1. 이터레이터는 __next__ 메서드 안에서 직접 return으로 값을 반환했지만 제너레이터는 yield에 지정한 값이 __next__ 메서드(next 함수)의 반환값으로 나옵니다.

2. 이터레이터는 raise StopIteration 예외를 직접 발생시켰지만 제너레이터는 함수의 끝까지 도달하면 StopIteration 예외가 자동으로 발생합니다.

2)  yield의 동작 과정 알아보기

  • 변수 = next(제너레이터객체)
def number_generator():
    yield 0    # 0을 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보
    yield 1    # 1을 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보
    yield 2    # 2를 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보
 
g = number_generator()
 
a = next(g)    # yield를 사용하여 함수 바깥으로 전달한 값은 next의 반환값으로 나옴
print(a)       # 0
 
b = next(g)
print(b)       # 1
 
c = next(g)
print(c)       # 2
0
1
2

 

 

제너레이터와 return

제너레이터는 함수 끝까지 도달하면 StopIteration 예외가 발생합니다. 마찬가지로 return도 함수를 끝내므로 return을 사용해서 함수 중간에 빠져나오면 StopIteration 예외가 발생합니다.

특히 제너레이터 안에서 return에 반환값을 지정하면 StopIteration 예외의 에러 메시지로 들어갑니다.

def one_generator():
    yield 1
    return 'return에 지정한 값'

try:
    g = one_generator()
    print(next(g))
    print(next(g))
except StopIteration as e:
    print(e)     # return에 지정한 값
1
return에 지정한 값

 

2. 제너레이터 만들기

def number_generator(stop):
    n = 0              # 숫자는 0부터 시작
    while n < stop:    # 현재 숫자가 반복을 끝낼 숫자보다 작을 때 반복
        yield n        # 현재 숫자를 바깥으로 전달
        n += 1         # 현재 숫자를 증가시킴
 
for i in number_generator(3):
    print(i)
0
1
2

 

 

1) yield에서 함수 호출하기

def upper_generator(x):
    for i in x:
        yield i.upper()    # 함수의 반환값을 바깥으로 전달
 
fruits = ['apple', 'pear', 'grape', 'pineapple', 'orange']
for i in upper_generator(fruits):
    print(i)
APPLE
PEAR
GRAPE
PINEAPPLE
ORANGE
 
 

2) yield from으로 값을 여러 번 바깥으로 전달하기

이런 경우에는 매번 반복문을 사용하지 않고, yield from을 사용하면 됩니다. yield from에는 반복 가능한 객체, 이터레이터, 제너레이터 객체를 지정합니다
  • yield from 반복가능한객체
  • yield from 이터레이터
  • yield from 제너레이터객체
def number_generator():
    x = [1, 2, 3]
    yield from x    # 리스트에 들어있는 요소를 한 개씩 바깥으로 전달
 
for i in number_generator():
    print(i)
1
2
3

yield from x와 같이 yield from에 리스트(반복 가능한 객체)를 지정했습니다. 이렇게 하면 리스트에 들어있는 요소를 한 개씩 바깥으로 전달합니다. 

>>> g = number_generator()
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
Traceback (most recent call last):
  File "<pyshell#105>", line 1, in <module>
    next(g)
StopIteration

 

yield from에 제너레이터 객체 지정하기

def number_generator(stop):
    n = 0
    while n < stop:
        yield n
        n += 1
 
def three_generator():
    yield from number_generator(3)    # 숫자를 세 번 바깥으로 전달
 
for i in three_generator():
    print(i)
0
1
2

 

 
제너레이터 표현식

리스트 표현식을 사용할 때 [ ](대괄호)를 사용했습니다.

같은 리스트 표현식을 ( )(괄호)로 묶으면 제너레이터 표현식이 됩니다.

리스트 표현식은 처음부터 리스트의 요소를 만들어내지만 제너레이터 표현식은 필요할 때 요소를 만들어내므로 메모리를 절약할 수 있습니다.

 

(식 for 변수 in 반복가능한객체)

>>> [i for i in range(50) if i % 2 == 0]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48]
>>> (i for i in range(50) if i % 2 == 0)
<generator object <genexpr> at 0x024F02A0>


 

 

[ 연습문제 ]

다음 소스 코드에서 words.txt 파일을 한 줄씩 읽은 뒤 내용을 함수 바깥에 전달하는 제너레이터를 작성하세요. 파일의 내용을 출력할 때 파일에서 읽은 \n은 출력되지 않아야 합니다(단어 사이에 줄바꿈이 두 번 일어나면 안 됨).

[ words.txt ]

compatibility
experience
photography
spotlight

 

def file_read():
    with open('words.txt') as file:
        for f in file:
            yield f.strip('\n')

for i in file_read():
    print(i)
compatibility
experience
photography
spotlight

 

 

 

[ 연습문제 ]

표준 입력으로 정수 두 개가 입력됩니다(첫 번째 입력 값의 범위는 10~1000, 두 번째 입력 값의 범위는 100~1000이며 첫 번째 입력 값은 두 번째 입력 값보다 항상 작습니다).

다음 소스 코드에서 첫 번째 정수부터 두 번째 정수 사이의 소수(prime number)를 생성하는 제너레이터를 만드세요.

소수는 1과 자기자신만으로 나누어 떨어지는 1보다 큰 양의 정수입니다.

def prime_number(start, stop):
    L = []
    for i in range(start, stop):
        for j in range(2, i + 1):
            if i % j == 0 and i != j:
                break
            elif i % j == 0 and i == j:
                L.append(i)
    return L


def prime_number_generator(start, stop):
    yield from prime_number(start, stop)
    return "StopIterator"


start, stop = map(int, input().split())

g = prime_number_generator(start, stop)
print(type(g))
for i in g:
    print(i, end=' ')

 

[ 입력 ]
50 100

[ 결과 ]
<class 'generator'>
53 59 61 67 71 73 79 83 89 97 

[ 입력 ]
950 1000

[ 결과 ]
<class 'generator'>
953 967 971 977 983 991 997

 

'BASIC' 카테고리의 다른 글

데코레이터  (1) 2023.11.28
코루틴  (0) 2023.11.28
이터레이터  (0) 2023.11.28
회문 판별과 N-gram 만들기  (1) 2023.11.28
파일 다루기  (2) 2023.11.28

댓글