[python] 파이썬에서 생성기 이해

나는 현재 파이썬 요리 책을 읽고 있으며 현재 발전기를보고 있습니다. 머리를 돌리기가 어렵다.

Java 배경에서 왔을 때 Java에 해당하는 것이 있습니까? 이 책은 ‘프로듀서 / 소비자’에 대해 이야기하고 있었지만 스레딩에 대한 생각을 들었습니다.

발전기 란 무엇이며 왜 사용 하시겠습니까? 어떤 책도 인용하지 않고 분명히 (책에서 직접적이고 알맞은 대답을 찾을 수 없다면). 관대 한 느낌이 든다면 아마도 예가있을 것입니다!



답변

참고 :이 게시물은 Python 3.x 구문을 가정합니다.

발전기는 단순히 당신이 호출 할 수있는 객체를 반환하는 함수입니다 next그것이 제기 할 때까지 모든 호출 것이, 어떤 값을 반환 등 StopIteration모든 값이 생성 된 것을 신호, 예외를. 이러한 객체를 반복 자라고합니다 .

일반 함수 return는 Java와 마찬가지로을 사용하여 단일 값을 반환합니다 . 그러나 파이썬에는이라는 대안이 yield있습니다. yield함수의 어느 곳에서나 사용 하면 생성기가됩니다. 이 코드를 준수하십시오.

>>> def myGen(n):
...     yield n
...     yield n + 1
... 
>>> g = myGen(6)
>>> next(g)
6
>>> next(g)
7
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

당신이 볼 수 있듯이, myGen(n)산출하는 기능입니다 nn + 1. 모든 next값 을 산출 할 때까지 모든 호출 은 단일 값을 산출합니다. for루프 next는 백그라운드에서 호출 되므로 다음과 같습니다.

>>> for n in myGen(6):
...     print(n)
... 
6
7

마찬가지로 특정 일반적인 유형의 생성기를 간결하게 설명하는 수단을 제공하는 생성기 표현식 이 있습니다 .

>>> g = (n for n in range(3, 5))
>>> next(g)
3
>>> next(g)
4
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

생성기 표현식은 목록 이해 와 매우 비슷 합니다 .

>>> lc = [n for n in range(3, 5)]
>>> lc
[3, 4]

생성기 객체는 한 번 생성 되지만 해당 코드는 한 번에 실행 되지 않습니다 . next실제로 코드를 실행 (일부)하는 호출 만 가능합니다 . yield명령문에 도달 하면 생성기에서 코드 실행이 중지 되고 값이 리턴됩니다. 다음에 호출 next하면 생성기가 마지막 이후에 남아있는 상태에서 실행이 계속됩니다 yield. 이는 정규 함수와의 근본적인 차이점입니다. 즉, “상단”에서 실행을 시작하고 값을 반환하면 상태를 버립니다.

이 주제에 관해 할 말이 더 있습니다. 예를 들어 send생성기로 다시 데이터를 보내는 것이 가능합니다 ( 참조 ). 그러나 그것은 발전기의 기본 개념을 이해할 때까지 살펴 보지 말 것을 제안하는 것입니다.

이제 물어볼 수 있습니다 : 왜 발전기를 사용합니까? 몇 가지 좋은 이유가 있습니다.

  • 특정 개념은 생성기를 사용하여 훨씬 간결하게 설명 할 수 있습니다.
  • 값 목록을 반환하는 함수를 만드는 대신 값을 즉시 생성하는 생성기를 작성할 수 있습니다. 이것은리스트를 구성 할 필요가 없다는 것을 의미하며, 결과 코드는 메모리 효율성이 높다는 것을 의미합니다. 이러한 방식으로 메모리에 맞추기에는 너무 큰 데이터 스트림을 설명 할 수도 있습니다.
  • 생성기는 무한 스트림 을 자연스럽게 표현할 수있는 방법을 제공 합니다. 예를 들어 피보나치 수를 고려하십시오 .

    >>> def fib():
    ...     a, b = 0, 1
    ...     while True:
    ...         yield a
    ...         a, b = b, a + b
    ... 
    >>> import itertools
    >>> list(itertools.islice(fib(), 10))
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    

    이 코드는 itertools.islice무한 스트림에서 유한 요소 수를 가져 오는 데 사용 됩니다. itertools고급 생성기를 작성하기위한 필수 도구이므로 모듈 의 기능을 잘 살펴 보는 것이 좋습니다 .


   Python <= 2.6 정보 : 위의 예 에서 지정된 객체 next의 메서드를 호출하는 함수입니다 __next__. 파이썬 <2.6 용도, 즉 하나는 약간 다른 기술 = o.next()대신이 next(o). Python 2.7에는 next()호출이 .next있으므로 2.7 에서 다음을 사용할 필요가 없습니다.

>>> g = (n for n in range(3, 5))
>>> g.next()
3


답변

생성기는 실제로 완료되기 전에 (데이터)를 반환하는 함수이지만 해당 시점에서 일시 중지되며 해당 시점에서 함수를 다시 시작할 수 있습니다.

>>> def myGenerator():
...     yield 'These'
...     yield 'words'
...     yield 'come'
...     yield 'one'
...     yield 'at'
...     yield 'a'
...     yield 'time'

>>> myGeneratorInstance = myGenerator()
>>> next(myGeneratorInstance)
These
>>> next(myGeneratorInstance)
words

