“yield”키워드는 무엇을합니까?


10187

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)가 작성했습니다. 이것은 완전한 소스에 대한 링크입니다 : Module mspace .

답변:


14639

무엇을 이해하려면 생성기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파일 ...

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

발전기

제너레이터는 반복자입니다. 반복자는 한 번만 반복 할 수 있습니다 . 생성기는 메모리에 모든 값을 저장하지 않고 값을 즉시 생성합니다 .

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

()대신에 사용한 것을 제외하고는 동일합니다 []. 그러나 발전기는 한 번만 사용할 수 있으므로 두 번째로 수행 할 수 없습니다. 제로는 for i in mygenerator0을 계산 한 다음 잊어 버리고 1을 계산하고 4를 하나씩 계산하지 않습니다.

수율

yieldreturn함수는 생성기를 반환한다는 점을 제외하고 와 같이 사용되는 키워드입니다 .

>>> 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함수에서 생성 된 생성기 객체를 처음 호출하면 처음부터 치기까지 함수에서 코드를 실행 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 the 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 the 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 candidate's 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))생성기의 모든 값을 소진하지만 while동일한 노드에 적용되지 않으므로 이전 생성기와 다른 값을 생성하는 새 생성기 객체를 계속 생성합니다.

  • extend()메소드는 iterable을 예상하고 해당 값을 목록에 추가하는리스트 오브젝트 메소드입니다.

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

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

그러나 코드에서 생성기를 얻습니다.

  1. 값을 두 번 읽을 필요는 없습니다.
  2. 자녀가 많을 수 있으며 자녀가 모두 메모리에 저장되는 것을 원하지 않습니다.

그리고 파이썬은 메소드의 인수가 목록인지 아닌지 신경 쓰지 않기 때문에 작동합니다. 파이썬은 이터 러블을 기대하므로 문자열,리스트, 튜플, 생성기에서 작동합니다! 이것을 오리 타이핑이라고하며 파이썬이 그렇게 멋진 이유 중 하나입니다. 그러나 이것은 또 다른 질문입니다. 또 다른 질문입니다 ...

여기에서 멈추거나 약간만 읽으면 발전기의 고급 사용법을 볼 수 있습니다.

발전기 피로 제어

>>> 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 모듈에는 iterable을 조작하는 특수 함수가 포함되어 있습니다. 발전기를 복제하고 싶습니까? 두 발전기를 연결? 하나의 라이너로 중첩 목록의 값을 그룹화합니까? 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)]

반복의 내부 메커니즘 이해

반복은 이터 러블 ( __iter__()방법 구현 )과 반복자 (방법 구현)를 암시하는 프로세스 __next__()입니다. 이터 러블은 이터레이터를 얻을 수있는 객체입니다. 이터레이터는 이터 러블을 반복 할 수있는 객체입니다.

이 기사에는 루프 작동 방식for 에 대한 자세한 내용이 있습니다 .


355
yield이 답변이 제안하는 것처럼 마법이 아닙니다. yield어디서나 명령문 이 포함 된 함수를 호출하면 생성기 객체가 생성되지만 코드는 실행되지 않습니다. 그런 다음 생성기에서 객체를 추출 할 때마다 Python은 yield명령문에 도달 할 때까지 함수에서 코드를 실행 한 다음 객체를 일시 중지하고 전달합니다. 다른 객체를 추출하면 파이썬은 바로 다음에 재개되어 다른 객체에 yield도달 할 때까지 계속 됩니다 yield(종종 동일한 객체 이지만 나중에 반복). 이것은 기능이 끝날 때까지 계속되며,이 시점에서 발전기가 소진 된 것으로 간주됩니다.
Matthias Fripp

29
"이 iterables는 편리하지만 ... 모든 값을 메모리에 저장하고 이것이 항상 원하는 것은 아닙니다"는 잘못되었거나 혼동됩니다. iterable은 iterable에서 iter ()를 호출 할 때 iterator를 반환하며, iter 메소드 의 구현에 따라 iterator는 항상 값을 메모리에 저장하지 않아도 되며 요청시 순서대로 값을 생성 할 수도 있습니다.
picmate 涅

위대한 대답 에 추가하는 것이 좋을 것입니다. 왜 대신 사용()[] 했는지를 제외하고는 동일합니다 . 특히 무엇입니까 ()(튜플과 혼동이있을 수 있습니다).
WoJ

내가 틀렸을 수도 있지만, 제너레이터는 이터레이터가 아니며 "제너레이터"는 이터레이터입니다.
aderchox

2006

이해의 지름길 yield

yield명령문이 포함 된 함수를 볼 때 다음과 같은 간단한 트릭을 적용하여 발생하는 상황을 이해하십시오.

  1. result = []함수 시작 부분에 줄 을 삽입하십시오 .
  2. 각각을 yield expr로 바꿉니다 result.append(expr).
  3. return result함수의 맨 아래에 줄 을 삽입하십시오 .
  4. 예, 더 이상 yield진술이 없습니다 ! 코드를 읽고 파악하십시오.
  5. 기능을 원래 정의와 비교하십시오.

