파이썬 생성기 함수를 무엇에 사용할 수 있습니까?


213

나는 파이썬을 배우기 시작했고 생성 함수, yield 문이있는 함수를 발견했습니다. 이러한 기능이 실제로 해결하는 데 어떤 유형의 문제가 있는지 알고 싶습니다.


6
아마도 우리가 'em
cregox

1
실제 예를 여기
기리

답변:


239

발전기는 게으른 평가를 제공합니다. 'for'를 사용하여 명시 적으로 또는 암시 적으로 반복하여 함수 또는 반복 구조로 전달하여 사용합니다. 생성기는 마치 목록을 반환하는 것처럼 여러 항목을 반환하는 것으로 생각할 수 있지만 한 번에 하나씩 반환하는 대신 하나씩 반환하며 생성기 기능은 다음 항목이 요청 될 때까지 일시 중지됩니다.

생성기는 모든 결과가 필요한지 또는 모든 결과에 대해 동시에 메모리를 할당하지 않으려는 경우 큰 결과 세트 (특히 루프 자체를 포함하는 계산)를 계산하는 데 유용합니다. . 또는 생성기가 다른 생성기를 사용 하거나 다른 리소스를 사용하는 상황에서 가능한 한 늦게 발생하면 더 편리합니다.

생성기 (실제로 동일)의 또 다른 용도는 콜백을 반복으로 바꾸는 것입니다. 어떤 상황에서는 함수가 많은 작업을 수행하고 때로는 호출자에게 다시보고하기를 원합니다. 일반적으로 콜백 함수를 사용합니다. 이 콜백을 작업 기능에 전달하면 주기적으로이 콜백을 호출합니다. 생성기 접근 방식은 일 함수 (현재 생성기)가 콜백에 대해 아무것도 알지 못하고보고 할 때마다 생성하는 것입니다. 호출자는 별도의 콜백을 작성하고이를 작업 기능에 전달하는 대신 모든보고 작업을 생성기 주변의 작은 'for'루프로 수행합니다.

예를 들어, '파일 시스템 검색'프로그램을 작성했다고 가정하십시오. 전체 검색을 수행하고 결과를 수집 한 다음 한 번에 하나씩 표시 할 수 있습니다. 첫 번째 결과를 보여주기 전에 모든 결과를 수집해야하며 모든 결과는 동시에 메모리에 저장됩니다. 또는 결과를 찾는 동안 결과를 표시 할 수 있으며, 이는 메모리 효율성이 높고 사용자에게 훨씬 친숙합니다. 후자는 결과 인쇄 기능을 파일 시스템 검색 기능에 전달하여 수행하거나 검색 기능을 생성기로 만들고 결과를 반복하여 수행 할 수 있습니다.

후자의 두 가지 접근 방식의 예를 보려면 os.path.walk () (콜백이있는 기존 파일 시스템 워킹 함수) 및 os.walk () (새로운 파일 시스템 워킹 생성기)를 참조하십시오. 당신은 정말로 모든 결과를리스트로 수집하고 싶었습니다. 제너레이터 접근법은 큰리스트 접근법으로 변환하기가 쉽지 않습니다 :

big_list = list(the_generator)

파일 시스템 목록을 생성하는 생성기와 같은 생성기가 루프에서 해당 생성기를 실행하는 코드와 병렬로 작업을 수행합니까? 이상적으로 컴퓨터는 루프 본문을 실행하고 (마지막 결과 처리) 다음 값을 얻기 위해 생성기가 수행해야하는 모든 작업을 동시에 수행합니다.
Steven Lu

@StevenLu : 그것은이 (가) 이전에 수동으로 발사 스레드 문제로 이동하지 않는 한 yieldjoin다음 결과를 얻을 후에, 그것은 병렬로 실행되지 않습니다 (및 표준 라이브러리 생성기는이 작업을 수행하지, 비밀리에 실행 스레드가 눈살을 찌푸리게한다). yield다음 값이 요청 될 때까지 생성기가 각각 일시 정지 합니다. 생성기가 I / O를 래핑하는 경우 OS는 곧 요청 될 것이라는 가정하에 파일에서 데이터를 사전에 캐싱 할 수 있지만 파이썬은 관련이없는 OS입니다.
ShadowRanger

