상위 목록: 하위 목록: 작성 날짜: 읽는 데 25 분 소요

생성자(Generator)

생성자(Generator)반복자(Iterator)를 생성해주는 함수입니다.

yield 키워드를 활용해 반복자 형태를 구현할 수 있습니다.

함수 내부에 yield가 존재하면, 함수 전체를 생성자(Generator)로 간주합니다.

일반적인 함수는 스코프(Scope)내의 코드를 모두 실행시킨 후, 소멸합니다.

하지만, 생성자 함수는 스코프 내에서 일시 중지해가며 실행시킬 수 있습니다.

즉, 함수가 실행되고 있는 도중에 중지하고 값을 수정할 수도 있습니다.



생성자(Generator) 사용하기

def generator():
    data = [0]

    print("First")
    data.append(1)
    yield data

    print("Second")
    data.append(2)
    yield data

    print("Third")


gen = generator()
print(next(gen))
print("———")
print(next(gen))
print("———")
print(next(gen, "END"))
print("———")
print(next(gen, "END"))
print("———")
결과
First
[0, 1]
———
Second
[0, 1, 2]
———
Third
END
———
END
———

생성자(Generator)yield 키워드를 통해 구현할 수 있습니다.

반복자(Iterator)를 사용해 생성자 객체를 반환합니다.

next() 함수를 실행시킬 때, yield 키워드 구문에서 일시 중지하게 됩니다.

함수를 초기화하는 것이 아닌 yield 구문에서 중지합니다.

함수 내부의 변수멤버는 유지되며, 변경이 가능합니다.

yield 키워드가 더 이상 남아있지 않을 때에는 남은 구문을 모두 출력하며, StopIteration 예외를 발생시킵니다.

하지만, next() 함수에 StopIteration 예외 발생시, 반환할 값을 설정할 수 있습니다.

StopIteration 예외 발생 이후에도 반환한다면, 더 이상 남아있는 구문이 없어 END 값만 반환합니다.



반복 가능한(iterable) 형식 반환하기

def generator():
    value = [1, 2, 3]
    yield value


gen = generator()
print(list(gen))
결과
[[1, 2, 3]]

yield 키워드를 통해 반복 가능한(iterable) 객체를 반환할 경우, 값이 묶여 반환됩니다.

이를 해결하기 위해 from 키워드를 추가해 한 번에 반환할 수 있습니다.


def generator():
    value = [1, 2, 3]
    yield from value


gen = generator()
print(list(gen))
결과
[1, 2, 3]

yield from iterable 형태로 값을 반환하면, 객체를 두 번 묶지 않고 반환할 수 있습니다.

이를 통해 불필요한 반복문이나 객체를 한 번 더 푸는 작업을 진행하지 않아도 됩니다.



코루틴(Coroutine)

from itertools import count


def coroutine():

    for c in count():
        value = yield c
        print("Value:{}, Count:{}".format(value, c))


cor = coroutine()
cor.send(None)
cor.send("A")
cor.send(200)
cor.send(50)
결과
Value:A, Count:0
Value:200, Count:1
Value:50, Count:2

여태까지 사용하던 함수는 서브루틴(Subroutine) 함수로, 진입 지점이 하나이며 return 키워드를 통해 종료되었습니다.

하지만, 생성자(Generator)는 함수 실행 도중에 일시 정지를 할 수 있으므로, 일시 중지한 시점에서 값을 추가로 입력할 수 있습니다.

즉, 진입 지점이 여러 개로 늘어나게 되며 값을 주고 받을 수도 있게됩니다. 이를 코루틴(Coroutine)이라 합니다.

코루틴 함수에서 value = yield c의 형태로 사용한다면, 생성자 함수로 값을 보낼 수 있습니다.

여기서 값을 보내기 위해서는 함수를 초기화해야합니다.

coroutine() 함수가 생성된 이후, cor.send(None) 또는 next(cor)를 활용해 생성자 함수를 초기화합니다.

이후, cor.send() 메서드를 통해 값을 전달할 수 있습니다.

