python - 설명 - 파이썬 제너레이터 yield




"yield"키워드는 무엇을합니까? (20)

파이썬에서 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 있는 함수는 호출되면 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 이 변경 사항은 사용자가 발전기로 데이터를 보낼 수 있도록 제안 되었습니다. 데이터를 보내려면 데이터를 무언가에 할당 할 수 있어야하며, 이에 대한 진술은 제대로 작동하지 않습니다.


Grokking yield 바로 가기

yield 문을 사용하여 함수를 볼 때 다음과 같은 일들을 이해하기 위해이 쉬운 트릭을 적용하십시오.

  1. 함수의 처음에 line result = [] 를 삽입하십시오.
  2. result.append(expr)yield expr 을 대체하십시오.
  3. 함수의 맨 아래에 return result 삽입하십시오.
  4. 예 - yield 선언문이 더 이상 없습니다! 코드를 읽고 알아 내십시오.
  5. 기능을 원래 정의와 비교하십시오.

이 트릭은 함수 뒤에있는 논리에 대한 아이디어를 줄 수 있지만 실제로 yield 에 따라 달라지는 것은리스트 기반 접근에서 일어나는 것과 상당히 다릅니다. 많은 경우 수율 접근 방식이 훨씬 더 효율적이고 빠른 메모리가 될 것입니다. 다른 경우에는 원래 기능이 제대로 작동하더라도이 트릭을 사용하면 무한 루프에 빠질 수 있습니다. 자세히 알아 보려면 계속 읽어보십시오 ...

반복문, 반복자 및 생성자를 혼동하지 마십시오.

첫째, 반복자 프로토콜 - 당신이 쓸 때

for x in mylist:
    ...loop body...

파이썬은 다음 두 단계를 수행합니다.

  1. mylist 의 반복자를 가져 mylist .

    iter(mylist) -> this는 next() 메서드 (또는 Python 3에서 __next__() 를 가진 객체를 반환합니다.

    [이것은 대부분의 사람들이 당신에게 말하는 것을 잊어 버리는 단계입니다]

  2. 반복기를 사용하여 항목을 반복합니다.

    1 단계에서 반환 된 반복자에서 next() 메서드를 계속 호출합니다. next() 의 반환 값이 x 할당되고 루프 본문이 실행됩니다. next() 내에서 StopIteration 예외가 발생하면 반복기에 값이 더 이상없고 루프가 종료되었음을 의미합니다.

진실은 파이썬이 객체의 내용을 반복 할 때마다 위의 두 단계를 수행하므로 for 루프 일 수 있지만 otherlist.extend(mylist) 와 같은 코드 일 수도 있습니다 ( otherlist 는 Python 목록) .

여기 mylist 는 iterator 프로토콜을 구현하기 때문에 iterable 이다. 사용자 정의 클래스에서 __iter__() 메서드를 구현하여 클래스의 인스턴스를 반복 가능하게 만들 수 있습니다. 이 메소드는 반복자를 리턴해야 합니다 . iterator는 next() 메서드가있는 객체입니다. 같은 클래스에서 __iter__()next() 를 모두 구현하고 __iter__()self 반환하도록 할 수 있습니다. 이것은 단순한 경우에는 효과가 있지만 동일한 객체에 대해 동시에 반복되는 두 개의 반복자를 원하는 경우에는 작동하지 않습니다.

이것이 iterator 프로토콜입니다. 많은 객체가이 프로토콜을 구현합니다.

  1. 붙박이 명부, 사전, 튜플, 세트, ​​파일.
  2. __iter__() 를 구현하는 사용자 정의 클래스.
  3. 발전기.

for 루프는 어떤 종류의 객체를 처리하는지 알지 못한다. iterator 프로토콜을 따르기 만하면 next() 호출 할 때 항목 뒤에 항목을 가져 오는 것이 좋다. 내장 목록은 항목을 하나씩 반환하고, 사전은 하나씩 키를 반환하고, 파일은 줄을 하나씩 반환합니다. 그리고 생성자가 반환합니다 ... 결과가 yield 됩니다.

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

yield 문 대신 f123()return 문이 세 개인 경우 첫 번째 문이 실행되고 함수는 종료됩니다. 그러나 f123() 은 일반적인 함수가 아닙니다. f123() 이 호출되면 yield 문에 값을 반환 하지 않습니다 ! 생성자 객체를 반환합니다. 또한 함수는 실제로 종료되지 않습니다. 일시 중지 상태가됩니다. for 루프가 생성자 객체 for 반복하려고하면 함수는 이전에 반환 된 yield 다음 바로 다음 행에서 일시 중단 된 상태에서 다시 시작하고 다음 줄의 코드 (이 경우 yield 문)를 실행하고 다음과 같이 반환합니다. 다음 항목. 이것은 함수가 종료 할 때까지 발생합니다.이 시점에서 생성기는 StopIteration 발생시키고 루프가 종료됩니다.

따라서 생성자 객체는 어댑터와 비슷합니다. 한쪽 끝에서 __iter__()next() 메서드를 노출하여 for 루프를 행복하게 유지하면서 iterator 프로토콜을 보여줍니다. 그러나 다른 쪽에서는 함수를 실행하여 다음 값을 꺼내 정지 모드로 되돌립니다.

왜 발전기를 사용합니까?

일반적으로 생성자를 사용하지 않고 동일한 로직을 구현하는 코드를 작성할 수 있습니다. 하나의 옵션은 이전에 언급 한 '트릭'임시 목록을 사용하는 것입니다. 예를 들어 무한 루프가 있거나 정말 긴 목록을 가지고있을 때 비효율적 인 메모리 사용을하는 경우가 있습니다. 다른 접근법은 인스턴스 멤버에 상태를 유지하고 next() (또는 Python 3에서 __next__() ) 메소드에서 다음 논리적 단계를 수행하는 새로운 iterable 클래스 SomethingIter 를 구현하는 SomethingIter 입니다. 논리에 따라 next() 메서드 내부의 코드가 매우 복잡해지고 버그가 발생하기 쉽습니다. 여기서 발전기는 깨끗하고 쉬운 솔루션을 제공합니다.


이것을 다음과 같이 생각하십시오.

iterator는 next () 메서드가있는 객체에 대한 멋진 말입니다. 따라서 yield-ed 함수는 다음과 같이 끝납니다 :

원본 버전 :

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

이것은 기본적으로 파이썬 인터프리터가 위의 코드로 수행하는 작업입니다.

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

배후에서 무슨 일이 벌어지고 있는지 더 자세히 알기 for 루프를 다음과 같이 다시 작성할 수 있습니다.

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

그게 더 이해가 되니 아니면 더 많이 혼란 스럽습니까? :)