90

generator를 사용하는 이유 중 하나는 솔루션을 좀 더 명확하게하기위한 것입니다.

다른 하나는 결과를 한 번에 하나씩 처리하여 처리 할 결과 목록을 크게 만들지 않는 것입니다.

다음과 같은 피보나치-업 -n 기능이있는 경우 :

# function version
def fibon(n):
    a = b = 1
    result = []
    for i in xrange(n):
        result.append(a)
        a, b = b, a + b
    return result

다음과 같이 함수를보다 쉽게 ​​작성할 수 있습니다.

# generator version
def fibon(n):
    a = b = 1
    for i in xrange(n):
        yield a
        a, b = b, a + b

기능이 더 명확합니다. 그리고이 기능을 사용하면 :

for x in fibon(1000000):
    print x,

이 예제에서 생성기 버전을 사용하는 경우 전체 1000000 항목 목록이 전혀 작성되지 않고 한 번에 하나의 값만 작성됩니다. 목록 버전을 사용할 때는 목록이 먼저 생성되는 경우에는 해당되지 않습니다.


18
목록이 필요하다면 언제든지 할 수 있습니다list(fibon(5))
endolith

41

PEP 255 의 "동기화"섹션을 참조하십시오 .

명백하게 생성기를 사용하는 것은 인터럽트 가능한 기능을 생성하여 스레드 업데이트를 사용하지 않고 UI를 업데이트하거나 여러 작업을 "동시"(실제로 인터리브)하는 등의 작업을 수행 할 수 있습니다.


1
동기 부여 섹션에는 다음과 같은 구체적인 예가 있습니다. "생산자 기능이 생산 된 값 사이의 상태를 유지해야하는 작업을 충분히 수행 할 때 대부분의 프로그래밍 언어는 생산자 인수에 콜백 기능을 추가하는 것보다 즐겁고 효율적인 솔루션을 제공하지 않습니다. list ... 예를 들어 표준 라이브러리의 tokenize.py는 이러한 접근 방식을 취합니다. "
Ben Creasy

38

나는 의심의 여지가없는이 설명을 발견한다. 모르는 사람 Generators도 모르는 가능성이 있기 때문에yield

반환

return 문은 모든 로컬 변수가 파괴되고 결과 값이 호출자에게 반환 (반환)되는 곳입니다. 나중에 같은 함수를 호출하면 함수에 새로운 변수 세트가 새로 생깁니다.

수율

그러나 함수를 종료 할 때 지역 변수가 버려지지 않으면 어떻게 될까요? 이것은 우리가 멈출 수 resume the function있는 곳을 의미합니다 . 여기에서 개념 generators이 소개되고 중단 된 yield부분부터 문이 다시 시작됩니다 function.

  def generate_integers(N):
    for i in xrange(N):
    yield i

    In [1]: gen = generate_integers(3)
    In [2]: gen
    <generator object at 0x8117f90>
    In [3]: gen.next()
    0
    In [4]: gen.next()
    1
    In [5]: gen.next()

이것이 파이썬에서 returnyield문장 의 차이점 입니다.

항복 진술은 함수를 생성기 함수로 만드는 것입니다.

따라서 생성기는 반복자를 작성하기위한 간단하고 강력한 도구입니다. 일반 함수처럼 작성되지만 yield데이터를 리턴 할 때마다 명령문 을 사용합니다 . next ()가 호출 될 때마다 생성기는 중단 된 위치에서 재개합니다 (모든 데이터 값과 마지막으로 실행 된 명령문을 기억합니다).


33

실제 예

MySQL 테이블에 1 억 개의 도메인이 있고 각 도메인의 Alexa 순위를 업데이트하려고한다고 가정하겠습니다.

가장 먼저 필요한 것은 데이터베이스에서 도메인 이름을 선택하는 것입니다.

테이블 이름이 domains있고 열 이름이 있다고 가정 해 봅시다.domain 입니다.

사용 SELECT domain FROM domains하면 많은 메모리를 소비하는 1 억 개의 행을 반환합니다. 따라서 서버가 중단 될 수 있습니다.

