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. 이 코드는 미터법 공간을 위한 훌륭한 Python 라이브러리를 만든 Jochen Schulz(jrschulz)가 작성했습니다. 이것은 완전한 소스에 대한 링크입니다: [Module mspace][1].
답변자 : e-satis
yield 가 무엇인지 이해하려면 생성기 가 무엇인지 이해해야 합니다. 그리고 생성자를 이해하기 전에 iterables 를 이해해야 합니다.
반복 가능
목록을 만들 때 항목을 하나씩 읽을 수 있습니다. 항목을 하나씩 읽는 것을 반복이라고 합니다.
>>> 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 mygenerator 대해 두 번째로 수행 할 수 없습니다. 생성기는 0을 계산한 다음 잊어버리고 1을 계산하고 하나씩 4를 계산합니다.
생산하다
yield 는 함수가 생성기를 반환한다는 점을 제외하고는 return 처럼 사용되는 키워드입니다.
>>> def create_generator(): ... mylist = range(3) ... for i in mylist: ... yield i*i ... >>> mygenerator = create_generator() # create a generator >>> print(mygenerator) # mygenerator is an object! <generator object create_generator 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]
그러나 코드에서는 다음과 같은 이유로 좋은 생성기를 얻습니다.
값을 두 번 읽을 필요가 없습니다.
자녀가 많을 수 있으며 모든 자녀가 메모리에 저장되는 것을 원하지 않을 수 있습니다.
그리고 파이썬은 메소드의 인자가 리스트인지 아닌지 상관하지 않기 때문에 작동합니다. Python은 iterable을 기대하므로 문자열, 목록, 튜플 및 생성기와 함께 작동합니다! 이것을 덕 타이핑이라고 하며 파이썬이 멋진 이유 중 하나입니다. 그러나 이것은 또 다른 이야기입니다. 다른 질문에 대해...
여기에서 멈추거나 생성기의 고급 사용을 보려면 조금 읽을 수 있습니다.
발전기 고갈 제어
>>> 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을 조작하는 특수 함수가 포함되어 있습니다. 발전기를 복제하고 싶으십니까? 2개의 발전기를 연결하시겠습니까? 한 줄짜리 중첩 목록의 값을 그룹화하시겠습니까? 다른 목록을 만들지 않고 Map / Zip
yield 문이 있는 함수를 볼 때 다음과 같은 쉬운 트릭을 적용하여 어떤 일이 일어날지 이해하십시오.
함수 시작 부분에 줄 result = []
각 yield expr 을 result.append(expr) 바꿉니다.
함수의 맨 아래에 줄 return result
예 - 더 이상 yield 문은 없습니다! 코드를 읽고 파악합니다.
기능을 원래 정의와 비교하십시오.
이 트릭은 함수 이면의 논리에 대한 아이디어를 제공할 수 있지만 실제로 yield 발생하는 일은 목록 기반 접근 방식에서 발생하는 것과 크게 다릅니다. 많은 경우에 yield 접근 방식은 훨씬 더 메모리 효율적이고 빠릅니다. 다른 경우에는 원래 기능이 제대로 작동하더라도 이 트릭을 사용하면 무한 루프에 빠지게 됩니다. 자세히 알아보려면 계속 읽으십시오...
Iterable, Iterator 및 Generator를 혼동하지 마십시오.
첫째, 반복자 프로토콜 - 작성할 때
for x in mylist: ...loop body...
Python은 다음 두 단계를 수행합니다.
mylist 대한 반복자를 가져옵니다.
iter(mylist) 호출 next() 메서드(또는 Python 3의 __next__() 가 있는 객체를 반환합니다.
[이것은 대부분의 사람들이 당신에게 말하는 것을 잊는 단계입니다]
반복자를 사용하여 항목을 반복합니다.
호출 유지 next() 에서 1 단계로의 반환 값을 반환 반복기에있어서 next() 에 할당 x 루프 본체가 실행된다. next() 내에서 StopIteration 예외가 발생하면 반복자에 더 이상 값이 없고 루프가 종료됨을 의미합니다.
진실은 Python이 객체의 내용을 반복 하고 싶을 때마다 위의 두 단계를 수행한다는 것입니다. 따라서 for 루프일 수 있지만 otherlist.extend(mylist) 와 같은 코드일 수도 있습니다(여기서 otherlist 는 Python 목록) .
여기서 mylist 는 iterator 프로토콜을 구현하기 때문에 iterable입니다. 사용자 정의 클래스에서 __iter__() 메서드를 구현하여 클래스의 인스턴스를 반복 가능하게 만들 수 있습니다. 이 메서드는 iterator를 반환해야 합니다. next() 메서드가 있는 객체입니다. 모두 구현할 수 __iter__() 및 next() 같은 클래스를하고있다 __iter__() 반환 self . 이것은 간단한 경우에는 작동하지만 동일한 객체에 대해 두 개의 반복자를 동시에 반복하려는 경우에는 작동하지 않습니다.
이것이 반복자 프로토콜이며 많은 객체가 이 프로토콜을 구현합니다.
내장 목록, 사전, 튜플, 집합, 파일.
__iter__() 를 구현하는 사용자 정의 클래스.
발전기.
for 루프는 어떤 종류의 객체를 다루고 있는지 알지 못합니다. 반복자 프로토콜을 따르고 next() 호출할 때 항목 다음에 항목을 가져오는 것을 기쁘게 생각합니다. 기본 제공 목록은 항목을 하나씩 반환하고, 사전은 키를 하나씩 반환하고, 파일은 줄을 하나씩 반환합니다. 그리고 생성기는 반환합니다. 바로 이것이 yield 가 들어오는 곳입니다.
def f123(): yield 1 yield 2 yield 3 for item in f123(): print item
yield 문 대신 f123() 에 세 개의 return 문이 있는 경우 첫 번째 문만 실행되고 함수가 종료됩니다. 그러나 f123() 은 일반적인 함수가 아닙니다. f123() 이 호출되면 yield 문의 값을 반환 하지 않습니다! 생성자 객체를 반환합니다. 또한 함수는 실제로 종료되지 않고 일시 중단된 상태가 됩니다. for 루프가 생성기 객체에 대해 루프를 시도할 때 yield 이후 바로 다음 줄에서 일시 중단된 상태에서 다시 시작하고 다음 코드 줄(이 경우에는 yield 문)을 실행하고 다음을 반환합니다. 다음 항목으로. 이것은 함수가 종료될 때까지 발생합니다. 이 시점에서 생성기는 StopIteration 발생시키고 루프가 종료됩니다.
따라서 생성기 객체는 일종의 어댑터와 같습니다. 한쪽 끝에는 for 루프를 만족스럽게 유지하기 __iter__() 및 next() 그러나 다른 쪽 끝에서는 다음 값을 가져올 만큼만 함수를 실행하고 일시 중단 모드로 되돌립니다.
제너레이터를 사용하는 이유
일반적으로 생성기를 사용하지 않지만 동일한 논리를 구현하는 코드를 작성할 수 있습니다. 한 가지 옵션은 앞에서 언급한 임시 목록 '트릭'을 사용하는 것입니다. 예를 들어 무한 루프가 있거나 정말 긴 목록이 있을 때 메모리를 비효율적으로 사용할 수 있는 경우와 같이 모든 경우에 작동하지는 않습니다. next() (또는 Python 3의 __next__() ) 메서드에서 다음 논리적 단계를 수행하는 새로운 반복 가능한 클래스 SomethingIter를 구현하는 것입니다. 논리에 따라 next() 메서드 내부의 코드는 매우 복잡해 보이고 버그가 발생하기 쉽습니다. 여기에서 발전기는 깨끗하고 쉬운 솔루션을 제공합니다.
답변자 : Jason Baker
다음과 같이 생각하십시오.
next() 메서드가 있는 객체에 대한 멋진 용어입니다. 따라서 yield-ed 함수는 다음과 같이 됩니다.
원본 버전:
def some_function(): for i in xrange(4): yield i for i in some_function(): print i
이것은 기본적으로 Python 인터프리터가 위의 코드로 수행하는 작업입니다.
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 루프를 다음과 같이 다시 작성할 수 있습니다.
컴파일러는 감지하면 yield 비아 함수 내부의 키워드 어디서나, 그 기능을 더 이상 반환 return 문을. 대신 생성기라고 하는 게으른 "보류 목록" 개체를즉시 반환합니다.
생성기는 반복 가능합니다. 반복 가능 이란 무엇입니까? 특정 순서로 각 요소를 방문하기 위한 내장 프로토콜이 있는 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]]
예시
Python의 rangemakeRange 함수를 정의해 보겠습니다. 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]
"그냥 목록을 반환"에 대한 예제 비교
위의 예는 단순히 추가하고 반환하는 목록을 만드는 것으로 생각할 수 있습니다.
# return a list # # return a generator 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 return TO_RETURN # >>> makeRange(5) [0, 1, 2, 3, 4]
그러나 한 가지 중요한 차이점이 있습니다. 마지막 섹션을 참조하십시오.
발전기를 사용하는 방법
iterable은 list comprehension의 마지막 부분이며 모든 생성기는 iterable이므로 종종 다음과 같이 사용됩니다.
# < ITERABLE > >>> [x+10 for x in makeRange(5)] [10, 11, 12, 13, 14]
제너레이터에 대한 더 나은 느낌을 얻으려면 itertools 모듈을 가지고 놀 수 있습니다(보증되는 경우 chainchain.from_iterable 을 사용하십시오). itertools.count() 와 같이 무한히 긴 지연 목록을 구현하기 위해 생성기를 사용할 수도 있습니다. def enumerate(iterable): zip(count(), iterable) 을 구현하거나 yield 키워드를 사용하여 구현할 수 있습니다.
참고: 제너레이터는 실제로 코루틴 구현 , 비결정적 프로그래밍 또는 기타 우아한 작업과 같은 더 많은 작업에 사용될 수 있습니다. 그러나 여기서 내가 제시하는 "게으른 목록" 관점은 가장 일반적으로 사용되는 것입니다.
무대 뒤에서
이것이 "Python 반복 프로토콜"이 작동하는 방식입니다. list(makeRange(5)) 를 수행할 때 무슨 일이 일어나고 있는지입니다. 이것은 앞서 "게으른 증분 목록"으로 설명한 것입니다.
내장 함수 next().next() 함수를 호출합니다. 이 함수는 "반복 프로토콜"의 일부이며 모든 반복기에서 찾을 수 있습니다. 일반적으로 가독성을 희생시키면서 멋진 것을 구현 next() 함수(및 반복 프로토콜의 다른 부분)를 수동으로 사용할 수 있으므로 그렇게 하지 마십시오...
사소한 점
일반적으로 대부분의 사람들은 다음과 같은 구분에 대해 관심이 없으며 여기에서 읽기를 중단하고 싶을 것입니다.
파이썬에서 iterable[1,2,3] 과 같이 "for-loop의 개념을 이해하는" 객체이고, iterator[1,2,3].__iter__() 와 같이 요청된 for-loop의 특정 인스턴스입니다. [1,2,3].__iter__() . 제너레이터 는 작성된 방식(함수 구문 포함)을 제외하고 모든 반복자와 정확히 동일합니다.
목록에서 반복자를 요청하면 새 반복자가 생성됩니다. 그러나 반복자에게 반복자를 요청하면(거의 하지 않을 것입니다), 그것은 단지 당신에게 자신의 복사본을 제공합니다.
... 그런 다음 생성기가 반복자 임을 기억하십시오. 즉, 일회용입니다. 다시 사용하려면 myRange(...) 다시 호출해야 합니다. 결과를 두 번 사용해야 하는 경우 결과를 목록으로 변환하고 변수 x = list(myRange(5)) 합니다. 절대적으로 생성기를 복제해야 하는 사람들(예를 들어, 끔찍하게 해킹된 메타프로그래밍을 하는 사람들)은 복사 가능한 반복기 Python PEP 표준 제안 이 연기되었기 때문에 절대적으로 필요한 경우 itertools.tee ( 여전히 Python 3에서 작동)를 사용할 수 있습니다.
Python 3에서는 yield from 사용하여 양방향으로 한 생성기에서 다른 생성기로 위임 할 수 있습니다.
(부록은 맨 위의 답변을 포함하여 몇 가지 답변을 비판하고 생성기에서 return
발전기:
yield 는 함수 정의 내에서만 유효하며 함수 정의 에 yield 를 포함하면 생성기를 반환합니다.
생성기에 대한 아이디어는 구현이 다양한 다른 언어(각주 1 참조)에서 비롯됩니다. Python의 Generators에서 코드 실행은 yield 지점에서 고정됩니다. 제너레이터가 호출되면(메소드가 아래에 설명됨) 실행이 재개되고 다음 수율에서 멈춥니다.
yield__iter__ 및 next (Python 2) 또는 __next__ (Python 3)의 두 가지 방법으로 정의된 반복자 프로토콜 을 구현하는 쉬운 방법을 제공합니다. collections 모듈 Iterator 추상 기본 클래스로 유형 검사할 수 있는 개체를 반복자로 만듭니다.
>>> 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.
yield from 이 Python 3에서 사용 가능하다는 것을 기억하십시오. 이를 통해 코루틴을 서브코루틴에 위임할 수 있습니다.
def money_manager(expected_rate): # must receive deposited value from .send(): under_management = yield # yield None to start. 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''' raise 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''' # must queue up manager: next(manager) # <- same as manager.send(None) # This is where we send the initial deposit to the manager: manager.send(deposited) try: yield from manager except GeneratorExit: return manager.close() # delegate?
Traceback (most recent call last): File "<stdin>", line 4, in <module> File "<stdin>", line 6, in money_manager File "<stdin>", line 2, in <module> ValueError
결론
나는 다음 질문의 모든 측면을 다뤘다고 생각합니다.
yield 키워드는 Python에서 무엇을 합니까?
yield 이 많은 것으로 나타났습니다. 여기에 더 자세한 예를 추가할 수 있다고 확신합니다. 더 많은 것을 원하거나 건설적인 비판이 있으면 아래에 의견을 말하여 알려주십시오.
부록:
최고/승인된 답변에 대한 비판**
목록을 예로 사용하여 iterable 을 만드는 것이 무엇인지 혼란스럽습니다. 위의 내 참조를 참조하십시오. 그러나 요약하면 iterable에는 iterator를 반환하는 __iter__ 메서드가 있습니다. 반복자 는 .next (Python 2 또는 .__next__ (Python 3)) 메서드를 제공합니다. 이 메서드는 StopIteration 때까지 for 루프에 의해 암시적으로 호출되고 일단 발생하면 계속 그렇게 합니다.
그런 다음 생성기 표현식을 사용하여 생성기가 무엇인지 설명합니다. 제너레이터는 단순히 iterator 를 생성하는 편리한 방법이기 때문에 문제를 혼란스럽게 할 뿐이며 아직 yield 부분에 도달하지 못했습니다.
제너레이터 고갈 제어에서.next 메서드를 호출합니다. 대신 내장 함수인 next 사용해야 합니다. 그의 코드가 Python 3에서 작동하지 않기 때문에 적절한 간접 참조 계층이 될 것입니다.
이터툴? yield 가 하는 일과 전혀 관련이 없었습니다.
그 방법의 토론 yield 새로운 기능과 함께 제공 yield from 파이썬 3. 상단은 / 허용 대답은 매우 불완전 답변을하지 않습니다.
CLU, Sather 및 Icon 언어는 Python에 생성기 개념을 도입하기 위한 제안에서 참조되었습니다. 일반적인 아이디어는 함수가 내부 상태를 유지하고 사용자의 요구에 따라 중간 데이터 포인트를 생성할 수 있다는 것입니다. 이것은 일부 시스템에서조차 사용할 수 없는 Python 스레딩을 포함한 다른 접근 방식보다 성능 이 월등하다는 것을 약속했습니다.
이것은 예를 들어 range 객체가 반복 가능하더라도 재사용할 수 있기 때문에 Iterator 가 아님을 의미합니다. 목록과 마찬가지로 __iter__ 메서드는 반복자 객체를 반환합니다.
yieldreturn 과 같습니다. (제너레이터로) 지시한 대로 반환합니다. 차이점은 다음에 생성기를 호출할 때 yield 문에 대한 마지막 호출부터 실행이 시작된다는 것입니다. 반환과 달리 스택 프레임은 yield가 발생할 때 정리되지 않지만 제어가 호출자에게 다시 전송되므로 다음에 함수가 호출될 때 상태가 다시 시작됩니다.
코드의 경우 get_child_candidates 함수는 반복자처럼 작동하므로 목록을 확장할 때 새 목록에 한 번에 하나의 요소를 추가합니다.
list.extend 는 소진될 때까지 반복자를 호출합니다. 게시한 코드 샘플의 경우 튜플을 반환하고 이를 목록에 추가하는 것이 훨씬 더 명확할 것입니다.
답변자 : Claudiu
한 가지 추가로 언급할 사항이 있습니다. 산출하는 함수는 실제로 종료할 필요가 없습니다. 다음과 같은 코드를 작성했습니다.
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);
일부 문제를 단순화하는 데 실제로 도움이 되며 일부 작업을 더 쉽게 수행할 수 있습니다.
답변자 : Daniel
최소한의 작업 예제를 선호하는 사람들을 위해 이 대화형 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
답변자 : Bob Stein
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.
>>> for square in square_list(4): ... print(square) ... 0 1 4 9 >>> for square in square_yield(4): ... print(square) ... 0 1 4 9
다른 행동:
Yield is single-pass : 한 번만 반복할 수 있습니다. 함수에 yield가 있을 때 우리는 그것을 생성기 함수 라고 부릅니다. 그리고 iterator 는 반환하는 것입니다. 그 용어들이 드러나고 있습니다. 우리는 컨테이너의 편리함을 잃어버렸지만, 필요에 따라 계산되고 임의로 긴 시리즈의 힘을 얻습니다.
Yield is lazy , 계산이 지연됩니다. yield가 있는 함수는 호출할 때 실제로 전혀 실행되지 않습니다. 중단된 위치를 기억 하는 반복자 객체 를 반환합니다. next() 를 호출할 때마다(for-loop에서 발생) 실행은 다음 수율로 앞으로 나아갑니다. return 은 StopIteration을 발생시키고 시리즈를 종료합니다(이것은 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
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 루프 등에서 사용할 수 있지만 각 값은 한 번만 액세스할 수 있습니다. 모든 값은 동시에 메모리에 저장되지 않습니다. 제너레이터 객체는 마지막으로 호출했을 때 루프의 위치를 "기억"합니다. 이렇게 하면 iterable을 사용하여 500억까지 세는 경우 500억까지 모두 세지 않아도 됩니다. 한 번에 500억 개의 숫자를 저장하여 셀 수 있습니다.
다시 말하지만, 이것은 꽤 인위적인 예입니다. 정말로 500억까지 세고 싶다면 itertools를 사용할 것입니다. :)
이것은 생성기의 가장 간단한 사용 사례입니다. 말했듯이 일종의 스택 변수를 사용하는 대신 yield를 사용하여 호출 스택을 통해 항목을 푸시하여 효율적인 순열을 작성하는 데 사용할 수 있습니다. 제너레이터는 특수 트리 순회 및 기타 모든 작업에도 사용할 수 있습니다.
핵심 아이디어는 컴파일러/인터프리터/무엇이든 약간의 속임수를 수행하여 호출자에 관한 한 그들은 next()를 계속 호출할 수 있고 생성기 메서드가 일시 중지된 것처럼 값을 계속 반환한다는 것입니다. 이제 분명히 메소드를 "일시 중지"할 수 없으므로 컴파일러는 현재 위치와 로컬 변수 등이 어떻게 생겼는지 기억할 수 있도록 상태 머신을 빌드합니다. 이것은 반복자를 직접 작성하는 것보다 훨씬 쉽습니다.
답변자 : aestrivex
제너레이터 사용 방법을 설명하는 많은 훌륭한 답변 중 아직 제공되지 않은 한 가지 유형의 답변이 있습니다. 프로그래밍 언어 이론 답변은 다음과 같습니다.
Python의 yield 문은 생성기를 반환합니다. Python의 생성기는 연속 (특히 코루틴 유형이지만 연속은 진행 상황을 이해하기 위한 보다 일반적인 메커니즘을 나타냄)을 반환하는 함수입니다.
프로그래밍 언어 이론의 연속은 훨씬 더 기본적인 종류의 계산이지만 추론하기 매우 어렵고 구현하기 매우 어렵기 때문에 자주 사용되지 않습니다. 그러나 연속이 무엇인지에 대한 아이디어는 간단합니다. 아직 완료되지 않은 계산 상태입니다. 이 상태에서 변수의 현재 값, 아직 수행되지 않은 작업 등이 저장됩니다. 그런 다음 프로그램의 나중에 어떤 지점에서 계속을 호출하여 프로그램의 변수를 해당 상태로 재설정하고 저장된 작업을 수행할 수 있습니다.
이 보다 일반적인 형태의 연속은 두 가지 방법으로 구현될 수 있습니다. call/cc 방식에서는 프로그램의 스택이 말 그대로 저장되고 계속이 호출되면 스택이 복원됩니다.
연속 전달 스타일(CPS)에서 연속은 프로그래머가 명시적으로 관리하고 서브루틴으로 전달하는 일반 함수(함수가 일급인 언어에서만)입니다. 이 스타일에서 프로그램 상태는 스택의 어딘가에 상주하는 변수가 아니라 클로저(및 그 안에 인코딩된 변수)로 표시됩니다. 제어 흐름을 관리하는 함수는 연속을 인수로 수락하고(일부 CPS 변형에서는 함수가 여러 연속을 수락할 수 있음) 단순히 호출하고 나중에 반환하여 호출하여 제어 흐름을 조작합니다. 연속 전달 스타일의 매우 간단한 예는 다음과 같습니다.
이 (매우 단순한) 예에서 프로그래머는 실제로 파일을 연속으로 작성하는 작업을 저장하고(이는 잠재적으로 작성해야 할 세부 사항이 많은 매우 복잡한 작업일 수 있음) 해당 연속을 전달합니다(즉, 첫 번째로 클래스 클로저)를 더 많은 처리를 수행한 다음 필요한 경우 호출하는 다른 연산자에 연결합니다. (저는 이 디자인 패턴을 실제 GUI 프로그래밍에서 많이 사용합니다. 코드 줄을 절약하거나 더 중요하게는 GUI 이벤트가 트리거된 후 제어 흐름을 관리하기 때문입니다.)
이 게시물의 나머지 부분에서는 일반성을 잃지 않고 연속 작업을 CPS로 개념화할 것입니다. 왜냐하면 이해하고 읽기가 훨씬 더 쉽기 때문입니다.
이제 Python의 제너레이터에 대해 이야기해 보겠습니다. 생성기는 연속의 특정 하위 유형입니다. 연속 작업은 일반적으로 계산 상태(즉, 프로그램의 호출 스택)를 저장할 수 있는 반면 , 제너레이터는 반복자에 대해서만 반복 상태를 저장할 수 있습니다. 그러나 이 정의는 생성기의 특정 사용 사례에 대해 약간 오해의 소지가 있습니다. 예를 들어:
def f(): while True: yield 4
이것은 동작이 잘 정의된 합리적인 iterable입니다. 제너레이터가 반복할 때마다 4를 반환합니다(영원히 그렇게 함). 그러나 iterators를 생각할 때 마음에 떠오르는 것은 아마도 iterable의 원형 유형이 아닐 것입니다(즉, for x in collection: do_something(x) ). 이 예제는 제너레이터의 힘을 보여줍니다. 어떤 것이 반복자인 경우 제너레이터는 반복 상태를 저장할 수 있습니다.
반복하자면: Continuation은 프로그램 스택의 상태를 저장할 수 있고 Generator는 반복의 상태를 저장할 수 있습니다. 이것은 연속이 제너레이터보다 훨씬 강력하지만 제너레이터가 훨씬 훨씬 쉽다는 것을 의미합니다. 언어 디자이너가 구현하기가 더 쉽고 프로그래머가 사용하기가 더 쉽습니다(시간을 할애할 시간이 있다면 이 페이지에서 계속 및 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 키워드는 실제로 실제 생성기 함수에 대한 구문 설탕이며 기본적으로 다음과 같습니다.
이것은 단지 의사 코드이고 Python에서 생성기의 실제 구현은 더 복잡하다는 것을 기억하십시오. yield 키워드를 사용하지 않고 제너레이터 객체를 구현하기 위해 연속 전달 스타일을 사용해 보십시오.
답변자 : tzot
다음은 일반 언어로 된 예입니다. 나는 높은 수준의 인간 개념과 낮은 수준의 Python 개념 간의 대응을 제공할 것입니다.
일련의 숫자에 대해 작업을 수행하고 싶지만 해당 시퀀스를 만드는 데 신경을 쓰고 싶지는 않습니다. 하고 싶은 작업에만 집중하고 싶습니다. 그래서 저는 다음을 수행합니다.
나는 당신에게 전화를 걸어 특정한 방식으로 생성된 일련의 숫자를 원한다고 말하고 알고리즘이 무엇인지 알려줍니다. 이 단계는 대응 def 즉, 함유 함수 발생기 기능 ining yield .
잠시 후, "좋아요, 숫자의 순서를 말해 줄 준비를 하세요"라고 말합니다. 이 단계는 제너레이터 객체를 반환하는 제너레이터 함수를 호출하는 것에 해당합니다. 아직 숫자를 알려주지 않았습니다. 당신은 당신의 종이와 연필을 잡습니다.
"다음 번호를 알려주세요"라고 물으면 첫 번째 번호를 알려줍니다. 그 후, 당신은 내가 다음 번호를 물어볼 때까지 기다립니다. 당신이 어디에 있었는지, 당신이 이미 말한 숫자와 다음 숫자가 무엇인지 기억하는 것은 당신의 일입니다. 나는 세부 사항에 관심이 없습니다. 이 단계는 생성기 객체에서 .next() 를 호출하는 것에 해당합니다.
... 이전 단계를 반복할 때까지...
결국, 당신은 끝날 수 있습니다. 번호를 알려주지 않습니다. 당신은 "말을 잡아! 난 끝났어! 더 이상 숫자가 없어!"라고 외치면 됩니다. 이 단계는 작업을 종료하고 StopIteration 예외를 발생시키는 생성기 객체에 해당합니다. 생성기 함수는 예외를 발생시킬 필요가 없습니다. return 실행할 때 자동으로 발생합니다.
이것이 제너레이터가 하는 일입니다( yield 를 포함하는 함수). yield 수행할 때마다 일시 중지 .next() 값을 요청하면 마지막 지점부터 계속됩니다. 순차적으로 값을 요청하는 방법을 설명하는 Python의 반복자 프로토콜과 완벽하게 일치합니다.
iterator 프로토콜의 가장 유명한 사용자는 for 명령입니다. 따라서 다음을 수행할 때마다:
for item in sequence:
sequence 가 위에서 설명한 것처럼 목록, 문자열, 사전 또는 생성기 개체 인지 여부는 중요하지 않습니다. 결과는 동일합니다. 시퀀스에서 항목을 하나씩 읽습니다.
참고 def 포함 된 기능 ining yield 키워드하는 발전기를 만들 수있는 유일한 방법은 아니다; 생성하는 가장 쉬운 방법입니다.
당신이 사용하는 거라고 왜 답변을 많이 보여하지만 yield 발전기를 만들려면 더 많은 용도가있다 yield . 두 코드 블록 사이에 정보를 전달할 수 있는 코루틴을 만드는 것은 매우 쉽습니다. 생성기를 생성하기 위해 yield 를 사용하는 것에 대해 이미 제공된 훌륭한 예는 반복하지 않겠습니다.
도움이되는 이해 yield 다음 코드에서와, 당신은이 코드를 통해주기를 추적하기 위해 손가락을 사용할 수 yield . yield 칠 때마다 next 또는 send 가 입력될 때까지 기다려야 합니다. 하면 next 라고 당신이 명중 할 때까지, 당신은 코드를 통해 추적 yield 의 오른쪽에있는 코드 ... yield 평가 호출자에게 리턴됩니다 ... 당신은 기다립니다. next 가 다시 호출되면 코드를 통해 다른 루프를 수행합니다. 그러나, 당신은 코 루틴에 있습니다 것 yield 도 사용할 수 있습니다 send 항복 함수로 호출에서 값을 보내드립니다 .... send 가 주어지면 yield 는 보낸 값을 받고 왼쪽으로 뱉어냅니다. 그런 다음 코드를 통한 추적은 yieldnext 이 호출된 것처럼 끝에 값을 반환).
예를 들어:
>>> 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()
코루틴이 일반 생성기와 혼동되는 것을 피하기 위해(오늘날 yield 는 둘 다에서 사용됩니다).
답변자 : AbstProcDo
모든 훌륭한 답변이지만 초보자에게는 약간 어렵습니다.
return 문을 배웠다고 가정합니다.
비유하자면 return 과 yield 은 쌍둥이입니다. return 은 '돌아가서 중지'를 의미하는 반면 'yield'는 '돌아가지만 계속'을 의미합니다.
return 으로 num_list를 얻으십시오.
def num_list(n): for i in range(n): return i
실행:
In [5]: num_list(3) Out[5]: 0
보십시오, 당신은 그것들의 목록이 아니라 오직 하나의 숫자만을 얻습니다. return 결코 행복하게 승리하도록 허용하지 않으며, 한 번만 구현하고 종료합니다.
yield 이 온다
return 을 yield 바꿉니다.
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 실행 횟수가 계획한 시간입니다. return one of themreturn 으로 해석하고 yield 는 return all of them return 으로 해석할 수 있습니다. 이것을 iterable 이라고 합니다.
returnyield 문을 다시 작성할 수 있는 한 단계 더
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 출력'에서 한 번만 검색할 수 있습니다. Out[11]: <generator object num_list at 0x10327c990> 표시된 대로 새 이름 generator 개체가 있습니다.
이 계산을 구현하는 방법은 많이 있습니다. 나는 돌연변이를 사용했지만 현재 값과 다음 yielder를 반환함으로써 돌연변이 없이 이런 종류의 계산을 수행할 수 있습니다( 참조 투명하게 만듦). Racket은 일부 중개 언어에서 초기 프로그램의 일련의 변환을 사용합니다. 이러한 재작성 중 하나는 yield 연산자가 더 간단한 연산자를 사용하여 일부 언어로 변환되도록 합니다.
다음은 R6RS의 구조를 사용하는 yield를 다시 작성할 수 있는 방법에 대한 데모입니다. 그러나 의미는 Python과 동일합니다. 동일한 계산 모델이며 Python의 yield를 사용하여 다시 작성하려면 구문만 변경하면 됩니다.
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)
답변자 : johnzachary
나는 "제너레이터에 대한 빠른 설명을 위해 Beazley의 'Python: Essential Reference'의 19페이지 읽기"를 게시하려고 했지만 너무 많은 다른 사람들이 이미 좋은 설명을 게시했습니다.
또한 yield 는 코루틴에서 제너레이터 기능의 이중 사용으로 사용될 수 있습니다. 코드 조각과 같은 용도는 아니지만 (yield) 는 함수의 표현식으로 사용할 수 있습니다. send() 메서드를 사용하여 메서드에 값을 보낼 때 코루틴은 다음 (yield) 문이 만날 때까지 실행됩니다.
생성기와 코루틴은 데이터 흐름 유형 애플리케이션을 설정하는 멋진 방법입니다. yield 문을 다른 용도로 사용하는 방법에 대해 아는 것이 가치가 있다고 생각했습니다.
답변자 : Engin OZTURK
다음은 간단한 예입니다.
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)
저는 Python 개발자는 아니지만 yield 는 프로그램 흐름의 위치를 유지하고 다음 루프는 "yield" 위치에서 시작하는 것으로 보입니다. 그 위치에서 기다리고 있는 것 같고, 그 직전에 외부에서 값을 반환하고 다음 번에 계속 작동합니다.
재미있고 좋은 능력인 것 같습니다 :D
답변자 : Evgeni Sergeev
yield 가 하는 일에 대한 정신적 이미지입니다.
스레드를 스택이 있는 것으로 생각하고 싶습니다(그렇게 구현되지 않은 경우에도).
일반 함수가 호출되면 로컬 변수를 스택에 넣고 일부 계산을 수행한 다음 스택을 지우고 반환합니다. 지역 변수의 값은 다시는 볼 수 없습니다.
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_code 및 gi_frame 필드는 고정 상태가 저장되는 곳입니다. dir(..) 하여 탐색하면 위의 멘탈 모델이 신뢰할 수 있음을 확인할 수 있습니다.
답변자 : Gavriel Cohen
그것이 무엇인지 이해하는 쉬운 예: 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
답변자 : Mangu Singh Rajpurohit
모든 답변에서 알 수 있듯이 yield 는 시퀀스 생성기를 만드는 데 사용됩니다. 일부 시퀀스를 동적으로 생성하는 데 사용됩니다. 예를 들어 네트워크에서 파일을 한 줄씩 읽는 동안 다음과 같이 yield
def getNextLines(): while con.isOpen(): yield con.read()
다음과 같이 코드에서 사용할 수 있습니다.
for line in getNextLines(): doSomeThing(line)
실행 제어 전송
yield가 실행될 때 실행 제어는 getNextLines()에서 for 따라서 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"
답변자 : smwikipedia
(내 아래 답변은 스택 및 힙 조작의 일부 트릭을 포함하는 생성기 메커니즘의 기본 구현이 아니라 Python 생성기를 사용하는 관점에서만 말합니다.)
returnyield 를 사용하면 generator function 이라는 특별한 것으로 바뀝니다. generator 유형의 개체를 반환합니다. yield 키워드는 파이썬 컴파일러에게 이러한 기능을 특별히 취급하도록 알리는 플래그입니다. 정상적인 기능은 값이 반환되면 종료됩니다. 그러나 컴파일러의 도움으로 제너레이터 함수는 재개 가능한 것으로 생각할 수 있습니다. 즉, 실행 컨텍스트가 복원되고 마지막 실행부터 실행이 계속됩니다. 명시적으로 return을 호출할 때까지 StopIteration 예외(반복자 프로토콜의 일부이기도 함)를 발생시키거나 함수의 끝에 도달할 때까지. 나는 약 참조를 많이 찾을 generator 하지만,이 일 로부터 functional programming perspective 에서 가장 digestable입니다.
(지금은 뒤에있는 근거에 대해 얘기하고 싶지 generator 하고, iterator 내 자신의 이해를 기반으로. 나는 이것이 당신이 반복자와 발전기의 필수 의욕을 파악하는 데 도움 수 있기를 바랍니다.뿐만 아니라 같은 C #을 다른 언어로 이러한 개념 쇼까지.)
제가 알기로는 많은 양의 데이터를 처리할 때 일반적으로 먼저 데이터를 어딘가에 저장한 다음 하나씩 처리합니다. 그러나 이러한 순진한 접근 방식은 문제가 있습니다. 데이터 볼륨이 크면 미리 전체를 저장하는 데 비용이 많이 듭니다. data 자체를 직접 저장하는 대신 metadata , 즉 the logic how the data is computed 간접적으로 저장하지 않는 이유는 무엇입니까 ?
이러한 메타데이터를 래핑하는 방법에는 2가지가 있습니다.
OO 접근 방식에서는 메타데이터 as a class 래핑합니다. 이것은 반복자 프로토콜(예: __next__() 및 __iter__() 메서드) iterator 이것은 또한 일반적으로 볼 수 있는 반복자 디자인 패턴 입니다.
기능적 접근 방식에서는 메타데이터 as a function 래핑합니다. 이것은 소위 generator function 입니다. 그러나 내부적으로 반환된 generator object 는 반복자 프로토콜도 구현하기 때문에 여전히 IS-A
어느 쪽이든, 반복자가 생성됩니다. 즉, 원하는 데이터를 제공할 수 있는 일부 객체입니다. OO 접근 방식은 약간 복잡할 수 있습니다. 어쨌든, 어느 것을 사용할지는 당신에게 달려 있습니다.
답변자 : redbandit
요약하면, yield 문은 원래 함수의 본체를 감싸는 generator 라는 특수 객체를 생성하는 팩토리로 함수를 변환합니다. generatoryield 도달할 때까지 함수를 실행한 다음 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' 문에 도달할 때까지 함수를 실행하여 중지하고 값을 'yield'하거나 함수의 끝에 도달합니다. 이 경우 첫 번째 호출에서 next() 는 yield 문까지 실행하고 'n'을 산출하고, 다음 호출에서는 증분 문을 실행하고 'while'로 다시 점프하여 평가하고 true인 경우 중지하고 다시 'n'을 생성하며 while 조건이 false를 반환하고 생성기가 함수의 끝으로 점프할 때까지 계속됩니다.
답변자 : Kaleem Ullah
수확량은 개체입니다
return 함수에 단일 값을 반환합니다.
함수가 엄청난 양의 값을 반환하도록yield 사용하십시오.
더 중요한 것은 yield 이 장벽 입니다.
CUDA 언어의 barrier와 마찬가지로 완료될 때까지 제어를 전송하지 않습니다.
yield 도달할 때까지 함수의 코드를 실행합니다. 그런 다음 루프의 첫 번째 값을 반환합니다.
그런 다음 다른 모든 호출은 함수에 작성한 루프를 한 번 더 실행하여 반환할 값이 없을 때까지 다음 값을 반환합니다.
답변자 : Tom Fuller
많은 사람들이 yieldreturn 사용하지만, 어떤 경우에는 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)
두 함수 모두 동일한 작업을 수행하지만 yield 는 5줄 대신 3줄을 사용하고 걱정할 변수가 하나 적습니다.
다음은 코드의 결과입니다.
보시다시피 두 기능은 동일한 작업을 수행합니다. 유일한 차이점은 return_dates() 가 목록을 제공하고 yield_dates() 가 생성기를 제공한다는 것입니다.
실제 예는 파일을 한 줄씩 읽는 것과 같거나 생성기를 만들고 싶은 경우입니다.
답변자 : Will Dereham
yield 는 함수의 반환 요소와 같습니다. 차이점은 yield 요소가 함수를 생성기로 변환한다는 것입니다. 생성기는 무언가가 '양보'될 때까지 함수처럼 작동합니다. 제너레이터는 다음에 호출될 때까지 멈추고 시작된 것과 정확히 같은 지점에서 계속됩니다. list(generator()) 호출하여 모든 'yielded' 값의 시퀀스를 하나로 얻을 수 있습니다.
답변자 : Bahtiyar Özdere
yield 키워드는 단순히 반환 결과를 수집합니다. return += 와 같은 yield 생각하십시오.
답변자 : Christophe Roussy
또 다른 TL;DR
목록의 반복자 : next() 는 목록의 다음 요소를 반환합니다.
반복자 생성기 : next() 는 즉시 다음 요소를 계산합니다(코드 실행).
yield/generator는 흐름이 아무리 복잡하더라도 next를 호출하여 외부에서 제어 흐름 을 수동으로 실행하는 방법으로 볼 수 있습니다(예: 루프 한 단계 계속).
참고 : 발전기는 정상적인 기능 이 아닙니다. 로컬 변수(스택)와 같이 이전 상태를 기억합니다. 자세한 설명은 다른 답변이나 기사를 참조하십시오. 생성기는 한 번만 반복 될 수 있습니다. yield 없이 할 수는 있지만 그렇게 좋지 않을 것이므로 '매우 좋은' 언어 설탕으로 간주될 수 있습니다.