python 설명 - "yield"키워드는 무엇을합니까?





파이썬 제너레이터 (25)


수율은 당신에게 발전기를 제공합니다.

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

보시다시피, 첫 번째 경우 foo는 전체 목록을 메모리에 한 번에 보유합니다. 5 가지 요소가있는 목록에는 큰 문제가 아니지만 500 만 개의 목록을 원한다면 어떻게 될까요? 이것은 거대한 메모리 먹는 사람 일뿐만 아니라 함수가 호출 될 때 빌드하는 데 많은 시간을 소모합니다. 두 번째 경우에는 막대가 생성기를 제공합니다. 생성자는 iterable입니다. 즉 for 루프 등에서 사용할 수 있지만 각 값은 한 번만 액세스 할 수 있습니다. 모든 값은 동시에 메모리에 저장되지 않습니다. 생성자 객체는 마지막으로 호출 할 때 루핑 된 위치를 "기억"합니다. 이렇게하면 반복 가능한 반복을 사용하여 500 억까지 계산하면 500 억 개까지 계산할 필요가 없습니다. 즉시 계산할 500 억 개의 숫자를 저장합니다. 다시 말하지만, 이것은 꽤 고안된 예입니다.정말로 itertools를 사용한다면 아마도 500 억에 달할 것입니다. :)

이것은 발전기의 가장 간단한 사용 사례입니다. 앞에서 말했듯이, 스택 변수를 사용하는 대신 yield를 사용하여 호출 스택을 통해 물건을 밀어 올리는 효율적인 순열을 작성하는 데 사용할 수 있습니다. 생성기는 특수 트리 탐색 및 기타 모든 방법에 사용될 수도 있습니다.

파이썬에서 yield 키워드를 사용하는 것은 무엇입니까? 그것은 무엇을합니까?

예를 들어,이 코드 1 을 이해하려고합니다.

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

그리고 이것은 호출자입니다.

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

_get_child_candidates 메소드가 호출되면 어떻게됩니까? 목록이 반환 되었습니까? 단일 요소? 다시 부르니? 후속 통화는 언제 중단됩니까?

1.이 코드는 Jochen Schulz (jrschulz)가 작성한 것으로, 누가 통계 공간을위한 훌륭한 Python 라이브러리를 만든 것입니다. 이것은 전체 소스에 대한 링크입니다 : Module mspace .




모든 답변과 마찬가지로 yield시퀀스 생성기를 만드는 데 사용됩니다. 이것은 동적으로 일부 시퀀스를 생성하는 데 사용됩니다. 예를 들어 네트워크에서 한 줄씩 파일을 읽는 동안 yield다음과 같이이 함수를 사용할 수 있습니다 .

def getNextLines():
   while con.isOpen():
       yield con.read()

코드에서 다음과 같이 사용할 수 있습니다.

for line in getNextLines():
    doSomeThing(line)

실행 제어 전송 gotcha

foryield가 실행되면 실행 제어가 getNextLines ()에서 루프 로 전송됩니다 . 따라서 getNextLines ()가 호출 될 때마다 마지막으로 일시 중지 된 시점부터 실행이 시작됩니다.

따라서 간단히 말해 다음 코드를 가진 함수

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

인쇄 할 것이다.

"first time"
"second time"
"third time"
"Now some useful value 12"



yield 키워드는 파이썬에서 무엇을합니까?

답변 개요 / 요약

  • yield 있는 함수는 호출되면 Generator 반환합니다.
  • 생성자는 iterator 프로토콜 을 구현하기 때문에 반복자이므로 iterator 프로토콜 을 반복 할 수 있습니다.
  • 생성기는 정보를 전송 하여 개념적으로 코 루틴으로 만들 수 있습니다.
  • 파이썬 3에서는 한 방향에서 생성자를 양방향으로 위임 할 수 있습니다.
  • (부록은 1 위를 포함 해 몇 가지 해답을 제시하고 발전기에서의 return 사용에 대해 논의합니다.)

발전기 :

yield 는 함수 정의의 내부에서만 유효하며 함수 정의 yield 를 포함하면 생성기가 반환됩니다.

생성자에 대한 아이디어는 다른 언어 (각주 1 참조)에서 다양하게 구현됩니다. 파이썬의 Generators에서는 코드 실행이 yield 시점에서 frozen 됩니다. 생성기가 호출되면 (메서드는 아래에서 설명 함) 실행이 다시 시작된 후 다음 yield 때 고정됩니다.

yield__iter__next (Python 2) 또는 __next__ (Python 3)의 두 가지 메소드로 정의되는 반복자 프로토콜구현 하는 쉬운 방법을 제공합니다. 이 두 메소드는 객체를 반복자로 만들어 collections 모듈에서 Iterator Abstract Base Class로 유형을 검사 할 수 있습니다.

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

생성자 유형은 반복자의 하위 유형입니다.

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

필요한 경우 다음과 같이 입력 확인을 할 수 있습니다.

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Iterator 의 기능은 일단 고갈되면 재사용하거나 재설정 할 수 없다는 것입니다.

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

다시 기능을 사용하려면 다른 것을 만들어야합니다 (각주 2 참조).

>>> list(func())
['I am', 'a generator!']

예를 들어 프로그래밍 방식으로 데이터를 산출 할 수 있습니다.

def func(an_iterable):
    for item in an_iterable:
        yield item

위의 간단한 생성기는 또한 파이썬 3.3 (파이썬 2에서 사용 가능하지 않음)의 아래 yield from 다음 yield from 같습니다. yield from :

def func(an_iterable):
    yield from an_iterable

그러나 yield from 사용하면 하위 생성자에게 위임 할 수 있습니다. 하위 coroutines를 사용한 공동 위임에 대한 다음 절에서 설명합니다.

코 루틴 :

yield 는 데이터가 생성기로 전송되도록하는 표현식을 형성합니다 (각주 3 참조)

다음은 생성자에게 전송 된 데이터를 가리키는 received 변수를 기록한 예입니다.

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

먼저 builtin 함수를 사용하여 생성자를 큐업해야한다. 사용중인 Python의 버전에 따라 적절한 next 또는 __next__ 메소드를 호출합니다.

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