따라서 프로그램을 일괄 적으로 실행하기로 결정했습니다. 배치 크기가 1000이라고 가정 해 봅시다.

첫 번째 배치에서 첫 1000 행을 쿼리하고 각 도메인의 Alexa 순위를 확인하고 데이터베이스 행을 업데이트합니다.

두 번째 배치에서는 다음 1000 행에 대해 작업합니다. 세 번째 배치에서는 2001 년에서 3000 년 사이가됩니다.

이제 배치를 생성하는 생성기 함수가 필요합니다.

다음은 생성기 함수입니다.

def ResultGenerator(cursor, batchsize=1000):
    while True:
        results = cursor.fetchmany(batchsize)
        if not results:
            break
        for result in results:
            yield result

보시다시피, 우리의 기능 yield은 결과를 계속 유지 합니다. return대신 키워드를 사용하면 yield반환에 도달하면 전체 함수가 종료됩니다.

return - returns only once
yield - returns multiple times

함수가 키워드를 사용하는 경우 yield 를 생성기입니다.

이제 다음과 같이 반복 할 수 있습니다.

db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
    doSomethingWith(result)
db.close()

만약 수율이 재귀 / 동적 프로그래밍의 관점에서 설명 될 수 있다면 더 실용적 일 것이다!
igaurav

27

버퍼링. 큰 청크로 데이터를 가져 오는 것이 효율적이지만 작은 청크로 처리하는 경우 생성기가 도움이 될 수 있습니다.

def bufferedFetch():
  while True:
     buffer = getBigChunkOfData()
     # insert some code to break on 'end of data'
     for i in buffer:    
          yield i

위와 같이하면 버퍼링과 처리를 쉽게 분리 할 수 ​​있습니다. 소비자 함수는 이제 버퍼링에 대해 걱정할 필요없이 하나씩 값을 얻을 수 있습니다.


3
만약 getBigChuckOfData가 게으르지 않다면, 여기서 수익률이 무엇인지 이해하지 못합니다. 이 기능의 사용 사례는 무엇입니까?
Sean Geoffrey Pietz

1
그러나 요점은 IIUC, bufferedFetch가 getBigChunkOfData에 대한 호출을 지연시키는 것입니다. getBigChunkOfData가 이미 게으른 경우 bufferedFetch는 쓸모가 없습니다. bufferedFetch ()를 호출 할 때마다 BigChunk를 이미 읽었더라도 하나의 버퍼 요소를 반환합니다. 또한 수율의 역학이 암시 적으로 수행하기 때문에 반환 할 다음 요소의 개수를 명시 적으로 유지할 필요가 없습니다.
hmijail, 사임 자 슬픔

21

생성기가 코드를 정리하고 코드를 캡슐화하고 모듈화 할 수있는 매우 독특한 방법을 제공함으로써 매우 유용한 것으로 나타났습니다. 자체 내부 처리를 기반으로 지속적으로 값을 내뱉을 무언가가 필요한 상황에서 코드의 어느 곳에서나 (예를 들어 루프 또는 블록 내에서) 무언가를 호출 해야하는 경우 생성기는 다음같은 기능입니다. 사용하다.

추상적 인 예는 루프 내에 존재하지 않는 피보나치 수 생성기이며, 어디서나 호출 될 때 항상 다음 번호를 순서대로 반환합니다.

def fib():
    first = 0
    second = 1
    yield first
    yield second

    while 1:
        next = first + second
        yield next
        first = second
        second = next

fibgen1 = fib()
fibgen2 = fib()

이제 코드의 어느 곳에서나 호출 할 수있는 두 개의 피보나치 수 생성기 개체가 있으며 항상 다음과 같이 더 큰 피보나치 수를 순서대로 반환합니다.

>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5

생성기의 멋진 점은 객체를 만드는 과정을 거치지 않고 상태를 캡슐화한다는 것입니다. 그것들을 생각하는 한 가지 방법은 그들의 내부 상태를 기억하는 "기능"입니다.

Python Generators 에서 피보나치 예제를 얻었습니다 . 약간의 상상력으로 생성기가 for루프 및 다른 전통적인 반복 구성을 대체 할 수있는 다른 많은 상황을 생각해 낼 수 있습니다 .