이 트릭은 함수의 논리에 대한 아이디어를 줄 수 있지만 실제로 발생하는 yield것은 목록 기반 접근 방식에서 발생하는 것과 크게 다릅니다. 많은 경우 수율 접근 방식은 훨씬 더 효율적이고 빠른 메모리입니다. 다른 경우에는 원래 함수가 제대로 작동 하더라도이 트릭으로 인해 무한 루프에 빠질 수 있습니다. 자세한 내용은 계속 읽으십시오 ...

Iterables, Iterators 및 Generators를 혼동하지 마십시오

먼저 반복자 프로토콜 -쓸 때

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

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

  1. 다음에 대한 반복자를 가져옵니다 mylist.

    호출 iter(mylist)-> 메소드가있는 객체를 반환합니다 next()(또는 __next__()Python 3).

    [이것은 대부분의 사람들이 당신에게 말하지 않는 단계입니다]

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

    next()1 단계에서 리턴 된 반복자 에서 메소드를 계속 호출 하십시오.에서 리턴 값 next()이 지정되고 x루프 본문이 실행됩니다. StopIterationwithin에서 예외 가 발생 next()하면 반복기에 더 이상 값이없고 루프가 종료 된 것입니다.

진실은 파이썬이가 원하는 두 단계 위의 언제 수행입니다 돌이 객체의 내용 - 그것은 루프이 될 수 있도록,하지만 그것은 또한 같은 코드가 될 수있다 otherlist.extend(mylist)( otherlist파이썬 목록입니다).

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

이것이 반복자 프로토콜이므로 많은 객체가이 프로토콜을 구현합니다.

  1. 내장 목록, 사전, 튜플, 세트, ​​파일.
  2. 를 구현하는 사용자 정의 클래스 __iter__().
  3. 발전기.

참고는 것을 for루프는 다루고있어 개체 종류를 알고하지 않습니다 - 그냥 반복자 프로토콜을 따르며,이 호출로 항목 후에 항목을 얻을 행복하다 next(). 내장 목록은 항목을 하나씩 반환하고, 사전은 키를 하나씩 반환하고, 파일은 줄을 하나씩 반환합니다 . 그리고 생성기는 반환합니다 yield.

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

yield명령문 대신 세 개의 return명령문 f123()만 있으면 첫 번째 명령문 만 실행되고 함수가 종료됩니다. 그러나 f123()일반적인 기능은 없습니다. f123()호출 되면 yield 문에 값을 반환 하지 않습니다 ! 생성기 객체를 반환합니다. 또한 함수는 실제로 종료되지 않으며 일시 중단 상태가됩니다. for루프가 생성기 객체를 반복하려고 할 때 함수는 yield이전에 반환 된 후 바로 다음 줄에서 일시 중단 상태에서 다시 시작 하고 다음 코드 줄 (이 경우 yield명령문)을 실행하고 다음 줄을 반환합니다. 안건. 이것은 함수가 종료 될 때까지 발생하며,이 시점에서 제너레이터가 상승 StopIteration하고 루프가 종료됩니다.

따라서 제너레이터 객체는 일종의 어댑터와 비슷합니다. 한쪽 끝에는 루프를 만족 시키기위한 방법 __iter__()과 노출을 통해 반복기 프로토콜이 표시됩니다 . 그러나 다른 쪽 끝에서는 다음 값을 가져 오기에 충분할 정도로 함수를 실행하고이를 일시 중단 모드로 되돌립니다.next()for

왜 발전기를 사용해야합니까?

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


20
"수익률 문이있는 함수를 볼 때 발생하는 일을 이해하기 위해이 쉬운 트릭을 적용하십시오." 이것은 send생성기 시점의 큰 부분 인 생성기에 들어갈 수 있다는 사실을 완전히 무시하지 않습니까?
DanielSank

10
"for 루프 일 수도 있지만 otherlist.extend(mylist)"-> 와 같은 코드 일 수도 있습니다 . 이것은 올바르지 않습니다. extend()리스트를 제자리에서 수정하고 iterable을 리턴하지 않습니다. 암시 적으로 반환 하기 때문에 루프 오버를 시도 otherlist.extend(mylist)하면 실패하고 루프 오버 할 수 없습니다 . TypeErrorextend()NoneNone
페드로

4
@pedro 당신은 그 문장을 오해했습니다. 그것은 파이썬이 실행될 때 언급 mylist되지 않은 두 단계를 수행한다는 것을 의미합니다 . otherlistotherlist.extend(mylist)
오늘

555

이런 식으로 생각하십시오 :

이터레이터는 next()메서드 가있는 객체를 가리키는 멋진 용어입니다 . 따라서 항복 함수는 다음과 같습니다.

원본 버전 :

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

더 이해가 되나요? 아니면 더 혼란 스럽습니까? :)

