python 파이썬 제너레이터 - "yield"키워드는 무엇을합니까?




15 Answers

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() 메서드 내부의 코드가 매우 복잡해지고 버그가 발생하기 쉽습니다. 여기서 발전기는 깨끗하고 쉬운 솔루션을 제공합니다.

설명

파이썬에서 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 키워드는 두 가지 간단한 사실로 축소되었습니다.

  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 를 사용할 수 있습니다.




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

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

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




최소한의 작업 예제를 선호하는 사람들은이 대화식 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



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

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

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




많은 답변이 왜 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()



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

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

생성기 및 동시 루틴은 데이터 흐름 유형 응용 프로그램을 설정하는 멋진 방법입니다. 나는 그것이 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
->



다음은 간단한 예입니다.

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




모든 답변과 마찬가지로 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명령문은 함수를 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를 반환하고 생성기가 함수의 끝으로 점프 할 때까지 계속됩니다.




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

파이썬 함수에서 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 +=




또 다른 TL, DR

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

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

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

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






Related