19

간단한 설명 : for진술을 고려하십시오

for item in iterable:
   do_stuff()

많은 시간에, 모든 아이템 iterable은 처음부터 거기에있을 필요는 없지만, 필요할 때 즉시 생성 될 수 있습니다. 이것은 둘 다에서 훨씬 더 효율적일 수 있습니다

  • 공간 (모든 항목을 동시에 저장할 필요는 없음) 및
  • 시간 (모든 항목이 필요하기 전에 반복이 완료 될 수 있음).

다른 경우에는 모든 항목을 미리 알지 못합니다. 예를 들면 다음과 같습니다.

for command in user_input():
   do_stuff_with(command)

모든 사용자 명령을 미리 알 수는 없지만 생성기가 명령을 전달하는 경우 다음과 같이 멋진 루프를 사용할 수 있습니다.

def user_input():
    while True:
        wait_for_command()
        cmd = get_command()
        yield cmd

생성기를 사용하면 무한 시퀀스를 반복 할 수도 있습니다. 물론 컨테이너를 반복 할 때는 불가능합니다.


... 무한 시퀀스는 작은 목록을 반복적으로 순환하여 생성되어 끝에 도달 한 후 처음으로 돌아 오는 시퀀스 일 수 있습니다. 그래프에서 색상을 선택하거나 텍스트에서 바쁜 도둑이나 스피너를 만드는 데 사용합니다.
Andrej Panjkov

@mataap : 거기에 itertool대한 내용이 cycles있습니다.
martineau

12

내가 가장 좋아하는 용도는 "필터"및 "감소"작업입니다.

파일을 읽는 중이고 "##"으로 시작하는 행만 원한다고 가정 해 봅시다.

def filter2sharps( aSequence ):
    for l in aSequence:
        if l.startswith("##"):
            yield l

그런 다음 적절한 루프에서 생성기 기능을 사용할 수 있습니다

source= file( ... )
for line in filter2sharps( source.readlines() ):
    print line
source.close()

축소 예는 비슷합니다. <Location>...</Location>라인 블록을 찾아야하는 파일이 있다고 가정 해 봅시다 . [HTML 태그가 아니라 태그처럼 보이는 행]

def reduceLocation( aSequence ):
    keep= False
    block= None
    for line in aSequence:
        if line.startswith("</Location"):
            block.append( line )
            yield block
            block= None
            keep= False
        elif line.startsWith("<Location"):
            block= [ line ]
            keep= True
        elif keep:
            block.append( line )
        else:
            pass
    if block is not None:
        yield block # A partial block, icky

이 생성기를 다시 적절한 for 루프에서 사용할 수 있습니다.

source = file( ... )
for b in reduceLocation( source.readlines() ):
    print b
source.close()

아이디어는 생성기 함수를 사용하여 시퀀스를 필터링하거나 축소하여 한 번에 하나의 값으로 다른 시퀀스를 생성 할 수 있다는 것입니다.


8
fileobj.readlines()전체 파일을 메모리의 목록으로 읽고 생성기를 사용하는 목적을 무시합니다. 파일 객체는 이미 반복 가능하므로 for b in your_generator(fileobject):대신 사용할 수 있습니다 . 이렇게하면 전체 파일을 읽지 않기 위해 한 번에 한 줄씩 파일을 읽습니다.
nosklo

reduceLocation은 목록을 생성하는 것이 매우 이상합니다. 왜 각 라인을 생성하지 않습니까? 또한 필터링 및 축소는 예상되는 동작 (ipython의 도움말 참조)이 내장되어 있으며 "감소"의 사용법은 필터와 동일합니다.
James Antill

readlines ()에 대한 좋은 지적. 나는 일반적으로 파일이 단위 테스트 중에 일류 라인 반복 자라는 것을 알고 있습니다.
S.Lott

실제로 "감소"는 여러 개별 선을 복합 객체로 결합합니다. 좋아, 그것은 목록이지만 여전히 소스에서 가져온 축소입니다.
S.Lott

9