나는이 점에 유의해야한다 입니다 설명을 목적으로 지나치게 단순화. :)


1
__getitem__대신에 정의 될 수 있습니다 __iter__. 예를 들면 다음 class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i)과 같습니다. 0, 10, 20, ..., 90
jfs

17
파이썬 3.6 에서이 예제를 사용해 보았고 생성 iterator = some_function()하면 변수에 더 이상 iterator호출되는 함수가 next()없지만 __next__()함수 만 있습니다. 내가 언급 할 줄 알았는데
Peter

어디 for작성한 루프 구현 에서 인스턴스화 된 인스턴스 인의 __iter__메소드를 호출 합니까? iteratorit
SystematicDisintegration

455

yield키워드는 두 가지 간단한 사실로 감소된다 :

  1. 컴파일러는 감지하면 yield키워드 어디서나 함수 내부, 그 함수를 통해 더 이상 반환 return문을. 대신 , 즉시 생성기라는 게으른 "보류중인 목록"개체를 반환합니다.
  2. 생성기는 반복 가능합니다. iterable 은 무엇입니까 ? 유사한 그것의 아무것도 list또는 set또는 range또는 DICT - 전경, 내장 된 특정 순서로 각 요소를 방문해 프로토콜 .

요컨대 , 생성기는 게으르고 점증 적으로 보류중인 목록 이며 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]]

makeRangePython과 같은 함수 를 정의 해 봅시다 range. 전화하면 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(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이므로 종종 다음과 같이 사용됩니다.

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

발전기에 대해 더 나은 느낌을 얻으려면 itertools모듈을 가지고 놀 수 있습니다 ( 보증이 chain.from_iterable아닌 사용하십시오 chain). 예를 들어, 생성기를 사용하여 다음과 같이 무한히 긴 지연 목록을 구현할 수도 있습니다itertools.count() . 자체 구현 def enumerate(iterable): zip(count(), iterable)하거나 yieldwhile 루프 의 키워드로 구현할 수도 있습니다 .

참고 : 발전기는 실제로 다음과 같은 더 많은 것들에 사용될 수 있습니다 코 루틴 또는 비 결정적 프로그래밍 또는 기타 우아한 것들을 구현하는 . 그러나 여기에 제시된 "게으른 목록"관점은 가장 일반적인 용도입니다.


무대 뒤에서

이것이 "Python iteration protocol"의 작동 방식입니다. 즉, 할 때 무슨 일이 일어나고 있는지입니다 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()함수 (및 반복 프로토콜의 다른 부분)를 일반적으로 가독성을 희생하면서 멋진 것을 구현할 수 있으므로 그렇게하지 마십시오 ...


사소한 점

일반적으로 대부분의 사람들은 다음과 같은 차이점에 신경 쓰지 않으며 여기에서 읽기를 중단하려고합니다.

Python-speak에서 iterable 은 list와 같은 "for-loop의 개념을 이해하는"객체 [1,2,3]이며 iterator 는 요청 된 for-loop와 같은 특정 인스턴스입니다 [1,2,3].__iter__(). 발생기 가 (기능 구문) 기록 된 방법을 제외하고, 정확하게 어떤 반복기와 동일하다.

목록에서 반복자를 요청하면 새 반복자가 작성됩니다. 그러나 반복자에게 반복자를 요청하면 (거의 수행하지는 않음) 자체 사본을 제공합니다.

따라서, 당신이 이런 일을하지 못하는 경우에는 ...

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

... 제너레이터가 이터레이터 임을 기억하십시오 . 즉, 일회용입니다. 재사용하려면 다시 전화해야 myRange(...)합니다. 결과를 두 번 사용해야하는 경우 결과를 목록으로 변환하여 variable에 저장하십시오 x = list(myRange(5)). itertools.tee복사 가능한 반복자 Python PEP 표준 제안이 지연되었으므로 생성기를 복제해야하는 사람들 (예 : 끔찍하게 해킹 메타 프로그래밍을 수행하는 사람)은 필요한 경우 사용할 수 있습니다 .


377

뭐라고합니까 yield키워드는 파이썬에서합니까?

답변 개요 / 요약

  • yield호출 하면를 가진 함수 Generator를 반환합니다 .
  • 생성기는 반복자 프로토콜 을 구현하므로 반복자이므로 반복자 를 반복 할 수 있습니다.
  • 또한 생성기는 정보를 전송 하여 개념적으로 코 루틴으로 만들 수 있습니다 .
  • Python 3에서는을 사용 하여 한 발전기에서 다른 발전기로 양방향으로 위임 할 수 있습니다 yield from.
  • (부록은 상위 답변을 포함하여 두 가지 답변을 비판 return하고 발전기에서 의 사용에 대해 설명합니다 .)

발전기 :

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

생성기의 아이디어는 다양한 구현으로 다른 언어 (각주 1 참조)에서 나옵니다. Python 's Generators에서 코드 실행이 고정됩니다. 수율 시점에서 됩니다. 생성기가 호출되면 (방법에 대해서는 아래에서 설명) 실행이 재개 된 후 다음 수율로 정지됩니다.

yielditerator 프로토콜구현 하는 쉬운 방법을 제공하며 다음 두 가지 방법으로 정의됩니다 : __iter__next(Python 2) 또는 __next__(Python 3). 이 두 가지 방법 모두 모듈 Iterator에서 추상 기본 클래스로 유형을 확인할 수있는 객체를 반복자로 만듭니다 collections.

>>> 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

위의 간단한 생성기는 다음과 같습니다 .Python 3.3부터 (Python 2에서는 사용할 수 없음) 다음을 사용할 수 있습니다 yield from.

def func(an_iterable):
    yield from an_iterable

그러나 yield from하위 생성자에게 위임 할 수도 있습니다. 하위 생성자와의 공동 위임에 대해서는 다음 섹션에서 설명합니다.

코 루틴 :

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)