이제 생성기로 데이터를 보낼 수 있습니다. ( 발신 Nonenext 전화와 동일 합니다.) :

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

yield from 가진 Sub-Coroutine에 협력적인 대표단

이제 yield from 은 Python 3에서 사용할 수 있습니다. 이렇게하면 coroutines를 subcoroutine에 위임 할 수 있습니다.

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

이제는 하위 생성기에 기능을 위임 할 수 있으며 위와 같이 생성기에서 사용할 수 있습니다.

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

yield from 의 정확한 의미에 대한 자세한 내용은 PEP 380을 참조하십시오.

다른 메소드 : close 및 throw

close 메서드는 함수 실행이 중단 된 지점에서 GeneratorExit 를 발생시킵니다. 이것은 또한 __del__ 의해 호출 될 것이므로 GeneratorExit 를 처리하는 곳에 정리 코드를 넣을 수 있습니다 :

>>> my_account.close()

또한 생성자에서 처리되거나 사용자에게 전파 될 수있는 예외를 throw 할 수 있습니다.

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

결론

나는 다음 질문에 대한 모든면을 다뤘다고 믿는다.

yield 키워드는 파이썬에서 무엇을합니까?

yield 이 많은 것으로 나타났습니다. 나는 이것에 훨씬 더 철저한 예를 더할 수있을 것이라고 확신한다. 더 많은 것을 원하거나 건설적인 비평을하고 싶다면, 아래에 설명하여 알려주십시오.

충수:

최고 / 수락 된 답변 **

  • 예를 들어 목록을 사용하면 iterable을 만드는 것에 혼란 스럽습니다. 위의 내 참조를 참조하십시오 : 그러나 iterable에는 반복기를 반환하는 __iter__ 메서드가 있습니다. iteratorStopIteration 때까지 for 루프에 의해 암시 적으로 호출되는 .next (Python 2 또는 .__next__ (Python 3) 메소드를 제공하며 일단 수행하면 계속 그렇게합니다.
  • 그런 다음 발전기 표현식을 사용하여 발전기가 무엇인지 설명합니다. 생성기는 반복자 를 만드는 데 편리한 방법 일 뿐이므로 문제를 혼란스럽게하고 아직 yield 을 얻지 못했습니다.
  • 발전기 고갈 제어에서 그는 대신 .next 메소드를 호출하는데, 대신 내장 함수를 사용해야합니다. 코드가 파이썬 3에서 작동하지 않기 때문에 이것은 간접적 인 지시의 적절한 레이어가 될 것입니다.
  • Itertools? 이것은 yield 전혀 관련이 없습니다.
  • yield 제공하는 메소드에 대한 논의는 Python 3 새로운 기능을 제공하지 않습니다 . 맨 위 / 허용 된 대답은 매우 불완전한 대답입니다.

발전기 표현 또는 이해력에서의 yield 을 제시하는 답의 비판.

문법은 현재 목록 이해에서 모든 표현을 허용합니다.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

수율은 표현식이기 때문에, 특히 유익한 사례가 없다는 사실에도 불구하고 이해력이나 생성자 표현에 사용하는 것이 흥미로운 것으로 유혹되었습니다.

CPython 핵심 개발자는 해당 허용치를 사용하지 않는 것에 대해 논의하고 있습니다. 메일 링리스트의 관련 게시물은 다음과 같습니다.

2017 년 1 월 30 일 19:05에 Brett Cannon은 다음과 같이 썼습니다.

Sun, 2017 년 1 월 29 일 16:39 Craig Rodrigues는 다음과 같이 썼습니다.

나는 어느 쪽의 방법으로도 괜찮다. 파이썬 3에서와 같은 방식으로 남겨 두는 것은 좋지 않습니다.

내 투표는 구문에서 기대하는 바를 얻지 못하기 때문에 SyntaxError가됩니다.

현재 행동에 의존하는 코드는 유지 보수하기에 너무 똑똑하기 때문에 결국 우리가 끝내는 것이 합리적인 곳이라는 데 동의합니다.

거기에 도달하는 측면에서, 우리는 아마도 원할 것입니다 :

  • 3.7의 SyntaxWarning 또는 DeprecationWarning
  • 2.7.x에서의 Py3k 경고
  • SyntaxError in 3.8

건배, 닉.

- 닉 코 그란 | gmail.com의 ncoghlan | 오스트레일리아 브리즈번

더구나 결코 좋은 아이디어가 되지 못하는 방향을 가리키는 것처럼 보이는 현저한 이슈 (10544) 가 있습니다 (Python으로 작성된 PyPy는 이미 구문 경고를 발생시키고 있습니다).

CPython의 개발자가 우리에게 달리 말할 때까지 결론적으로 말하자면, 생성기 표현식이나 이해력에 yield 를 넣지 마십시오.

생성기의 return

파이썬 2에서 :

생성자 함수에서 return 문은 expression_list 를 포함 할 수 없습니다. 이러한 맥락에서 베어 return 은 제너레이터가 완료되었음을 나타내며 StopIteration 을 발생시킵니다.

expression_list 는 기본적으로 쉼표로 구분 된 여러 expression_list 입니다. 기본적으로 Python 2에서는 반환 값으로 생성기를 중지 할 수 있지만 값을 반환 할 수는 없습니다.

파이썬 3 :

생성기 함수에서 return 문은 생성기가 완료되었음을 나타내며 StopIteration 을 발생시킵니다. 반환 값 (있는 경우)은 StopIteration 을 구성하는 인수로 사용되며 StopIteration.value 특성이됩니다.

각주

  1. CLU, Sather 및 Icon 언어는 Python에 발전기 개념을 도입하려는 제안에서 참조되었습니다. 일반적인 개념은 함수가 내부 상태를 유지하고 사용자가 필요에 따라 중간 데이터 포인트를 산출 할 수 있다는 것입니다. 이것은 일부 시스템에서는 사용할 수없는 파이썬 스레딩을 포함하여 다른 접근법보다 성능우수하다고 약속했습니다 .

  2. 예를 들어, 이것은 xrange( rangePython 3에서) 객체 Iterator는 재사용 할 수 있기 때문에 iterable이더라도 s 가 아닙니다 . 리스트와 마찬가지로 그들의 __iter__메소드는 반복자 객체를 반환한다.

  3. yield원래 코드 블록에있는 줄의 시작 부분에만 나타날 수 있다는 것을 의미하는 성명서로 소개되었습니다. 이제 yieldyield 식을 만듭니다. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt 이 변경 사항은 사용자가 발전기로 데이터를 보낼 수 있도록 제안 되었습니다. 데이터를 보내려면 데이터를 무언가에 할당 할 수 있어야하며, 이에 대한 진술은 제대로 작동하지 않습니다.




다음 yield은 fibonacci 시리즈를 계산하기 위한 간단한 기반 접근 방식입니다.

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

이것을 REPL에 입력하고 전화를 걸면 신비한 결과를 얻게됩니다.

>>> fib()
<generator object fib at 0x7fa38394e3b8>

왜냐하면 yield파이썬 에 신호를 보내서 생성자 를 생성해야하기 때문입니다. 즉, 필요에 따라 값을 생성하는 객체입니다.

그렇다면이 값들은 어떻게 생성합니까? 이것은 내장 함수를 사용하여 직접 수행 next하거나 간접적으로 값을 사용하는 구조로 공급 하여 수행 할 수 있습니다 .

내장 next()함수를 사용하여 .next/ 를 직접 호출 __next__하여 생성기가 값을 생성하도록합니다.

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

간접적 fib으로 for루프를 만들거나 list이니셜 라이저, tuple이니셜 라이저 또는 값을 생성 / 생성하는 객체를 제공하는 경우 더 이상 값을 생성 할 수 없을 때까지 생성기를 "소비"합니다. :

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

마찬가지로, tuple초기화 프로그램에서 :

>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

발전기는 게으른 의미에서 함수와 다릅니다. 그것은 지역의 상태를 유지하고 필요할 때 언제든지 재개 할 수있게함으로써이를 수행합니다.

처음 호출 할 때 fib:

f = fib()

파이썬은 함수를 컴파일하고, yield키워드를 발견 하고 단순히 생성기 객체를 반환합니다. 그것은별로 도움이되지 않습니다.

그런 다음 직접 또는 간접적으로 첫 x 째 값을} 성하도록 요청하면, 발견 한 모든 명령.을 실행하고 yield, a를 만날 때까지 제공된 값을 다시 산출 yield하고 일시 정지합니다. 이것을 더 잘 보여주는 예제를 위해, 몇 가지 print호출을 사용합시다 ( print "text"파이썬 2 에서 if로 대체하십시오 ) :

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

