Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/Python] 제네레이터 (generator) 본문

Programming/Python

[Programming/Python] 제네레이터 (generator)

scii 2023. 1. 29. 20:06

제네레이터는 제네레이터 객체에서 __next__ 메서드를 호출할 때마다 함수 안의 yield 까지 코드를 실행하며 yield에서 값을 발생시킨다(generate). 그래서 이름이 제네레이터이다.

for와 제네레이터

for 반복문은 반복할 때마다 __next__ 를 호출하므로 yield에서 발생시킨 값을 가져온다. 그리고 StopIteration 예외가 발생하면 반복을 끝낸다.

참고로 제네레이터 객체에서 __iter__를 호출하면 self를 반환하므로 같은 객체가 나온다. (제네레이터 함수 호출 > 제네레이터 객체 > __iter__는 self 반환 > 제네레이터 객체)

그런데 generate라는 키워드를 사용하면 되지 왜 yield라고 이름을 지었을까? yield는 생산하다라는 뜻과 양보하다라는 뜻도 가지고 있다. 즉, yield를 사용하면 값을 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보한다. 따라서 yield는 현재 함수를 잠시 중단하고 함수 바깥의 코드가 실행되도록 만든다.

yield의 동작 과정

yield의 동작 과정을 알아보기 위해 for 반복문 대신 next 함수로 __next__ 메서드를 직접 호출해보자.

def number_generator():
    yield 0     # 0을 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보
    yield 1     # 1을 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보
    yield 2     # 2을 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보


if __name__ == '__main__':
    g = number_generator()
    
    a = next(g)     # yield를 사용하여 함수 바깥으로 전달한 값은 next의 반환값으로 나옴
    print(a)        # 0
    
    b = next(g)     
    print(b)        # 1
    
    c = next(g)
    print(c)        # 2

yield/를 사용하여 바깥으로 전달한 값은 next함수의 반환값으로 나온다고 했다. 따라서 next(g)의 반환 값을 출력해보면 yield에 지정한 값 0, 1, 2가 차례대로 나온다. 즉, 제네레이터 함수가 실행되는 중간에 next로 값을 가져온다.

제네레이터는 함수를 끝내지 않은 상태에서 yield를 사용하여 값을 바깥으로 전달할 수 있다. 즉, return은 반환 즉시 함수가 끝나지만 yield는 잠시 함수 바깥의 코드가 실행되도록 양보하여 값을 가져가게 한 뒤 다시 제네레이터 안의 코드를 계속 실행하는 방식이다.


제네레이터 만들기

range(count)처럼 동작하는 제네레이터를 만들어보자.

ef number_generator(stop: int) -> int:
    i = 0
    while i < stop:
        yield i
        i += 1


for i in number_generator(3):
    print(i)
    
    
# 결과
0
1
2

yield에서 함수 호출

def upper_generator(x: list) -> str:
    for i in x:
        yield i.upper()


fruits = ['apple', 'pear', 'grape']
for i in upper_generator(fruits):
    print(i, end=' ')
    
    
# 결과
APPLE PEAR GRAPE

yield from으로 값을 여러 번 바깥으로 전달

지금까지 yield로 값을 한 번씩 바깥으로 전달했다. 그래서 값을 여러 번 바깥으로 전달할 때는 for 또는 while 반복문으로 반복하면서 yield를 사용했다. 다음은 리스트의 1, 2, 3을 바깥으로 전달한다.

def number_generator():
    x = [1, 2, 3]
    for i in x:
        yield i

for i in number_generator():
    print(i)

이런 경우에는 매번 반복문을 사용하지 않고 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)

yield from x와 같이 yield from에 리스트를 지정했다. 이렇게 하면 리스트에 들어있는 요소를 한 개씩 바깥으로 전달한다. 즉, yield from을 한 번 사용하여 값을 세 번 바깥으로 전달한다. 따라서 next()를 세 번 호출할 수 있다.

>>> g = number_generator()
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)    # StopIteration 예외 발생

yield from에 제네레이터 객체 지정

def number_generator(stop):
    n = 0
    while n < stop:
        yield n
        n += 1

def three_genertor():
    yield from number_generator(3)

for i in three_genertor():
    print(i)

number_generator는 매개변수로 받은 숫자 직전까지 숫자를 만들어낸다. 그리고 three_generator에서는 yield from number_generator(3)과 같이 yield from에 제네레이터 객체를 지정했다.

number_generator(3)은 숫자를 세 개를 만들어내므로 yield from number_generator(3)은 숫자를 세 번 바깥으로 전달한다. 따라서 for 반복문에 three_generator()를 사용하면 숫자를 세 번 출력한다.

Comments