등등. 생성기 (또는 하나)의 이점은 한 번에 하나의 데이터를 처리하기 때문에 대량의 데이터를 처리 할 수 ​​있다는 것입니다. 목록을 사용하면 과도한 메모리 요구 사항이 문제가 될 수 있습니다. 목록과 마찬가지로 생성기는 반복 가능하므로 동일한 방식으로 사용할 수 있습니다.

>>> for word in myGeneratorInstance:
...     print word
These
words
come
one
at
a
time

예를 들어 생성기는 무한대를 처리하는 다른 방법을 제공합니다.

>>> from time import gmtime, strftime
>>> def myGen():
...     while True:
...         yield strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
>>> myGeneratorInstance = myGen()
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:17:15 +0000
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:18:02 +0000   

제너레이터는 무한 루프를 캡슐화하지만 요청할 때마다 각 답변을 얻으므로 문제가되지 않습니다.


답변

우선, 생성기 라는 용어 는 원래 파이썬에서 다소 잘못 정의되어 많은 혼란을 초래했습니다. 아마도 반복자이터 러블을 의미 할 것입니다 ( 여기 참조 ). 그런 다음 파이썬에는 생성기 함수 (생성기 객체를 반환), 생성기 객체 (반복자) 및 생성기 표현식 (생성기 객체로 평가)이 있습니다.

제너레이터 용어집 에 따르면 이제 공식 용어는 제너레이터 가 “제너레이터 기능”의 줄임말 인 것으로 보입니다 . 과거에는 문서가 용어를 일관성없이 정의했지만 다행스럽게도이 용어는 수정되었습니다.

더 구체적으로 지정하지 않고 “발전기”라는 용어를 사용하지 않는 것이 좋습니다.


답변

제너레이터는 이터레이터를 만들기위한 속기라고 생각할 수 있습니다. 그것들은 Java Iterator처럼 동작합니다. 예:

>>> g = (x for x in range(10))
>>> g
<generator object <genexpr> at 0x7fac1c1e6aa0>
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> list(g)   # force iterating the rest
[3, 4, 5, 6, 7, 8, 9]
>>> g.next()  # iterator is at the end; calling next again will throw
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

이것이 도움이 되길 바랍니다.

최신 정보:

다른 많은 답변이 보여주는 것처럼 생성기를 만드는 다른 방법이 있습니다. 위의 예제와 같이 괄호 구문을 사용하거나 yield를 사용할 수 있습니다. 또 다른 흥미로운 특징은 제너레이터가 “무한”할 수 있다는 것입니다.

>>> def infinite_gen():
...     n = 0
...     while True:
...         yield n
...         n = n + 1
...
>>> g = infinite_gen()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
...


답변

이에 상응하는 Java는 없습니다.

다음은 약간의 고려 된 예입니다.

#! /usr/bin/python
def  mygen(n):
    x = 0
    while x < n:
        x = x + 1
        if x % 3 == 0:
            yield x

for a in mygen(100):
    print a

생성기에는 0에서 n까지 실행되는 루프가 있으며 루프 변수가 3의 배수이면 변수가 생성됩니다.

for루프가 반복 될 때마다 발전기가 실행됩니다. 생성기가 처음으로 실행되면 처음부터 시작하고 그렇지 않으면 이전에 생성 한 시간부터 계속됩니다.


답변

나는 프로그래밍 언어와 컴퓨팅에 대한 적절한 배경을 가진 사람들에게 스택 프레임 측면에서 생성기를 설명하고 싶습니다.

많은 언어에서 스택은 현재 스택 “프레임”입니다. 스택 프레임에는 해당 함수에 전달 된 인수를 포함하여 함수에 로컬 인 변수에 할당 된 공간이 포함됩니다.

함수를 호출하면 현재 실행 지점 ( “프로그램 카운터”또는 이와 동등한 기능)이 스택으로 푸시되고 새 스택 프레임이 생성됩니다. 그런 다음 실행은 호출되는 함수의 시작 부분으로 전송됩니다.

일반 함수를 사용하면 어느 시점에서 함수가 값을 반환하고 스택이 “팝핑”됩니다. 함수의 스택 프레임이 삭제되고 이전 위치에서 실행이 재개됩니다.

함수가 제너레이터 인 경우 yield 문을 사용하여 스택 프레임을 버리지 않고 반환 할 수 있습니다 . 함수 내의 지역 변수 값과 프로그램 카운터는 유지됩니다. 따라서 yield 문에서 실행을 계속하면서 생성기를 나중에 다시 시작할 수 있으며 더 많은 코드를 실행하고 다른 값을 반환 할 수 있습니다.

Python 2.5 이전에는 모든 생성자가 수행했습니다. 파이썬 2.5 다시 값을 전달하는 기능이 추가 발생기로도있다. 이렇게하면 전달 된 값은 생성기에서 제어 (및 값)를 일시적으로 리턴 한 yield 문에서 발생하는 표현식으로 사용할 수 있습니다.

생성기의 주요 장점은 스택 프레임을 폐기 할 때마다 일반 상태와 달리 함수의 “상태”가 유지된다는 것입니다. 두 번째 이점은 일부 기능 호출 오버 헤드 (스택 프레임 생성 및 삭제)를 피할 수 있다는 것입니다.


답변

Stephan202의 답변에 추가 할 수있는 유일한 것은 David Beazley의 PyCon ’08 프레젠테이션 “시스템 프로그래머를위한 생성자 트릭”을 보도록 권장하는 것입니다. 어딘가에. 이것은 “Python looks fun”에서 “이것이 내가 찾던 것”으로 나를 데려 간 것입니다. 그것은에서의 http://www.dabeaz.com/generators/ .