이제 REPL에 입력하십시오.

>>> gen = yielder("Hello, yield!")

값을 생성하는 명령을 기다리는 생성기 객체가 있습니다. next인쇄물의 사용 및보기 :

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

인용되지 않은 결과는 인쇄 된 것입니다. 인용 된 결과는 리턴 된 것 yield입니다. next다시 전화 하세요.

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

생성기는 일시 중지되었으며 yield value거기 에서 다시 시작 되었음을 기억합니다 . 다음 메시지가 인쇄되고 yield중지 할 문에 대한 검색이 다시 수행됩니다 ( while루프 때문에 ).




yield마치 return제너레이터처럼 당신이 말한 것을 되돌려줍니다. 차이점은 다음에 생성기를 호출 할 때 실행이 yield명령문 의 마지막 호출에서 시작한다는 것입니다. 반환과 달리 스택 프레임은 출력량이 발생할 때 정리되지 않지만 컨트롤은 호출자에게 다시 전송되므로 다음 번에 함수를 다시 시작할 때 상태가 다시 시작됩니다.

코드의 경우 함수 get_child_candidates는 반복자처럼 작동하므로 목록을 확장하면 한 번에 한 요소 씩 새 목록에 추가됩니다.

list.extend소진 될 때까지 반복기를 호출합니다. 게시 한 코드 샘플의 경우 튜플을 반환하고 목록에 추가하는 것이 훨씬 명확합니다.




yield 키워드는 두 가지 간단한 사실로 축소되었습니다.

  1. 컴파일러가 함수 내에서 yield 키워드를 발견하면 해당 함수는 더 이상 return 문을 통해 반환되지 않습니다. 대신 생성기라고하는 게으른 "보류 목록"개체를 즉시 반환합니다.
  2. 생성기는 반복 가능합니다. 반복 가능 이란 무엇입니까? 특정 순서로 각 요소를 방문하기위한 프로토콜내장list 또는 set 또는 range 또는 견본보기와 같은 것입니다.

요컨대 , 생성기는 게으르고 점진적으로 보류 된 목록 이며 yield 문을 사용하면 함수 표기법을 사용 하여 생성기가 점차적으로 침을 뱉어 야 하는 목록 값을 프로그래밍 할 수 있습니다 .

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

파이썬의 range 와 같은 makeRange 함수를 정의합시다. makeRange(n) 호출 makeRange(n) 반환 :

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

생성자가 보류중인 값을 즉시 반환하도록하려면 list() 반복 가능한 것처럼 list() 전달할 수 있습니다.

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

예를 "목록을 반환하는 것"과 비교하면

위의 예는 추가하고 리턴하는리스트를 작성하는 것으로 간주 할 수 있습니다.

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

하나의 큰 차이점이 있습니다. 마지막 절을 참조하십시오.

발전기 사용 방법

iterable은 목록 이해력의 마지막 부분이며 모든 생성자는 반복 가능하므로 자주 사용됩니다.

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

생성기에 대한 더 나은 느낌을 얻으려면 itertools 모듈을 가지고 놀 수 있습니다 (보증 할 때 chain 아닌 chain.from_iterable 을 사용해야합니다). 예를 들어 생성자를 사용하여 itertools.count() 와 같은 무한 길이의 지연 목록을 구현할 수도 있습니다. 당신은 자신의 def enumerate(iterable): zip(count(), iterable) 구현할 수 있습니다 def enumerate(iterable): zip(count(), iterable) 또는 while 루프에서 yield 키워드를 사용합니다.

참고 : 생성자는 실제로 코 루틴 또는 비 결정적 프로그래밍이나 다른 우아한 것들을 구현하는 것과 같이 더 많은 것들에 사용될 수 있습니다. 그러나 내가 여기에 제시하는 "게으른 목록"관점은 당신이 찾을 수있는 가장 보편적 인 사용법입니다.