나는 이것을 설명하기위한 단순화라고 지적해야한다. :)


yield 이 무엇인지 이해하려면 발전기 가 무엇인지 이해해야합니다. 그리고 발전기가 반복 되기 전에.

반복문

목록을 만들 때 항목을 하나씩 읽을 수 있습니다. 항목을 하나씩 읽는 것을 반복이라고합니다.

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist반복 가능하다 . 목록 이해력을 사용하면 목록을 작성하므로 반복 가능합니다.

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

" for... in... "에 사용할 수있는 모든 것은 반복 가능합니다. lists , strings , 파일 ...

이 반복문은 원하는만큼 읽을 수 있기 때문에 편리하지만 모든 값을 메모리에 저장하므로 값이 많을 때 항상 원하는 것은 아닙니다.

발전기

생성자는 반복자이며 한 번만 반복 할 수 있는 일종의 반복자입니다. 생성기는 모든 값을 메모리에 저장하지 않고 즉석에서 값을 생성합니다 .

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

[] 대신에 () 를 사용하는 것을 제외하고는 똑같습니다. 하지만 발전기는 한 번만 사용할 수 있기 때문에 발전기 for i in mygenerator 수행 할 수 없습니다 . 0을 계산 한 다음이를 잊어서 1을 계산하고 4를 하나씩 계산을 종료합니다.

수율

yield 는 함수가 생성자를 반환한다는 점을 제외하고는 return 과 같이 사용되는 키워드입니다.

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

여기서는 쓸데없는 예제이지만, 한 번만 읽어야하는 커다란 값 집합이 함수에서 반환된다는 것을 알면 편리합니다.

yield 를 마스터하기 위해서는 함수를 호출 할 때 함수 본문에 작성한 코드가 실행되지 않는다는 것을 알아야 합니다. 이 함수는 생성자 객체 만 리턴합니다. 이것은 약간 까다 롭습니다 :-)

그런 다음 for 가 생성자 for 사용할 때마다 코드가 실행됩니다.

이제 어려운 부분 :

for 가 함수에서 생성 된 generator 객체 for 처음 호출 할 때, yield 에서 yield 때까지 함수의 코드를 처음부터 실행합니다. 그러면 루프의 첫 번째 값을 반환합니다. 그런 다음 서로 다른 호출은 함수에서 작성한 루프를 한 번 더 실행하고 반환 할 값이 없을 때까지 다음 값을 반환합니다.

함수가 실행되면 생성기는 비어있는 것으로 간주되지만 더 이상 yield 는 발생하지 않습니다. 루프가 끝났거나 "if/else" 더 이상 만족시키지 않았기 때문일 수 있습니다.

코드 설명

발전기:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

방문객:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