먼저 내장 함수 인 생성기를 큐잉해야합니다 next. 사용중인 Python 버전에 따라 적절한 next또는 __next__메소드를 호출합니다 .

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

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

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

하위 코 루틴과의 협력 위임 yield from

이제 yield from파이썬 3에서 사용할 수 있습니다.이를 통해 코 루틴을 서브 코 루틴에 위임 할 수 있습니다.

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방법은 제기 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에는 iterator를__iter__ 반환하는 메서드 가 있습니다. 반복자는 제공 (파이썬 2 또는 암시 적으로 호출 (파이썬 3) 메소드 가 제기 될 때까지 루프를.next.__next__forStopIteration , 그리고 그것을 수행하면, 그것은 그렇게 할 것입니다.
  • 그런 다음 생성기 표현식을 사용하여 생성기가 무엇인지 설명합니다. 제너레이터는 단순히 이터레이터 를 만드는 편리한 방법 이므로 문제를 혼동하기 만했지만 아직 yield부분에 도달하지 못했습니다 .
  • 에서 발전기 고갈 제어 그가 부르는 .next대신 그가 내장 기능을 사용할시기, 방법,next . 그의 코드는 Python 3에서 작동하지 않기 때문에 적절한 간접 계층이 될 것입니다.
  • Itertools? 이것은 전혀 어떤 것과 관련이 없습니다 yield.
  • Python 3 yield 새로운 기능과 함께 제공 되는 메서드에 대한 설명은 없습니다 . 최고 / 허용 답변은 매우 불완전한 답변입니다.yield from

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 핵심 개발자는 허용되지 않는 것에 대해 논의하고 있습니다. 메일 링리스트의 관련 게시물은 다음과 같습니다.

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

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

어느 쪽이든 접근해도 괜찮습니다. IMHO는 Python 3에서와 같은 방식으로 떠나는 것이 좋지 않습니다.

내 투표는 구문에서 기대하는 것을 얻지 못하기 때문에 SyntaxError입니다.

현재 행동에 의존하는 코드가 실제로 유지하기에는 너무 영리하기 때문에 우리가 끝내기에 합리적인 장소라는 데 동의합니다.

도착하는 관점에서 우리는 다음을 원할 것입니다.

  • 3.7의 구문 경고 또는 비추천 경고
  • 2.7.x의 Py3k 경고
  • 3.8의 구문 오류

건배, 닉

-닉 코 글란 | gmail.com에서 ncoghlan | 브리즈번, 오스트레일리아

또한, 이것이 결코 좋은 생각이 아니라는 방향으로 지적하고 있는 뛰어난 문제 (10544) 가 있습니다 (PyPy, Python으로 작성된 Python 구현은 이미 구문 경고를 발생시킵니다).

결론은 CPython 개발자가 달리 말할 때까지 : 발전기 표현이나 이해를 넣지 마십시오 yield.

return발전기 의 진술

에서 파이썬 2 :

생성기 함수에서 return명령문은을 포함 할 수 없습니다 expression_list. 이와 관련하여, 베어 (bare) return는 생성기가 완료되었으며 StopIteration발생하게됩니다.

A expression_list는 기본적으로 쉼표로 구분 된 많은 수의 표현식입니다. 기본적으로 Python 2에서는으로로 제너레이터를 중지 return할 수 있지만 값을 반환 할 수는 없습니다.

에서 파이썬 3 :

생성기 함수 return에서이 명령문은 생성기가 완료되었으며 StopIteration발생하게됩니다. 리턴 값은 (만약 있다면) 구성의 인수로서 사용 StopIteration하고해진다 StopIteration.value속성.

각주

  1. CLU, Sather 및 Icon 언어는 제안서에서 생성기 개념을 Python에 도입하기 위해 참조되었습니다. 일반적인 아이디어는 함수가 내부 상태를 유지하고 사용자의 요청에 따라 중간 데이터 포인트를 생성 할 수 있다는 것입니다. 이것은 일부 시스템에서는 사용할 수없는 Python threading같은 다른 접근 방식보다 성능우수하다는 것을 약속했습니다 .

  2. 예를 들어, 파이썬 3에서 xrange객체 는 재사용이 가능하기 때문에 반복 가능하더라도 객체 range가 아닙니다 Iterator. 목록과 마찬가지로 해당 __iter__메서드는 반복자 객체를 반환합니다.

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


328

yield그냥 return-생성기로 모든 것을 반환합니다. 차이점은 다음에 생성기를 호출 할 때 마지막 호출부터 yield명령문에 대한 실행이 시작된다는 것 입니다. 반환과 달리 스택 프레임은 수율이 발생할 때 정리되지 않지만 제어가 호출자에게 다시 전송되므로 다음에 함수가 호출 될 때 상태가 다시 시작됩니다.

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

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


107
이것은 가깝지만 정확하지 않습니다. yield 문이있는 함수를 호출 할 때마다 새로운 생성기 객체를 반환합니다. 마지막 생성 후 실행이 재개되는 생성기의 .next () 메소드를 호출 할 때만 가능합니다.
kurosch

239

언급해야 할 것이 하나 더 있습니다. 수율 함수는 실제로 종료 할 필요가 없습니다. 다음과 같은 코드를 작성했습니다.

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);