무대 뒤에서

이것이 "파이썬 반복 프로토콜"의 작동 방식입니다. 즉, list(makeRange(5)) 를 수행 할 때 무슨 일이 벌어지고 있는지입니다. 이것은 이전에 "게으른 점진적 목록"으로 설명했습니다.

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

내장 함수 next() 는 "반복 프로토콜"의 일부이며 모든 반복자에서 발견되는 객체 .next() 함수를 호출합니다. next() 함수 (및 반복 프로토콜의 다른 부분 next() 수동으로 사용하여 일반적으로 가독성을 희생하면서 멋진 작업을 구현할 수 있으므로 그렇게하지 않는 것이 좋습니다.

사소한 점

일반적으로 대부분의 사람들은 다음과 같은 구분에 관심이 없으며 아마도 여기에서 읽지 않으려합니다.

파이썬에서 말하는 iterable 은리스트 [1,2,3] 처럼 "for-loop의 개념을 이해하는"객체이고, 반복자[1,2,3].__iter__() 와 같은 [1,2,3].__iter__() 요청 된 for-loop의 특정 인스턴스이다. [1,2,3].__iter__() . 생성기 는 작성된 방식을 제외하고 모든 반복기와 완전히 동일합니다 (함수 구문 사용).

목록에서 반복기를 요청하면 새로운 iterator가 작성됩니다. 그러나 반복자에서 반복자를 요청하면 (거의하지 않을 것입니다), 그것은 단지 자신의 복사본을 제공합니다.

따라서, 당신이 이런 일을하지 못하는 경우는 드물 것입니다 ...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... 생성자가 반복자 임을 기억하십시오. 즉, 일회용입니다. 재사용하고 싶다면 myRange(...) 다시 호출해야합니다. 결과를 두 번 사용해야하는 경우 결과를 목록으로 변환하고 변수 x = list(myRange(5)) 합니다. 복사 가능한 반복자 Python PEP 표준 제안이 연기 되었기 때문에 절대적으로 필요한 생성자 (예 : 무서운 해킹 메타 프로그래밍을 수행하는 사람)를 절대적으로 필요로하는 사용자는 itertools.tee 를 사용할 수 있습니다.




TL, DR

대신 이것 :

def squares_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

이 작업을 수행:

def squares_the_yield_way(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

언제든지 처음부터 목록을 작성하는 yield대신 각 부분을 작성해야합니다.

이것은 수확량을 가진 나의 첫번째 "aha"순간이었다.

yield 말할 수있는 좋은 방법이다.

일련의 물건을 만들다.

같은 행동 :

>>> for square in squares_list(4):
...     print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
...     print(square)
...
0
1
4
9

다른 행동 :

수율은 단일 패스입니다 . 한 번만 반복 할 수 있습니다. 함수에 yield가 있으면 함수를 생성 함수 라고 부릅니다 . 와 iterator 그것을 반환 것입니다. 그것은 드러내고 있습니다. 우리는 컨테이너의 편리함을 잃지 만, 임의로 긴 시리즈의 힘을 얻습니다.

수율은 게으르다 . 계산을 방해한다. 당신이 그것을 호출 할 때 yield가있는 함수는 실제로는 전혀 실행되지 않습니다 . 반환하는 iterator 객체 는 함수의 내부 컨텍스트를 유지하기 위해 magic 을 사용합니다 . next()iterator 를 호출 할 때마다 (for-loop에서 발생) 실행은 다음 yield로 넘어 간다. ( 시리즈를 return올리고 StopIteration종료합니다.)

수율은 다양 합니다. 무한 루프를 수행 할 수 있습니다.

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

다중 패스 가 필요 하고 시리즈가 너무 길지 않은 경우 전화 list()를 걸면 됩니다.

>>> list(squares_the_yield_way(4))
[0, 1, 4, 9]

두 가지 의미가 모두 적용 yield되므로 훌륭한 단어 선택 :

생산량을 제공하거나 제공한다 (농업에서와 같이)

... 시리즈의 다음 데이터를 제공하십시오.

양보하다 - 정치적으로 권력을 쥐거나 양도하다

... 반복자가 진행될 때까지 CPU 실행을 포기합니다.




여기에 무슨 일 yield이 일어 났는지에 대한 정신적 인 이미지 가 있습니다.

스레드가 스택을 가지고있는 것으로 생각하고 싶습니다 (그렇게 구현되지 않았을 때조차도).

정상 함수가 호출되면 로컬 변수를 스택에 넣고 계산을 수행 한 다음 스택을 지우고 반환합니다. 로컬 변수의 값은 다시는 볼 수 없습니다.

yield함수를 사용하여 코드가 실행되기 시작하면 (즉, 함수가 호출 된 후 next()메서드가 호출 되는 생성기 객체를 반환 한 후) 로컬 변수를 스택에 넣고 잠시 동안 계산합니다. 그런 다음 yield명령문에 도달하면 스택의 일부를 지우고 반환하기 전에 로컬 변수의 스냅 샷을 생성자 객체에 저장합니다. 또한 현재 코드 (예 : 특정 yield명령문) 의 현재 위치를 기록합니다 .

따라서 발전기가 걸려있는 고정 된 기능입니다.

next()나중에 when이 호출 되면 함수의 소지품을 스택으로 가져 와서 다시 애니메이트합니다. 함수는 방금 중단 한 지점부터 계산을 계속하면서 추운 곳에서 영원을 보냈다는 사실을 잊어 버립니다.

다음 예제를 비교하십시오.

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

두 번째 함수를 호출하면 첫 번째 함수와 매우 다르게 동작합니다. 그 yield진술은 도달 할 수 없지만, 그것이 어디에나 존재한다면, 그것은 우리가 다루고있는 것의 본질을 변화시킨다.

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

호출 yielderFunction()은 코드를 실행하지 않지만 코드에서 생성기를 제거합니다. ( yielder가독성 을 위해 접두어를 붙이면 좋습니다.)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

gi_codegi_frame냉동 상태가 저장되는 위치 필드가 있습니다. 그것들을 탐구하면서 dir(..), 우리는 위의 우리의 정신 모형이 신뢰할 수 있음을 확인할 수 있습니다.




또 다른 yield사용과 의미가 있습니다 (Python 3.3 이후) :

yield from <expr>

에서 Subgenerator에 위임 구문 - PEP 380 :

생성자가 다른 생성자로 연산의 일부를 위임하는 구문이 제안됩니다. 이를 통해 'yield'를 포함하는 코드 섹션을 계산하여 다른 생성기에 배치 할 수 있습니다. 또한 하위 생성자는 값을 반환 할 수 있으며 값은 위임 생성자에서 사용할 수 있습니다.

새로운 구문은 또한 한 생성기가 다른 생성기에서 생성 된 값을 다시 산출 할 때 최적화를위한 몇 가지 기회를 열어줍니다.

더욱이 this (파이썬 3.5 이후) 소개 할 것이다.

async def new_coroutine(data):
   ...
   await blocking_action()

코 루틴이 일반 발전기와 혼동되는 것을 피하기 위해 (오늘 yield은 둘 다 사용됩니다).




프로그래밍 관점에서 볼 때 반복기는 thunks 로 구현됩니다 .

동시 실행 등을위한 반복자, 생성자 및 스레드 풀을 썽크 (익명 함수라고도 함)로 구현하기 위해 디스패처가있는 클로저 객체로 전송 된 메시지를 사용하고 디스패처는 "메시지"에 응답합니다.

http://en.wikipedia.org/wiki/Message_passing

" next "는 " iter "호출에 의해 작성된 클로저에 전송 된 메시지 입니다.

이 계산을 구현하는 데는 여러 가지 방법이 있습니다. 나는 돌연변이를 사용했으나 현재 가치와 다음 야수를 돌려줌으로써 돌연변이없이 그것을하는 것은 쉽습니다.

다음은 R6RS의 구조를 사용하는 데모이지만, 의미는 파이썬과 완전히 동일합니다. 그것은 동일한 계산 모델이며 파이썬에서 구문을 수정하기 위해서만 변경이 필요합니다.

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->



많은 사람들이 return보다는 오히려 사용 yield하지만 어떤 경우에는 yield보다 효율적이고 쉽게 작업 할 수 있습니다.

여기에 yield가장 적합한 예제 가 있습니다.

반환 (함수에서)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

수율 (기능상)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

함수 호출하기

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

두 함수 모두 똑같은 일을하지만, yield5 행 대신 3 행을 사용하고 걱정할 변수가 하나 더 적습니다.

이것은 코드의 결과입니다.

당신이 볼 수 있듯이 두 기능 모두 똑같은 일을합니다. 유일한 차이점은 return_dates()목록을 yield_dates()제공하고 생성기를 제공한다는 것입니다.

실생활의 예는 한 줄씩 파일을 읽거나 단순히 발전기를 만들고 싶을 때와 같습니다.




모든 중대한 답변, 그러나 초보자에게는 조금 어려움.

나는 당신이 그 return진술 을 배웠다고 가정합니다 .

비유로, return그리고 yield쌍둥이입니다. return'수익률'은 '수익률'을 의미하지만 '수익률'은 '수익률'을 의미하지만 '수익률'

  1. num_list를 사용해보십시오 return.
def num_list(n):
    for i in range(n):
        return i

그것을 실행하십시오 :

In [5]: num_list(3)
Out[5]: 0

보시다시피, 당신은 그 중 하나의 번호 만 얻습니다. return당신이 행복하게 이기고 결코 한 번만 실천하고 그만 두지 마십시오.

  1. 거기 온다. yield

교체 returnyield:

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

자, 당신은 모든 숫자를 얻기 위해 승리합니다.

return어느 실행이 한 번이고 멈추는지를 비교하여 yield계획 한 시간을 실행합니다. 당신이 해석 할 수있는 returnreturn one of them, 및 yield로를 return all of them. 이것이라고 iterable합니다.

  1. yield성명서를 다시 쓸 수있는 한 걸음 더 나아가십시오.return
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

그것은 핵심 yield입니다.

목록 return출력과 개체 yield출력 의 차이점 은 다음과 같습니다.

당신은 항상리스트 객체로부터 [0, 1, 2]를 얻지 만, 오직 '객체 yield출력' 으로부터 그것들을 한 번만 검색 할 수 있습니다 . 그래서, 그것은에 generator표시된 것처럼 새로운 이름 객체를 가진다 Out[11]: <generator object num_list at 0x10327c990>.

결론적으로, 그것을 비유하는 은유로서 :

  • return그리고 yield쌍둥이는
  • list그리고 generator쌍둥이는



많은 답변이 왜 yield발전기를 만드는 데 사용하는지 보여 주지만 더 많은 용도가 yield있습니다. 코 루틴을 만드는 것은 매우 쉽습니다. 두 개의 코드 블록간에 정보를 전달할 수 있습니다. yield발전기를 만드는 데 이미 사용 된 훌륭한 예제를 반복하지 않겠습니다 .

yield다음 코드에서 수행 할 작업을 이해하는 데 도움이되도록 손가락이있는 코드를 통해주기를 추적 할 수 있습니다 yield. 손가락이 닿을 때마다 yielda next또는 a send가 입력 될 때까지 기다려야합니다 . a next가 호출 되면 코드를 추적 할 때까지 코드를 추적합니다. 코드 yield의 오른쪽에있는 코드 yield가 평가되고 호출자에게 반환됩니다. 그러면 기다립니다. next다시 호출 되면 코드를 통해 다른 루프를 수행합니다. 그러나, 당신은 코 루틴에 있습니다 것 yield또한 사용할 수 있습니다 send... 호출자의 값을 보낼 것이다 항복 기능. a send가 주어지면yield전송 된 값을 받으면 왼쪽 사이드로 침을 뱉어 ... 코드를 추적하면 yield다시 히트 할 때까지 진행 됩니다 (마지막에 값을 반환하는 것처럼 next호출됩니다).

예 :

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()



발전기를 반환합니다. 특히 Python에 익숙하지는 않지만 C #의 iterator 블록에 익숙하다면 똑같은 종류라고 생각합니다 .

핵심 아이디어는 컴파일러 / 인터프리터 / 호출기와 관련하여 다음 호출 (계속)을 유지할 수있는 몇 가지 속임수를 사용하며 , 생성기 메서드가 일시 중지 된 것처럼 값을 계속 반환합니다 . 이제 분명히 메서드를 "일시 중지"할 수 없으므로 컴파일러는 현재 위치 및 로컬 변수 등을 기억할 수있는 상태 시스템을 만듭니다. 이는 반복자를 직접 작성하는 것보다 훨씬 쉽습니다.




다음은 간단한 예입니다.

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

산출:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

저는 파이썬 개발자는 아니지만 yield프로그램 흐름의 위치를 ​​파악하고 "yield"위치에서 다음 루프를 시작합니다. 그것은 그 위치에서 기다리고있는 것처럼 보입니다. 그리고 그 직전에 값을 외부로 반환하고 다음에 계속 작동합니다.

그것은 재미 있고 멋진 능력 인 것 같습니다 : D




또 다른 TL, DR

목록 반복자 : 목록next() 의 다음 요소를 반환합니다.

반복자 생성기 : next()즉석에서 다음 요소를 계산합니다 (코드 실행).

수동으로 외부 에서 제어 흐름 을 실행하는 방법으로 yield / generator를 볼 수 있습니다 (반복 루프 한 단계). 호출 next은 복잡하지만 흐름은 복잡합니다.

참고 : 발전기는 일반적인 기능 이 아닙니다 . 로컬 변수 (스택)와 같은 이전 상태를 기억합니다. 자세한 내용은 다른 답변이나 기사를 참조하십시오. 생성기는 한 번만 반복 할 수 있습니다 . 당신은없이 할 수 yield있지만 좋지 않을 것입니다, 그래서 그것은 '아주 좋은'언어 설탕으로 간주 될 수 있습니다.




요약하면이 yield명령문은 함수를 generator원래 함수의 본문을 감싸는 a라는 특수 오브젝트를 생성하는 팩토리로 변환합니다 . generatoriterated가 실행 되면 함수는 다음에 도달 할 때까지 함수를 실행 한 다음 yield실행을 일시 중단하고 전달 된 값으로 평가합니다 yield. 실행 경로가 기능을 종료 할 때까지 각 반복에서이 프로세스를 반복합니다. 예를 들어,

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

단순히 산출물

one
two
three

전력은 시퀀스를 계산하는 루프와 함께 발전기를 사용하는 것으로부터 발생하며, 생성기는 매번 계산의 다음 결과를 '산출'하기 위해 매번 루프를 실행하고, 이런 식으로 즉석에서 목록을 계산합니다. 이점은 메모리입니다 특히 큰 계산을 위해 저장 됨

rangeiterable 범위의 숫자를 생성하는 자신 만의 함수 를 만들고 싶다면 그렇게 할 수 있습니다.

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

이런 식으로 사용하십시오.

for i in myRangeNaive(10):
    print i

그러나 이것은 비효율적입니다. 왜냐하면

  • 한 번만 사용하는 배열을 만듭니다 (이렇게하면 메모리가 낭비됩니다)
  • 이 코드는 실제로 해당 배열을 두 번 반복합니다! :(

다행스럽게도 Guido와 그의 팀은 발전기를 개발할만큼 충분히 관대했기 때문에 우리는 이것을 할 수있었습니다.

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

이제 각 반복에서 호출 된 생성기 next()의 함수는 함수가 종료되고 값을 '산출'하거나 함수의 끝에 도달하는 'yield'문에 도달 할 때까지 함수를 실행합니다. 이 경우 첫 번째 호출 next()에서 yield 문까지 실행하고 'n'을 반환합니다. 다음 호출에서는 increment 문을 실행하고 'while'으로 돌아가 평가하고 참이면 중지하고 'n'을 다시 산출하면 while 조건이 false를 반환하고 생성기가 함수의 끝으로 점프 할 때까지 계속됩니다.




나는 "발전기에 대한 간단한 설명을 위해 Beazley의 'Python : Essential Reference'페이지 19를 읽었습니다. 그러나 많은 사람들이 이미 좋은 설명을 게시했습니다.

또한 yield코 루틴에서는 생성기 기능에서의 이중 용도로 사용할 수 있습니다. 코드 스 니펫과 같은 용도는 아니지만 (yield)함수의 표현식으로 사용할 수 있습니다. 호출자가 메서드를 사용하여 send()메서드에 값을 보내면 다음 (yield)명령문이 발생할 때까지 동시 루틴이 실행 됩니다.

생성기 및 동시 루틴은 데이터 흐름 유형 응용 프로그램을 설정하는 멋진 방법입니다. 나는 그것이 yield함수 내 에서 문장 의 다른 사용에 대해 아는 것이 가치가 있다고 생각했다 .




발전기를 사용하는 방법을 설명하는 많은 중대한 답변 중에서 내가받은 느낌이 들지 않는 한 가지 유형의 대답이 있습니다. 다음은 프로그래밍 언어 이론 답변입니다.

yield파이썬 의 문장은 생성자를 리턴한다. 파이썬에서 생성자는 연속 을 반환하는 함수입니다 (특히 코 루틴의 유형이지만, 연속은 무슨 일이 일어나고 있는지 이해하기위한보다 일반적인 메커니즘을 나타냅니다).

프로그래밍 언어 이론의 연속은 훨씬 더 근본적인 계산 방식이지만, 사용하기가 극히 어렵고 구현하기도 어렵 기 때문에 자주 사용되지 않습니다. 그러나 연속이 무엇인지에 대한 아이디어는 간단합니다. 아직 완료되지 않은 것은 계산 상태입니다. 이 상태에서는 변수의 현재 값, 아직 수행되지 않은 작업 등이 저장됩니다. 그런 다음 나중에 프로그램의 어떤 지점에서 프로그램의 변수가 해당 상태로 재설정되고 저장된 작업이 수행되도록 연속을 호출 할 수 있습니다.

이보다 일반적인 형태의 연속성은 두 가지 방법으로 구현 될 수 있습니다. 에서 call/cc방법, 프로그램의 스택은 그대로 저장되고 계속 호출 할 때 다음 스택은 복원됩니다.

연속 통과 스타일 (CPS)에서 연속은 프로그래머가 명시 적으로 관리하고 서브 루틴으로 전달하는 일반 함수 (함수가 첫 번째 클래스 인 언어에서만)입니다. 이 스타일에서 프로그램 상태는 스택의 어딘가에있는 변수가 아니라 클로저 (및 그 안에 인코딩되는 변수)로 표현됩니다. 제어 흐름을 관리하는 함수는 인수로 연속을 수락합니다 (CPS의 일부 변형에서는 함수가 여러 연속을 허용 할 수 있음). 간단히 호출하고 나중에 반환하여 호출함으로써 제어 흐름을 조작합니다. 연속 전달 스타일의 아주 간단한 예는 다음과 같습니다.

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

이 (매우 단순한) 예제에서, 프로그래머는 파일을 실제로 연속으로 저장하는 작업을 저장하고 (많은 세부 사항이있는 매우 복잡한 작업이 될 수 있음) 해당 연속을 전달합니다 (즉, 클래스 클로저)를 다른 오퍼레이터에게 전달하고, 필요하다면 그것을 호출한다. (필자는이 디자인 패턴을 실제 GUI 프로그래밍에서 많이 사용하는데, 그 이유는 GUI 이벤트가 트리거 된 후에 제어 코드를 관리하거나 코드 줄을 저장하기 때문입니다.)

이 게시물의 나머지 부분은 일반성을 잃지 않고 CPS로 지속성을 개념화합니다. 왜냐하면 이해와 읽기가 훨씬 쉽기 때문입니다.


파이썬으로 발전기에 대해 이야기 해보자. 생성자는 연속의 특정 하위 유형입니다. 반면, 연속 요청은 상태 저장 일반 수있는 연산 (즉, 프로그램의 호출 스택), 발전기에만 걸쳐 반복의 상태를 저장할 수있는 반복기 . 하지만이 정의는 발전기의 특정 사용 사례에 대해 약간 오도 된 것입니다. 예를 들면 :

def f():
  while True:
    yield 4

이것은 명확하게 행동이 잘 정의 된 합리적인 반복 가능합니다 - 생성기가 반복 할 때마다 4를 반환합니다 (영원히 그렇게합니다). 그러나 iterator (즉, for x in collection: do_something(x))를 생각할 때 떠오르는 iterable의 프로토 타입 유형은 아마도 아닙니다 . 이 예제는 생성자의 힘을 보여줍니다. 반복자가 있으면 생성자는 반복의 상태를 저장할 수 있습니다.

다시 말하면 : 연속은 프로그램 스택의 상태를 저장할 수 있고 생성자는 반복 상태를 저장할 수 있습니다. 이것은 발전이 발전기보다 훨씬 강력하다는 것을 의미하지만, 발전기는 훨씬 더 쉽습니다. 언어 디자이너가 쉽게 구현할 수 있으며, 프로그래머가 쉽게 사용할 수 있습니다 (화상을 입을 시간이 있으면 이 페이지에서 계속과 통화 / cc에 대해 읽고 이해하려고 시도하는 경우 ).

그러나 연속 전달 스타일의 단순하고 구체적인 경우로 발전기를 쉽게 구현할 수 있습니다 (개념화).

yield호출 될 때마다 함수에 연속을 반환하도록 지시합니다. 함수가 다시 호출되면 중단 된 부분부터 시작됩니다. 따라서 pseudo-pseudocode (즉, 의사 코드가 아닌 코드)에서 생성기의 next메서드는 기본적으로 다음과 같습니다.

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

여기서 yield키워드는 실제로 실제 발전기 기능에 대한 문법 설탕, 같은 기본적으로 뭔가 :

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

이것은 단지 의사 코드 일 뿐이며 파이썬에서 실제로 생성자를 구현하는 것은 더 복잡하다는 것을 기억하십시오. 그러나 무슨 일이 벌어지고 있는지 이해하기위한 연습으로, yield키워드 를 사용하지 않고 생성자 객체를 구현하기 위해 연속 전달 스타일을 사용해보십시오 .




(아래의 답변 은 스택 생성과 힙 조작의 트릭과 관련된 생성기 메커니즘기본 구현이 아니라 파이썬 생성기를 사용하는 관점에서만 설명합니다 .)

파이썬 함수에서 a yield대신에 returnwhen 이 사용 되면 ,이 함수는 호출 된 특별한 것으로 변환됩니다 generator function. 이 함수는 generator유형 의 객체를 반환합니다 . 키워드는 특별히 이러한 기능을 치료하기 위해 파이썬 컴파일러를 알리는 플래그이다. 정상적인 함수는 어떤 값이 반환되면 종료됩니다. 그러나 컴파일러의 도움으로 발전기 기능 재개 가능한 것으로 생각할 수 있습니다 . 즉, 실행 컨텍스트가 복원되고 실행은 마지막 실행에서 계속됩니다. 명시 적으로 return을 호출 할 때까지 예외를 발생 시키거나 (iterator 프로토콜의 일부이기도 함) 함수의 끝 부분에 도달 할 때까지. 나는 약 참조를 많이 발견 하지만,이 oneyieldStopIterationgeneratorone에서 functional programming perspective가장 소화가됩니다.

(이제는 배후의 논리 generatoriterator내 자신의 이해를 바탕으로 이야기하고 싶다 . 반복자와 생성자 의 근본적인 동기 를 파악하는 데 도움이되기를 바랍니다 . 이러한 개념은 C #과 같은 다른 언어에서도 나타납니다.)

이해할 수 있듯이 많은 데이터를 처리하려는 경우 일반적으로 먼저 데이터를 어딘가에 저장 한 다음 하나씩 처리합니다. 그러나이 직관적 인 접근법은 문제가됩니다. 데이터 볼륨이 큰 경우 미리 전체를 저장하는 데 많은 비용이 듭니다. 그래서 data직접 저장하는 대신 , metadata간접적으로 저장하십시오the logic how the data is computed .

이러한 메타 데이터를 래핑하는 방법에는 2 가지가 있습니다.

  1. 객체 지향 접근 방식에서는 메타 데이터를 래핑합니다 as a class. 이것은 iterator반복자 프로토콜 (즉 __next__(), 및 __iter__()메소드) 을 구현하는 소위 입니다 . 이것은 일반적으로 보인 반복자 디자인 패턴 이기도합니다 .
  2. 기능적 접근 방식으로 메타 데이터를 래핑합니다 as a function. 이것은 소위 generator function입니다. 그러나 후드 아래에서 iterator 프로토콜을 구현하기 때문에 generator object여전히 IS-Aiterator 가 반환 됩니다.

어느 쪽이든, 반복자가 생성됩니다. 즉, 원하는 데이터를 제공 할 수있는 객체입니다. OO 접근법은 약간 복잡 할 수 있습니다. 어쨌든 사용할 수있는 것은 당신에게 달려 있습니다.




파이썬이 실제로 생성자를 구현하는 방법에 대한 파이썬 예제는 다음과 같습니다.

파이썬 생성기로서 :

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

생성자 대신 렉시 컬 클로저 사용

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

생성자 대신 객체 클로저 사용 ( ClosuresAndObjectsAreEquivalent )

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)



yield키워드는 단순히 반환 결과를 수집합니다. 생각 yield처럼return +=




수율은 대상입니다

return함수의 A 는 단일 값을 반환합니다.

당신이 원하는 경우 함수 값의 거대한 집합을 반환하는 데 사용합니다 yield.

더 중요한 것은, yieldA는 장벽 .

CUDA 언어의 장벽과 마찬가지로, 컨트롤이 완료 될 때까지 컨트롤을 전송하지 않습니다.

즉, 함수에서 처음부터 코드가 실행될 때까지 코드를 실행합니다 yield. 그런 다음 루프의 첫 번째 값을 반환합니다.

그런 다음 다른 모든 호출은 함수에서 작성한 루프를 한 번 더 실행하고 반환 할 값이 없을 때까지 다음 값을 반환합니다.




다음은 일반 언어로 된 예제입니다. 높은 수준의 인간 개념과 낮은 수준의 파이썬 개념 사이의 통신을 제공 할 것입니다.

일련의 숫자를 조작하고 싶지만 그 시퀀스의 생성으로 자신을 귀찮게하고 싶지는 않습니다. 원하는 작업에만 집중하고 싶습니다. 그래서, 나는 다음과 같이한다.

  • 나는 당신을 불러 모으고 특정한 방법으로 생산되는 일련의 수를 원한다는 것을 말하고 알고리즘이 무엇인지 알려주겠습니다.
    이 단계 def는 생성자 함수, 즉 a를 포함하는 함수에 해당합니다 yield.
  • 언젠가 나중에, 나는 당신에게 말한다, "좋아, 숫자의 순서를 말해 줄 준비를해라."
    이 단계는 생성자 객체를 반환하는 생성자 함수를 호출하는 것과 같습니다. 아직 어떤 번호도 알려주지 않았다는 점에 유의하십시오. 당신은 당신의 종이와 연필을 움켜 잡습니다.
  • 나는 너에게 "다음 번호를 말해줘."라고 물어 보면, 첫 번째 번호를 말해 준다. 그 후에, 다음 전화 번호를 물어보기를 기다립니다. 당신이 어디에 있었는지, 이미 말한 번호와 다음 번호가 무엇인지 기억하는 것이 당신의 일입니다. 나는 세부 사항에 관심이 없다.
    이 단계 .next()는 생성자 객체 를 호출 하는 것과 같습니다.
  • ... 이전 단계를 반복 할 때까지 ...
  • 결국, 당신은 끝날 수도 있습니다. 너는 나에게 숫자를 말하지 않는다. 너는 그냥 소리 지른다. "너의 말을 잡아라. 다 끝났어! 더 이상 숫자는 없어!"
    이 단계는 해당 작업을 끝내고 StopIteration예외를 발생시키는 생성기 개체에 해당합니다 . 생성기 함수는 예외를 발생시킬 필요가 없습니다. 함수가 끝나면 자동으로 발생합니다 return.

이것은 생성기가하는 것입니다 (a를 포함하는 함수 yield). 실행을 시작하고, 실행될 때마다 일시 중지하며 yield, .next()마지막 시점에서 계속 값을 요청할 때 일시 중지됩니다 . 순차적으로 값을 요청하는 방법을 설명하는 Python의 반복자 프로토콜과 완벽하게 잘 어울립니다.

반복자 프로토콜의 가장 유명한 사용자는 forPython 의 명령입니다. 그래서, 당신이 할 때마다 :

for item in sequence:

위에 기술 된 것과 같은 sequence목록, 문자열, 사전 또는 생성자 객체 가 중요한지는 중요하지 않습니다 . 결과는 동일합니다. 즉, 항목을 하나씩 차례로 읽습니다.

키워드가 def포함 된 함수 yield를 사용하는 것이 생성기를 만드는 유일한 방법이 아니라는 점에 유의하십시오 . 그것은 단지 하나를 만드는 가장 쉬운 방법입니다.

보다 정확한 정보를 얻으려면, 반복자 유형 , yield 문generators 에 대해 Python 설명서를 읽으십시오 .




link 에는 간단한 예제가 있습니다.

심지어 간단한 예도 여기에 있습니다.

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

수익률 반환은 메소드에서 반환되지 않습니다. yield returnWriteLine 을 넣을 수도 있습니다.

위의 코드는 4 정수 4,4,4,4의 IEnumerable을 생성합니다.

여기에 WriteLine 있습니다. 목록에 4를 추가하고 abc를 인쇄 한 다음 목록에 4를 추가 한 다음 메서드를 완료하면 메서드에서 실제로 반환됩니다 (메서드가 완료되면 반환하지 않는 프로 시저 에서처럼 발생합니다). 그러나 이것은 값을 가지며, 완료시 리턴하는 IEnumerable int 리스트입니다.

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

yield를 사용할 때 리턴하는 것은 함수와 동일한 유형이 아님을 주목하십시오. 그것은 IEnumerable 목록 내의 요소 유형입니다.

메서드의 반환 형식을 yield로 IEnumerable 합니다. 메서드의 반환 유형이 int 또는 List<int> 이고 yield 를 사용하는 경우 컴파일되지 않습니다. 당신은 yield없이 IEnumerable 메소드 리턴 타입을 사용할 수 있지만 IEnumerable 메소드 리턴 타입없이 yield를 사용할 수없는 것 같습니다.

그리고 그것을 실행하려면 특별한 방법으로 호출해야합니다.

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}




python iterator generator yield coroutine