"수율"이라는 단어에는 두 가지 의미가 있습니다 : 무언가를 생산하는 것 (예 : 옥수수를 생산하는 것)과 누군가 / 다른 것을 계속하게하는 것 (예 : 보행자에게 양보하는 자동차). 두 정의 모두 파이썬에 적용됩니다.yield
키워드에 . 생성기 함수를 특별하게 만드는 것은 일반 함수와 달리 생성자 함수를 종료하지 않고 일시 중지하는 동안 호출자에게 값을 "반환"할 수 있다는 것입니다.
"왼쪽"끝과 "오른쪽"끝이있는 양방향 파이프의 한쪽 끝으로 생성기를 상상하는 것이 가장 쉽습니다. 이 파이프는 생성기 자체와 생성기 함수의 본문 사이에 값이 전송되는 매체입니다. 파이프의 각 끝에는 두 개의 작업이 있습니다.. 파이프 의 다른 끝이 값을 누를 때까지 차단하고 푸시 된 값을 반환합니다. 런타임에 실행은 파이프의 한쪽에있는 컨텍스트 사이에서 앞뒤로 튀어 오릅니다. 각 쪽은 다른쪽에 값을 보낼 때까지 실행됩니다.이 시점에서 중지되고 다른 쪽이 실행되도록하고 반대쪽이 멈추고 다시 시작합니다. 즉, 파이프의 각 끝은 값을받는 순간부터 값을 보내는 순간까지 이어집니다.push
이 값은 파이프의 다른 쪽 끝이 값을 가져올 때까지 값을 보내고 차단하며 아무것도 반환하지 않습니다. 과pull
파이프는 기능적으로 대칭이지만 일반적 으로이 답변에서 정의하고 있습니다. 왼쪽 끝은 생성기 함수의 본문 내에서만 사용할 수 있으며 yield
키워드 를 통해 액세스 할 수 있으며 오른쪽 끝 은 생성기이며 발전기의 send
기능. 단일 파이프의 각 단부에 대한 인터페이스로서, yield
그리고 send
이중 임무를 수행 및 그들 각각의 푸시 풀 값 모두에 / 파이프의 단부에서 yield
우측으로 가압하면서 좌측 당기기 send
반대한다. 이 이중 의무는와 같은 문장의 의미를 둘러싼 혼란의 핵심입니다 x = yield y
. 실연 yield
과 send
이 명시 적 푸시 / 풀 단계로 그들의 의미는 훨씬 더 명확하게합니다 :
g
생성기가 있다고 가정 하십시오. g.send
파이프의 오른쪽 끝을 통해 왼쪽으로 값을 푸시합니다.
g
일시 정지 컨텍스트 내에서 실행 하여 생성기 함수의 본문을 실행할 수 있습니다.
- 밀린 값 은 파이프의 왼쪽 끝에서 왼쪽
g.send
으로 당겨 yield
집니다. 에서 x = yield y
, x
끌어온 값이 지정됩니다.
- 다음 행을 포함 할 때까지 생성기 함수의 본문 내에서 실행이 계속됩니다
yield
.
yield
파이프의 왼쪽 끝을 통해 오른쪽으로 값을 밀어 올립니다 g.send
. 이어 x = yield y
, y
파이프를 통해 우측으로 가압된다.
- 제너레이터 기능의 바디 내에서 실행이 일시 중지되어 외부 스코프가 중단 된 위치에서 계속 될 수 있습니다.
g.send
다시 시작하여 값을 가져 와서 사용자에게 반환합니다.
- 때
g.send
다음라고하며, 1 단계로 돌아갑니다.
순환하는 동안,이 절차가 시작을 가지고 : 때 g.send(None)
- 무엇 인 next(g)
에 대한 짧은 - 처음이라고합니다 (이 아닌 다른 뭔가를 통과하는 것은 불법입니다 None
처음에 send
호출). 그리고 yield
생성기 함수의 본문에 더 이상 설명 이 없을 때 끝날 수 있습니다 .
그 yield
진술 (또는 더 정확하게는 생성기)을 그렇게 특별 하게 만드는 이유를 알고 있습니까? measly return
키워드 와 달리 yield
호출자에게 값을 전달하고 존재하는 기능을 종료하지 않고도 호출자로부터 값을받을 수 있습니다! (물론 함수 또는 제너레이터를 종료하려는 경우 return
키워드를 사용하는 것이 편리합니다 .) yield
명령문이 발생하면 제너레이터 함수는 일시 정지 한 다음 왼쪽으로 돌아 오는 즉시 다시 선택합니다. 다른 값을 보내면 꺼집니다. 그리고 send
외부에서 발전기 기능 내부와 통신하기위한 인터페이스 일뿐입니다.
이 푸시 / 풀 / 파이프 유추를 가능한 한 세분화하려면 1 ~ 5 단계를 제외 yield
하고 send
는 동일한 코인 파이프의 양면 인 홈을 구동하는 다음 의사 코드로 끝납니다 .
right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_end.pause()
left_end.start()
initial_value = left_end.pull()
if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
left_end.do_stuff()
left_end.push(y) # the first half of yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_end.do_stuff()
right_end.push(value2) # the first half of g.send (again, but with a different value)
right_end.pause()
left_end.resume()
x = left_end.pull() # the second half of yield
goto 6
중요한 변화는 우리가 분할해야한다는 것입니다 x = yield y
및 value1 = g.send(value2)
두 문장에 각각 : left_end.push(y)
및 x = left_end.pull()
; 및 value1 = right_end.pull()
및 right_end.push(value2)
. yield
키워드 에는 두 가지 특별한 경우가 있습니다 : x = yield
및 yield y
. 이들에 대한 각각 문법적 있습니다 x = yield None
와 _ = yield y # discarding value
.
파이프를 통해 값이 전송되는 정확한 순서에 대한 자세한 내용은 아래를 참조하십시오.
다음은 위의 다소 긴 구체적인 모델입니다. 우선, 먼저 임의 생성기 주목해야한다 g
, next(g)
정확히 동일하다 g.send(None)
. 이를 염두에두고 우리는 send
작동 방식 에만 초점을 맞추고 발전기를 발전시키는 것에 대해서만 이야기 할 수 있습니다 send
.
우리가 가지고 있다고 가정
def f(y): # This is the "generator function" referenced above
while True:
x = yield y
y = x
g = f(1)
g.send(None) # yields 1
g.send(2) # yields 2
이제 f
다음과 같은 일반적인 (비 생성기) 기능에 대한 대략적인 설탕 제거 정의 :
def f(y):
bidirectional_pipe = BidirectionalPipe()
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
def impl():
initial_value = left_end.pull()
if initial_value is not None:
raise TypeError(
"can't send non-None value to a just-started generator"
)
while True:
left_end.push(y)
x = left_end.pull()
y = x
def send(value):
right_end.push(value)
return right_end.pull()
right_end.send = send
# This isn't real Python; normally, returning exits the function. But
# pretend that it's possible to return a value from a function and then
# continue execution -- this is exactly the problem that generators were
# designed to solve!
return right_end
impl()
이 변환에서 다음이 발생했습니다 f
.
- 구현을 중첩 함수로 옮겼습니다.
left_end
중첩 함수에 right_end
의해 액세스되고 외부 스코프에 의해 반환 및 액세스되는 양방향 파이프를 생성했습니다 right_end
. 생성기 객체로 알고 있습니다.
- 중첩 된 함수 내, 우리가하는 첫 번째 일은 검사입니다
left_end.pull()
입니다 None
과정에서 밀어 값을 소모.
- 중첩 함수 내에서 명령문
x = yield y
은 두 줄로 대체되었습니다 : left_end.push(y)
및 x = left_end.pull()
.
- 에 대한
send
함수를 정의했습니다.이 함수 는 이전 단계에서 명령문을 right_end
바꾼 두 줄 x = yield y
에 해당합니다.
이 환상의 세계에서 함수가 돌아온 후에도 계속 될 수 있고 g
할당 된 right_end
다음 impl()
호출됩니다. 위의 예에서, 우리는 실행을 한 줄씩 따라야했지만, 대략 다음과 같은 일이 일어날 것입니다 :
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
y = 1 # from g = f(1)
# None pushed by first half of g.send(None)
right_end.push(None)
# The above push blocks, so the outer scope halts and lets `f` run until
# *it* blocks
# Receive the pushed value, None
initial_value = left_end.pull()
if initial_value is not None: # ok, `g` sent None
raise TypeError(
"can't send non-None value to a just-started generator"
)
left_end.push(y)
# The above line blocks, so `f` pauses and g.send picks up where it left off
# y, aka 1, is pulled by right_end and returned by `g.send(None)`
right_end.pull()
# Rinse and repeat
# 2 pushed by first half of g.send(2)
right_end.push(2)
# Once again the above blocks, so g.send (the outer scope) halts and `f` resumes
# Receive the pushed value, 2
x = left_end.pull()
y = x # y == x == 2
left_end.push(y)
# The above line blocks, so `f` pauses and g.send(2) picks up where it left off
# y, aka 2, is pulled by right_end and returned to the outer scope
right_end.pull()
x = left_end.pull()
# blocks until the next call to g.send
위의 16 단계 의사 코드에 정확하게 매핑됩니다.
오류가 전파되는 방법 및 발생기 끝에 도달 할 때 발생하는 상황 (파이프가 닫힘)과 같은 다른 세부 사항이 있지만이를 사용하면 기본 제어 흐름이 작동하는 방식이 명확해야합니다 send
.
이 동일한 설탕 제거 규칙을 사용하여 두 가지 특별한 경우를 살펴 보겠습니다.
def f1(x):
while True:
x = yield x
def f2(): # No parameter
while True:
x = yield x
대부분의 경우와 같은 방식으로 설탕을 제거합니다 f
. 단, 차이점은 yield
문장이 어떻게 변환 되는지입니다 .
def f1(x):
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
def f2():
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
첫 번째로 전달 된 값 f1
은 처음에 푸시 (수율) 된 다음 당겨진 (송신 된) 모든 값이 다시 푸시 (수율)됩니다. 제에서, x
첫 회 온 (아직) 값이없는 push
이되도록 UnboundLocalError
상승된다.