실제로 일부 문제를 단순화하고 작업하기가 더 쉽습니다.


233

최소한의 예제를 선호하는 사람들을 위해이 대화 형 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

209

TL; DR

이 대신에 :

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

이 작업을 수행:

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

처음부터 목록을 작성할 때 yield마다 각 조각을 대신 사용하십시오.

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


yieldA는 달콤한 말을하는 방법은

일련의 물건을 만들다

같은 행동 :

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

다른 행동 :

수율은 단일 패스입니다 . 한 번만 반복 할 수 있습니다. 함수가 항복 할 때이를 생성기 함수 라고합니다 . 그리고 반복자 는 그것이 반환하는 것입니다. 그 용어가 드러납니다. 우리는 컨테이너의 편의성을 잃지 만 필요에 따라 계산되고 임의로 긴 시리즈의 성능을 얻습니다.

수율이 게으 르며 계산이 중단됩니다. 수율이있는 함수는 실제로 호출 할 때 전혀 실행되지 않습니다. 중단 된 위치를 기억 하는 반복자 객체 를 반환합니다 . next()반복자 를 호출 할 때마다 (이것은 for-loop에서 발생) 다음 수율로 진행됩니다. returnStopIteration을 발생시키고 시리즈를 종료합니다 (이것은 for-loop의 자연스러운 끝입니다).

수확량은 다양 합니다. 데이터를 모두 함께 저장할 필요는 없으며 한 번에 하나씩 사용할 수 있습니다. 무한 할 수 있습니다.

>>> 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(square_yield(4))
[0, 1, 4, 9]

두 가지 의미가 모두 적용 yield되므로 단어를 훌륭하게 선택하십시오 .

수확량 — 생산 또는 제공 (농업에서와 같이)

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

수확량 — 정치 권력에서와 같이 길을 포기하거나 포기 함

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


194

수확량은 발전기를 제공합니다.

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 개의 요소가있는 목록에는 별 문제가되지 않지만 5 백만 개의 목록을 원한다면 어떻게해야합니까? 이것은 거대한 메모리 소비 기 일뿐 만 아니라 함수가 호출 될 때 빌드하는 데 많은 시간이 걸립니다.

두 번째 경우 bar에는 발전기를 제공합니다. 생성기는 반복 가능하므로 for루프 등에서 사용할 수 있지만 각 값은 한 번만 액세스 할 수 있습니다. 모든 값은 동시에 메모리에 저장되지 않습니다. 생성자 객체는 마지막으로 호출했을 때 반복 된 위치를 "기억"합니다. 이렇게하면 반복 가능한 것을 사용하여 500 억까지 계산할 경우 500 억까지 계산할 필요가 없습니다. 한 번에 500 억 개의 숫자를 저장하여 계산합니다.

다시 말하지만, 이것은 꽤 고안된 예입니다. 실제로 500 억에 달하고 싶다면 itertools를 사용할 것입니다. :)

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


Python 3에서는 메모 range대신 목록 대신 생성기를 반환하므로 더 좋은 결과를 표시하기 위해 __repr__/ __str__를 재정의 한다는 점을 제외하고 비슷한 아이디어 가 표시 range(1, 10, 2)됩니다.
NotALie입니다.

189

발전기를 반환합니다. 나는 특별히 파이썬에 익숙하지는 않지만, 익숙하다면 C #의 반복자 블록 과 같은 종류라고 생각합니다 .