이 코드에는 몇 가지 스마트 파트가 포함되어 있습니다.

  • 루프는 목록에서 반복되지만 루프가 반복되는 동안 목록이 확장됩니다. 이것은 네가 무한 루프로 끝날 수 있기 때문에 조금 위험하더라도 모든 중첩 된 데이터를 처리하는 간결한 방법입니다. 이 경우 candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) 는 생성기의 모든 값을 소모하지만 동일한 생성자 객체에 적용되지 않았기 때문에 이전 생성자 객체와 다른 값을 생성하는 새 생성기 객체를 계속 생성합니다. 마디.

  • extend() 메서드는 반복 가능한 객체를 기대하고 목록에 값을 추가하는 목록 객체 메서드입니다.

일반적으로 목록을 전달합니다.

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

그러나 코드에서 생성기를 얻습니다. 그 이유는 다음과 같습니다.

  1. 값을 두 번 읽을 필요는 없습니다.
  2. 당신은 많은 아이들을 가질 수 있고 그들 모두가 메모리에 저장되기를 원하지 않습니다.

파이썬은 메소드의 인수가리스트인지 아닌지 상관하지 않기 때문에 작동합니다. 파이썬은 iterables가 문자열,리스트, 튜플 및 생성자와 함께 작동 할 것으로 기대합니다! 이것을 오리 타이핑 (duck typing)이라고하며, 파이썬이 그렇게 멋진 이유 중 하나입니다. 하지만 이것은 또 다른 질문입니다.

여기에서 멈추거나 발전기의 고급 사용법을 조금이라도 읽을 수 있습니다.

발전기 고갈 제어

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

참고 : Python 3의 경우 print(corner_street_atm.__next__()) 또는 print(next(corner_street_atm))

자원에 대한 액세스 제어와 같은 다양한 작업에 유용 할 수 있습니다.

Itertools, 가장 친한 친구

itertools 모듈에는 iterables를 조작하기위한 특수 함수가 들어 있습니다. 이제까지 발전기를 복제하고 싶습니까? 두 개의 발전기를 연결 하시겠습니까? 한 줄짜리 중첩 목록의 값을 그룹화 하시겠습니까? Map / Zip 없이 다른 목록을 만드시겠습니까?

그런 다음 import itertools .

예를 들면? 4 종 경주에 대한 가능한 도착 순서를 봅시다.

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

반복의 내부 메커니즘 이해

반복은 iterables ( __iter__() 메소드 구현)와 iterators ( __next__() 메소드 구현)를 암시하는 프로세스입니다. 반복문은 반복자를 얻을 수있는 모든 객체입니다. 반복자는 반복 가능한 반복을 허용하는 객체입니다.

이 기사에서는 루프 작동 방식 for 대해 자세히 설명합니다 .


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

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

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


yield함수의 반환 요소와 같습니다. 차이점은 yield요소가 함수를 생성기로 변환한다는 것입니다. 생성기는 무언가 '산출'될 때까지 함수처럼 작동합니다. 발전기는 다음에 호출 될 때까지 정지하고 시작한 시점과 정확히 동일하게 계속됩니다. 전화를 걸어 '산출 된'모든 값의 순서를 얻을 수 있습니다 list(generator()).


수율은 대상입니다

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

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

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

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

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

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


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

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

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


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

나는 당신이 그 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쌍둥이는

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

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를 사용하여 호출 스택을 통해 물건을 밀어 올리는 효율적인 순열을 작성하는 데 사용할 수 있습니다. 생성기는 특수 트리 탐색 및 기타 모든 방법에 사용될 수도 있습니다.


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

파이썬 함수에서 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 접근법은 약간 복잡 할 수 있습니다. 어쨌든 사용할 수있는 것은 당신에게 달려 있습니다.


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


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

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

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

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

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

for item in sequence:

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

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

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


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

yield from <expr>

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

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

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

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

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

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


많은 사람들이 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()제공하고 생성기를 제공한다는 것입니다.

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


모든 답변과 마찬가지로 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파이썬 의 문장은 생성자를 리턴한다. 파이썬에서 생성자는 연속 을 반환하는 함수입니다 (특히 코 루틴의 유형이지만, 연속은 무슨 일이 일어나고 있는지 이해하기위한보다 일반적인 메커니즘을 나타냅니다).

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

이보다 일반적인 형태의 연속성은 두 가지 방법으로 구현 될 수 있습니다. 에서 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키워드 를 사용하지 않고 생성자 객체를 구현하기 위해 연속 전달 스타일을 사용해보십시오 .


언급해야 할 점이 한 가지 더 있습니다. 수익률 함수는 실제로 종료 할 필요가 없습니다. 나는 다음과 같은 코드를 작성했다 :

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

그런 다음이 코드를 다른 코드에서 사용할 수 있습니다.

for f in fib():
    if some_condition: break
    coolfuncs(f);

정말 일부 문제를 단순화하는 데 도움이되며 작업하기가 더 쉽습니다.


요약하면이 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를 반환하고 생성기가 함수의 끝으로 점프 할 때까지 계속됩니다.


최소한의 작업 예제를 선호하는 사람들은이 대화식 Python 세션을 묵상 해보십시오 .

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed




coroutine