답변:
발전기는 게으른 평가를 제공합니다. 'for'를 사용하여 명시 적으로 또는 암시 적으로 반복하여 함수 또는 반복 구조로 전달하여 사용합니다. 생성기는 마치 목록을 반환하는 것처럼 여러 항목을 반환하는 것으로 생각할 수 있지만 한 번에 하나씩 반환하는 대신 하나씩 반환하며 생성기 기능은 다음 항목이 요청 될 때까지 일시 중지됩니다.
생성기는 모든 결과가 필요한지 또는 모든 결과에 대해 동시에 메모리를 할당하지 않으려는 경우 큰 결과 세트 (특히 루프 자체를 포함하는 계산)를 계산하는 데 유용합니다. . 또는 생성기가 다른 생성기를 사용 하거나 다른 리소스를 사용하는 상황에서 가능한 한 늦게 발생하면 더 편리합니다.
생성기 (실제로 동일)의 또 다른 용도는 콜백을 반복으로 바꾸는 것입니다. 어떤 상황에서는 함수가 많은 작업을 수행하고 때로는 호출자에게 다시보고하기를 원합니다. 일반적으로 콜백 함수를 사용합니다. 이 콜백을 작업 기능에 전달하면 주기적으로이 콜백을 호출합니다. 생성기 접근 방식은 일 함수 (현재 생성기)가 콜백에 대해 아무것도 알지 못하고보고 할 때마다 생성하는 것입니다. 호출자는 별도의 콜백을 작성하고이를 작업 기능에 전달하는 대신 모든보고 작업을 생성기 주변의 작은 'for'루프로 수행합니다.
예를 들어, '파일 시스템 검색'프로그램을 작성했다고 가정하십시오. 전체 검색을 수행하고 결과를 수집 한 다음 한 번에 하나씩 표시 할 수 있습니다. 첫 번째 결과를 보여주기 전에 모든 결과를 수집해야하며 모든 결과는 동시에 메모리에 저장됩니다. 또는 결과를 찾는 동안 결과를 표시 할 수 있으며, 이는 메모리 효율성이 높고 사용자에게 훨씬 친숙합니다. 후자는 결과 인쇄 기능을 파일 시스템 검색 기능에 전달하여 수행하거나 검색 기능을 생성기로 만들고 결과를 반복하여 수행 할 수 있습니다.
후자의 두 가지 접근 방식의 예를 보려면 os.path.walk () (콜백이있는 기존 파일 시스템 워킹 함수) 및 os.walk () (새로운 파일 시스템 워킹 생성기)를 참조하십시오. 당신은 정말로 모든 결과를리스트로 수집하고 싶었습니다. 제너레이터 접근법은 큰리스트 접근법으로 변환하기가 쉽지 않습니다 :
big_list = list(the_generator)
yield
및 join
다음 결과를 얻을 후에, 그것은 병렬로 실행되지 않습니다 (및 표준 라이브러리 생성기는이 작업을 수행하지, 비밀리에 실행 스레드가 눈살을 찌푸리게한다). yield
다음 값이 요청 될 때까지 생성기가 각각 일시 정지 합니다. 생성기가 I / O를 래핑하는 경우 OS는 곧 요청 될 것이라는 가정하에 파일에서 데이터를 사전에 캐싱 할 수 있지만 파이썬은 관련이없는 OS입니다.
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 항목 목록이 전혀 작성되지 않고 한 번에 하나의 값만 작성됩니다. 목록 버전을 사용할 때는 목록이 먼저 생성되는 경우에는 해당되지 않습니다.
list(fibon(5))
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
문장 의 차이점 입니다.
항복 진술은 함수를 생성기 함수로 만드는 것입니다.
따라서 생성기는 반복자를 작성하기위한 간단하고 강력한 도구입니다. 일반 함수처럼 작성되지만 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 Generators 에서 피보나치 예제를 얻었습니다 . 약간의 상상력으로 생성기가 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()
아이디어는 생성기 함수를 사용하여 시퀀스를 필터링하거나 축소하여 한 번에 하나의 값으로 다른 시퀀스를 생성 할 수 있다는 것입니다.
fileobj.readlines()
전체 파일을 메모리의 목록으로 읽고 생성기를 사용하는 목적을 무시합니다. 파일 객체는 이미 반복 가능하므로 for b in your_generator(fileobject):
대신 사용할 수 있습니다 . 이렇게하면 전체 파일을 읽지 않기 위해 한 번에 한 줄씩 파일을 읽습니다.
생성기를 사용할 수있는 실제적인 예는 어떤 종류의 모양이 있고 모서리, 모서리 또는 그 밖의 것을 반복하려는 경우입니다. 내 자신의 프로젝트 (소스 코드 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
.
그러나 여기에 좋은 대답이 있지만, 더 강력한 발전기 사용 사례를 설명하는 데 도움 이되는 Python Functional Programming 자습서 를 읽어 보는 것이 좋습니다 .
생성기의 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
, 병렬 처리 생성기, 재귀 제한 탈출 등)
물건 더미. 언제든지 일련의 항목을 생성하고 싶지만 한 번에 모두 목록으로 '구체화'하고 싶지는 않습니다. 예를 들어 소수를 반환하는 간단한 생성기를 사용할 수 있습니다.
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)