생성기를 사용할 수있는 실제적인 예는 어떤 종류의 모양이 있고 모서리, 모서리 또는 그 밖의 것을 반복하려는 경우입니다. 내 자신의 프로젝트 (소스 코드 here )에는 사각형이 있습니다.

class Rect():

    def __init__(self, x, y, width, height):
        self.l_top  = (x, y)
        self.r_top  = (x+width, y)
        self.r_bot  = (x+width, y+height)
        self.l_bot  = (x, y+height)

    def __iter__(self):
        yield self.l_top
        yield self.r_top
        yield self.r_bot
        yield self.l_bot

이제 사각형을 만들고 모서리를 반복 할 수 있습니다.

myrect=Rect(50, 50, 100, 100)
for corner in myrect:
    print(corner)

대신에 __iter__메소드를 사용 iter_corners하여로 호출 할 수 있습니다 for corner in myrect.iter_corners(). 표현식 __iter__에서 클래스 인스턴스 이름을 직접 사용할 수 있기 때문에 사용하는 것이 더 우아합니다 for.


비슷한 클래스 필드를 발전기로 전달한다는 아이디어를 좋아했습니다.
eusoubrasileiro

7

입력 유지 상태를 반복 할 때 기본적으로 콜백 기능을 피하십시오.

생성기를 사용하여 수행 할 수있는 작업에 대한 개요는 여기여기 를 참조 하십시오 .


4

그러나 여기에 좋은 대답이 있지만, 더 강력한 발전기 사용 사례를 설명하는 데 도움 이되는 Python Functional Programming 자습서 를 읽어 보는 것이 좋습니다 .


3

생성기의 send 메소드는 언급되지 않았으므로 다음 예제가 있습니다.

def test():
    for i in xrange(5):
        val = yield
        print(val)

t = test()

# Proceed to 'yield' statement
next(t)

# Send value to yield
t.send(1)
t.send('2')
t.send([3])

실행중인 생성기에 값을 보낼 수있는 가능성을 보여줍니다. 아래 비디오의 생성기에 대한 고급 과정 (설명 yield, 병렬 처리 생성기, 재귀 제한 탈출 등)

PyCon 2014의 발전기에 대한 David Beazley


2

웹 서버가 프록시 역할을 할 때 생성기를 사용합니다.

  1. 클라이언트가 서버에서 프록시 URL을 요청합니다
  2. 서버가 대상 URL을로드하기 시작합니다
  3. 서버는 결과를 얻 자마자 클라이언트에게 결과를 반환합니다.

1

물건 더미. 언제든지 일련의 항목을 생성하고 싶지만 한 번에 모두 목록으로 '구체화'하고 싶지는 않습니다. 예를 들어 소수를 반환하는 간단한 생성기를 사용할 수 있습니다.

def primes():
    primes_found = set()
    primes_found.add(2)
    yield 2
    for i in itertools.count(1):
        candidate = i * 2 + 1
        if not all(candidate % prime for prime in primes_found):
            primes_found.add(candidate)
            yield candidate

그런 다음이를 사용하여 후속 프라임의 곱을 생성 할 수 있습니다.

def prime_products():
    primeiter = primes()
    prev = primeiter.next()
    for prime in primeiter:
        yield prime * prev
        prev = prime

이것들은 아주 사소한 예이지만, 사전에 생성하지 않고 큰 (잠재적으로 무한한) 데이터 세트를 처리하는 데 유용한 방법을 알 수 있습니다.


그렇지 않다면 (primes_found에서 소수에 대한 후보 % 소수) 모든 경우 (prim_found에서 소수에 대한 후보 % 소수)
rjmunro

. 그래, 난 너의이 있지만, 깔끔한 약간입니다되지 않은 (후보 %의 소수 == 0 primes_found 프라임에 대한)이 "쓰기 의미 :).
닉 존슨에게

나는 당신이 모두가 아니라면 'not'을 삭제하는 것을 잊었다 고 생각합니다 (primes_found에서 소수에 대한 후보 % 소수)
Thava

0

소수를 최대 n까지 인쇄 할 때도 좋습니다.

def genprime(n=10):
    for num in range(3, n+1):
        for factor in range(2, num):
            if num%factor == 0:
                break
        else:
            yield(num)

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