앞서, yield 구문을 만나면 해당 구문에서 일시 정지하였지만, 코루틴 함수는 해당 블록(for문 내부)을 모두 실행시킵니다.

그러므로, print() 함수가 실행됩니다. 즉, 값을 전달하고 블록을 모두 실행시킬 수 있습니다.


from itertools import count


def coroutine():

    for c in count():
        value = yield c
        print("next() - Value:{}, Count:{}".format(value, c))
        yield value
        print("send() - Value:{}, Count:{}".format(value, c))


cor = coroutine()
next(cor)
cor.send("A")
next(cor)
cor.send(200)
next(cor)
cor.send(50)
next(cor)
결과
next() - Value:A, Count:0
send() - Value:A, Count:0
next() - Value:200, Count:1
send() - Value:200, Count:1
next() - Value:50, Count:2
send() - Value:50, Count:2

valueyield c를 할당한 이후에 yield 키워드를 추가해 반이중 방식(half-duplex) 형태와 흡사한 함수를 구현할 수 있습니다.

cor.send() 메서드를 통해 값을 전달하는 방식과 동일하지만, 함수 내부에 yield value가 추가되어 주고 받는 형식이 됩니다.

send() 메서드에서는 다음 번째 yield value가 만나기 전까지 모든 구문을 실행합니다.

yield value 구문은 next() 함수를 통해 넘어갈 수 있었습니다.

값을 다시 반환받기 위해서는 next(cor)를 활용합니다.

즉, 값을 보낼때는 cor.send()를 사용하며, 값을 받을때는 next()를 사용한다 볼 수 있습니다.

  • Tip : cor.send()의 반환값은 value 값을 반환하며, next(cor)의 반환값은 c 값을 반환합니다.



오류 발생(throw)

from itertools import count


def coroutine():

    for c in count():
        try:
            value = yield c
            print("next() - Value:{}, Count:{}".format(value, c))
            yield value
            print("send() - Value:{}, Count:{}".format(value, c))
        except ValueError:
            print("Error", c)


cor = coroutine()
next(cor)
cor.send("A")
cor.throw(ValueError, "오류가 발생했습니다.")
cor.send(200)
cor.throw(ValueError, "오류가 발생했습니다.")
cor.throw(ValueError, "오류가 발생했습니다.")
결과
next() - Value:A, Count:0
Error 0
next() - Value:200, Count:1
Error 1
Error 2

코루틴(Coroutine) 도중 오류를 강제로 발생시켜, 예외 처리를 진행할 수 있습니다.

cor.throw()를 통해, 강제로 오류를 발생시킬 수 있습니다.

오류를 발생시켜도, 진행 중인 Count의 값은 증가하며, next() 함수와 동일한 기능을 합니다.

즉, 입력 → 출력, 입력 → 오류, 입력 → 출력 구조의 형태로도 구현이 가능합니다.

도중에 오류를 발생시키는 이유는, 함수 내부적으로 오류를 처리하는 것이 아닌 외부에서 처리하기 위함입니다.

이를 통해 특정 오류 상황을 알릴 수 있으며, 코드가 더 간결해질 수 있습니다.



생성자(Generator) 종료하기

from itertools import count


def coroutine():

    for c in count():
        try:
            value = yield c
            print("next() - Value:{}, Count:{}".format(value, c))
            yield value
            print("send() - Value:{}, Count:{}".format(value, c))
        except ValueError:
            print("Error", c)


cor = coroutine()
next(cor)
cor.send("A")
cor.close()
print(next(cor, "END"))
결과
next() - Value:A, Count:0
END

현재 생성자(Generator)는 무한히 반복되는 구조입니다.

코드 상에 종료 구문이 없으므로 끝이 존재하지 않습니다.

하지만, close() 함수를 통해 강제로 생성자를 종료할 수 있습니다.

cor.close()가 호출된 이후 부터 StopIteration 예외를 발생시킵니다.

즉, 더 이상 참조할 수 없는 형태로 변경합니다.

댓글 남기기