핵심 아이디어는 컴파일러 / 인터프리터 / 어떤 속임수라도 호출자가 관심이있는 한 계속 next ()를 호출 할 수 있으며 생성기 메서드가 일시 중지 된 것처럼 값을 계속 반환한다는 것 입니다. 이제는 실제로 메소드를 "일시 정지"할 수 없으므로 컴파일러는 현재 위치와 로컬 변수 등이 무엇인지 기억하기 위해 상태 머신을 빌드합니다. 이터레이터를 직접 작성하는 것보다 훨씬 쉽습니다.


167

생성기를 사용하는 방법을 설명하는 많은 훌륭한 답변 중에서 아직 느끼지 못한 한 가지 유형의 답변이 있습니다. 프로그래밍 언어 이론 답변은 다음과 같습니다.

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를 반환합니다 (그리고 영원히). 그러나 반복자를 생각할 때 떠오를 수있는 프로토 타입 유형의 반복 가능한 것은 아닙니다 (예 :) for x in collection: do_something(x). 이 예제는 생성기의 힘을 보여줍니다. 반복자가 있으면 생성기가 반복 상태를 저장할 수 있습니다.

반복 : 연속은 프로그램의 스택 상태를 저장할 수 있으며 생성기는 반복 상태를 저장할 수 있습니다. 이는 연속성이 생성기보다 훨씬 강력하지만 생성기가 훨씬 훨씬 쉽다는 것을 의미합니다. 언어 디자이너가 구현하기가 더 쉽고 프로그래머가 사용하기가 더 쉽습니다 (구울 시간이 있으면 연속 및 call / cc에 대한 이 페이지 를 읽고 이해 하십시오 ).

그러나 간단하고 구체적인 연속 전달 스타일로 생성기를 쉽게 구현하고 개념화 할 수 있습니다.

yield호출 될 때마다 함수에 연속을 반환하도록 지시합니다. 함수가 다시 호출되면 중단 된 위치부터 시작합니다. 따라서 의사 의사 코드 (의사 코드가 아니라 코드가 아닌)에서 생성기의 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키워드 를 사용하지 않고 생성기 객체를 구현하십시오 .


152

다음은 평범한 언어의 예입니다. 고급 인간 개념과 저수준 파이썬 개념 사이의 대응 관계를 제공 할 것입니다.

나는 일련의 숫자로 작업하고 싶지만 그 시퀀스의 생성으로 내 자신을 귀찮게하고 싶지 않습니다. 나는 내가하고 싶은 작업에만 집중하고 싶습니다. 그래서 나는 다음을 수행합니다.

  • 나는 당신에게 전화를 걸고 특정 방식으로 생성 된 일련의 숫자를 원한다고 말하고 알고리즘이 무엇인지 알려줍니다.
    이 단계 def는 생성기 함수, 즉 a를 포함하는 함수를 입력하는 것에 해당합니다 yield.
  • 나중에, 나는 당신에게 "알겠습니다, 일련의 숫자를 말해 줄 준비를하세요"라고 말합니다.
    이 단계는 생성기 객체를 반환하는 생성기 함수 호출에 해당합니다. 아직 숫자를 말하지 마십시오. 당신은 당신의 종이와 연필을 잡아.
  • 나는 당신에게 "다음 번호를 말해줘"라고 물으면 첫 번째 번호를 말해줍니다. 그 후, 당신은 내가 당신에게 다음 번호를 요구하기를 기다립니다. 현재 위치, 이미 말한 숫자 및 다음 숫자는 무엇인지 기억하는 것이 귀하의 임무입니다. 나는 세부 사항에 관심이 없습니다.
    이 단계 .next()는 생성기 객체 를 호출하는 것에 해당 합니다.
  • … 이전까지…
  • 결국, 당신은 끝날 수 있습니다. 당신은 나에게 숫자를 말하지 않습니다; "말을 잡아라! 끝났어! 더 이상 숫자가 없어!"
    이 단계는 작업을 종료하고 StopIteration예외를 발생시키는 생성기 오브젝트에 해당합니다 . 생성기 기능은 예외를 발생시킬 필요가 없습니다. 함수가 종료되거나 a를 발행하면 자동으로 발생합니다 return.

이것은 제너레이터가하는 일입니다 (를 포함하는 함수 yield). 실행을 시작하고을 수행 할 때마다 일시 중지 yield되며 .next()값을 요청하면 마지막 시점부터 계속됩니다. 순차적으로 값을 요청하는 방법을 설명하는 Python의 반복자 프로토콜을 통해 설계에 완벽하게 맞습니다.

반복자 프로토콜의 가장 유명한 사용자는 forPython 의 명령입니다. 따라서 할 때마다 :

for item in sequence:

위에서 설명한 sequence목록, 문자열, 사전 또는 생성기 개체 인지 여부 는 중요하지 않습니다 . 결과는 동일합니다. 순서대로 항목을 하나씩 읽습니다.

참고 def포함 된 기능 ining yield키워드하는 발전기를 만들 수있는 유일한 방법은 아니다; 가장 쉬운 방법입니다.

보다 정확한 정보 는 파이썬 문서에서 iterator types , yield 문생성기 에 대해 읽으십시오 .


