문제

저는 Python을 배우기 시작했고 생성기 함수, 즉 Yield 문이 있는 함수를 발견했습니다.이 기능들이 어떤 유형의 문제를 해결하는데 정말 좋은지 알고 싶습니다.

도움이 되었습니까?

해결책

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

생성기는 모든 결과가 필요한지 알 수 없거나 모든 결과에 동시에 메모리를 할당하고 싶지 않은 대규모 결과 집합(특히 루프 자체와 관련된 계산)을 계산하는 데 적합합니다. .또는 발전기가 사용하는 상황의 경우 또 다른 생성기를 사용하거나 다른 리소스를 소비하며 가능한 한 늦게 발생하는 것이 더 편리합니다.

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

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

후자의 두 접근 방식의 예를 보려면 os.path.walk()(콜백이 포함된 기존 파일 시스템 탐색 함수) 및 os.walk()(새 파일 시스템 탐색 생성기)를 참조하세요. 물론, 모든 결과를 목록으로 수집하고 싶었기 때문에 생성기 접근 방식을 큰 목록 접근 방식으로 변환하는 것은 쉽지 않습니다.

big_list = list(the_generator)

다른 팁

생성기를 사용하는 이유 중 하나는 어떤 종류의 솔루션에 대해 솔루션을 더 명확하게 만드는 것입니다.

다른 하나는 결과를 한 번에 하나씩 처리하여 어쨌든 분리하여 처리할 결과의 거대한 목록을 작성하는 것을 피하는 것입니다.

다음과 같은 fibonacci-to-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개 항목 목록이 전혀 생성되지 않고 한 번에 하나의 값만 생성됩니다.목록이 먼저 생성되는 목록 버전을 사용하는 경우에는 그렇지 않습니다.

"동기 부여" 섹션을 참조하세요. PEP 255.

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

나는 내 의심을 없애는 이 설명을 발견했습니다.모르는 사람이 그럴 가능성도 있으니까. 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()

그래서 차이점은 다음과 같습니다 return 그리고 yield Python의 명령문.

Yield 문은 함수를 생성기 함수로 만드는 것입니다.

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

실제 사례

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

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

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

위의 방법을 사용하면 버퍼링과 처리를 쉽게 분리할 수 있습니다.소비자 함수는 이제 버퍼링에 대한 걱정 없이 값을 하나씩 가져올 수 있습니다.

나는 생성기가 코드를 정리하고 코드를 캡슐화하고 모듈화하는 매우 독특한 방법을 제공함으로써 매우 도움이 된다는 것을 발견했습니다.자체 내부 처리를 기반으로 지속적으로 값을 뱉어낼 무언가가 필요하고 코드의 어느 곳에서나(예를 들어 루프나 블록 내에서뿐만 아니라) 호출해야 하는 경우 생성기는 다음과 같습니다. 그만큼 사용할 수 있는 기능입니다.

추상적인 예는 루프 내에 존재하지 않고 어디에서든 호출될 때 항상 시퀀스의 다음 숫자를 반환하는 피보나치 수 생성기입니다.

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 생성기 - 그게 무엇인가요? 그리고 약간의 상상력만 있으면 발전기가 다음과 같은 훌륭한 대안이 될 수 있는 많은 다른 상황을 떠올릴 수 있습니다. for 루프 및 기타 전통적인 반복 구성.

간단한 설명:다음을 고려해보세요. 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

생성기를 사용하면 무한 시퀀스에 대한 반복이 가능하지만 컨테이너를 반복할 때는 물론 불가능합니다.

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

파일을 읽고 있는데 "##"으로 시작하는 줄만 원한다고 가정해 보겠습니다.

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

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

생성기를 사용할 수 있는 실용적인 예는 일종의 모양이 있고 모서리, 가장자리 등을 반복하려는 경우입니다.내 프로젝트의 경우(소스 코드 여기) 나는 직사각형을 가지고 있었다 :

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 표현.

기본적으로 입력 유지 상태를 반복할 때 콜백 함수를 피합니다.

보다 여기 그리고 여기 생성기를 사용하여 수행할 수 있는 작업에 대한 개요입니다.

여기에는 좋은 답변이 있지만 Python의 전체 내용을 읽어 보는 것도 좋습니다. 함수형 프로그래밍 튜토리얼 이는 생성기의 보다 강력한 사용 사례를 설명하는 데 도움이 됩니다.

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

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

생성기의 전송 방법은 언급되지 않았으므로 여기에 예가 있습니다.

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

물건 더미.항목의 시퀀스를 생성하고 싶지만 모든 항목을 한 번에 목록으로 '구체화'하고 싶지 않은 경우.예를 들어, 소수를 반환하는 간단한 생성기가 있을 수 있습니다.

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

이는 매우 사소한 예이지만 미리 생성하지 않고 대규모(잠재적으로 무한한!) 데이터 세트를 처리하는 데 어떻게 유용할 수 있는지 확인할 수 있으며, 이는 보다 분명한 용도 중 하나일 뿐입니다.

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)
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top