130

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

yield다음 코드에서 수행 하는 작업을 이해 하기 위해 손가락을 사용하여 코드가있는 코드를 통해주기를 추적 할 수 있습니다 yield. 손가락 이을 ( yield를) 누를 때마다 a next또는 a send가 입력 될 때까지 기다려야합니다 . a next를 호출 yield하면 오른쪽에있는 코드 yield가 평가되어 호출자에게 반환 될 때까지 코드를 추적 합니다. 그러면 대기합니다. 를 next다시 호출하면 코드를 통해 다른 루프를 수행합니다. 그러나 코 루틴에서는 호출자와 값을 함수 보내는 값 yield과 함께 사용할 수도 있습니다 . 만약sendsend 가 주어지면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()

귀엽다! 트램 폴린 (리스프의 의미에서). 종종 사람들은 그것을 보지 않습니다!
00prometheus

129

다른 yield용도와 의미가 있습니다 (Python 3.3부터).

yield from <expr>

PEP 380 부터 -서브 제너레이터로 위임하기위한 구문 :

생성기가 작업의 일부를 다른 생성기에 위임하기위한 구문이 제안됩니다. 이를 통해 '수율'이 포함 된 코드 섹션을 분해하여 다른 생성기에 배치 할 수 있습니다. 또한 하위 생성기는 값과 함께 리턴 될 수 있으며 해당 값은 위임 생성기에 사용 가능합니다.

새로운 구문은 또한 한 발전기가 다른 발전기가 생산 한 값을 다시 산출 할 때 최적화 할 수있는 기회를 열어줍니다.

또한 이것은 (Python 3.5부터) 소개합니다.

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

코 루틴이 일반 발전기와 혼동되지 않도록하십시오 (오늘 yield은 두 가지 모두에 사용됩니다).


117

모든 훌륭한 답변이지만 초보자에게는 조금 어렵습니다.

나는 당신이 그 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 themyield로 해석 할 수 있습니다 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는 함수에서의 위치를 ​​기억하므로 다음에 next ()를 호출하면 함수는 next로 계속됩니다 yield. 나는 이것이 중요하다고 생각하며 표현되어야한다.
Mike S

104

파이썬이 구문 설탕을 제공하지 않은 것처럼 실제로 생성기를 구현하는 방법에 대한 몇 가지 파이썬 예제는 다음과 같습니다.

파이썬 생성기 :

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)

생성기 대신 객체 클로저 사용 ( CursuresAndObjectsAreEquivalent )

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)

97

"제너레이터에 대한 빠른 설명을 보려면 Beazley의 'Python : Essential Reference'19 페이지를 읽으십시오."

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

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


97

프로그래밍 관점에서 이터레이터는 thunks 로 구현됩니다 .

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

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

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

이 계산을 구현하는 방법에는 여러 가지가 있습니다. 나는 돌연변이를 사용했지만 현재 가치와 다음 생산자를 반환함으로써 돌연변이없이 쉽게 할 수 있습니다.

다음은 R6RS의 구조를 사용하는 데모이지만 시맨틱은 Python과 완전히 동일합니다. 동일한 계산 모델이며 파이썬에서 다시 작성하려면 구문 변경 만 필요합니다.

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
->

84

다음은 간단한 예입니다.

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프로그램 흐름의 위치를 ​​잡고 다음 루프가 "수율"위치에서 시작 하는 것으로 보입니다 . 그것은 그 위치에서 기다리고있는 것처럼 보이며 그 직전에 값을 외부로 반환하고 다음에는 계속 작동합니다.

흥미롭고 좋은 능력 인 것 같습니다 : D


당신이 올바른지. 그러나 "수율"의 동작을 보는 것이 흐름에 미치는 영향은 무엇입니까? 수학의 이름으로 알고리즘을 변경할 수 있습니다. "수율"에 대한 다른 평가를 얻는 데 도움이됩니까?
Engin OZTURK

68

다음은 무엇을하는 지의 정신적 인 이미지입니다 yield.

스레드가 스택을 갖는 것으로 생각하고 싶습니다 (그런 식으로 구현되지 않은 경우에도).

일반 함수가 호출되면 로컬 변수를 스택에 놓고 계산을 한 다음 스택을 지우고 반환합니다. 지역 변수의 값은 다시 표시되지 않습니다.

A의 yield해당 코드 (그 발전기 객체 반환하는 함수가 호출 될 때, 즉 이후에 실행하기 시작 기능 next()에있어서 다음 호출된다), 이것은 마찬가지로 잠시 동안 스택 계산해 상에 로컬 변수를 둔다. 그러나 yield명령문에 도달하면 스택의 일부를 지우고 리턴하기 전에 로컬 변수의 스냅 샷을 작성하여 생성기 오브젝트에 저장합니다. 또한 현재 코드의 특정 위치 (예 : 특정 yield명령문)를 기록합니다.

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

next()이후에 호출 되면 스택에서 함수의 소지품을 검색하여 다시 애니메이션합니다. 이 기능은 중단 된 곳부터 계속 계산하여 냉장 보관에서 영원을 보냈다는 사실을 알지 못합니다.

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

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(..)위의 정신 모델이 신뢰할 수 있음을 확인할 수 있습니다.


59

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

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

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

for line in getNextLines():
    doSomeThing(line)

실행 제어 전송

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"

59

그것이 무엇인지 이해하는 쉬운 예 : yield

def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print (i)

출력은 다음과 같습니다.

1 2 1 2 1 2 1 2

5
그 결과에 대해 확신합니까? print(i, end=' ')?를 사용하여 해당 print 문을 실행하면 한 줄에만 인쇄되지 않습니다. 그렇지 않으면, 나는 기본 동작은 새 줄에 각 번호를 넣어 것입니다 생각
user9074332

@ user9074332, 당신 말이 맞지만 이해를 돕기 위해 한 줄로 작성되었습니다
Gavriel Cohen

57

(아래 답변 은 스택 및 힙 조작의 일부 트릭을 포함하는 생성기 메커니즘기본 구현이 아닌 Python 생성기 사용 관점에서만 언급합니다 .)

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

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

내가 알다시피, 많은 데이터를 처리하고 싶을 때, 보통 데이터를 어딘가에 저장 한 다음 하나씩 처리합니다. 그러나이 순진한 접근 방식은 문제가 있습니다. 데이터 볼륨이 크면 미리 전체를 저장하는 데 비용이 많이 듭니다. 따라서 data자체를 직접 저장하는 대신 metadata간접적으로 저장하지 않는 것이the logic how the data is computed 좋습니다.

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

  1. OO 방식에서는 메타 데이터를 래핑합니다 as a class. 이것은 iterator반복자 프로토콜을 구현하는 소위 사람입니다 (예 : __next__()__iter__()메소드). 이것은 일반적으로 보이는 반복자 디자인 패턴 입니다.
  2. 기능적 접근 방식으로 메타 데이터를 래핑합니다 as a function. 이것은 소위 generator function입니다. 그러나 후드 에서 반복자 프로토콜도 구현하기 때문에 반환 된 generator object여전히 IS-A반복자가 있습니다.

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


54

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

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

for i in simple_generator():
    print i

단순히 출력

one
two
three

전력은 시퀀스를 계산하는 루프와 함께 생성기를 사용하여 발생하며, 생성기는 다음 계산 결과를 '수율'하기 위해 매번 루프 정지를 실행합니다.이 방법으로 즉시 목록을 계산합니다. 이점은 메모리입니다. 특히 큰 계산을 위해 저장

range반복 가능한 숫자 범위를 생성하는 고유 한 함수 를 만들고 싶다고 가정 해 봅시다 .

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 문 및 yield 'n'까지 실행하고 다음 호출에서는 증가 명령문을 실행하고 'while'로 되돌아 가서 평가 한 다음 true이면 중지하고 yield 'n'을 다시 지정하면 while 조건이 false를 반환하고 제너레이터가 함수의 끝으로 점프 할 때까지 계속 진행됩니다.


53

수확량은 물체입니다

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

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

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

CUDA 언어의 장벽과 같이 완료 될 때까지 제어를 이전하지 않습니다.

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

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


52

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

실제 예제는 파일을 한 줄씩 읽거나 생성기를 만들고 싶은 경우와 같습니다.


43

yield함수의 반환 요소와 같습니다. 차이점은 yield요소가 함수를 생성기로 변환한다는 것입니다. 생성기는 무언가가 '항복'될 때까지 함수처럼 작동합니다. 생성기는 다음에 호출 될 때까지 중지하고 시작과 정확히 같은 지점에서 계속됩니다. 를 호출하여 하나의 모든 '수율'값의 시퀀스를 얻을 수 있습니다 list(generator()).



36

yield피보나치 시리즈를 계산 하는 간단한 기반 접근법이 있습니다.

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 를 생성하려는 Python 에 신호를 보내면 , 즉 요청시 값을 생성하는 객체입니다.

그렇다면 이러한 값을 어떻게 생성합니까? 내장 함수를 사용하여 직접 수행 next하거나 값을 소비하는 구문에 간접적으로 제공 하여 수행 할 수 있습니다 .

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

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

당신이 제공하는 경우 간접적으로, fibA와 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키워드를 만나고 단순히 생성자 객체를 반환합니다. 별로 도움이되지 않는 것 같습니다.

그런 다음 요청하면 첫 번째 값을 직접 또는 간접적으로 생성하여 찾은 모든 명령문을 실행하고, 발견 될 때까지 yield제공 한 값을 반환 yield하고 일시 중지합니다. 이것을 더 잘 보여주는 예를 들어, 몇 가지 print호출을 사용합시다 ( print "text"Python 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루프 로 인해 다시 수행 될 때 일시 정지합니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.