python 의 yield 와 generator 를 쉽게 설명 합 니 다.

10488 단어 pythongeneratoryield
머리말
본 고 는 yield 와 generator 를 간단하게 상세 하 게 소개 할 것 이다.다음 과 같은 내용 을 포함한다.어떤 generator,generator 를 생 성 하 는 방법,generator 의 특징,generator 기초 와 고급 응용 장면,generator 사용 중의 주의사항 을 포함한다.본 고 는 enhanced generator 즉 pep 342 와 관련 된 내용 을 포함 하지 않 으 며 이 부분 은 나중에 소개 합 니 다.
발전기 기초
python 의 함수(function)정의 에서 yield 표현 식(Yield expression)이 나타 나 면 사실상 generator function 을 정의 합 니 다.이generator function반환 값 을 호출 하면 generator 입 니 다.이 일반적인 함수 호출 은 차이 가 있 습 니 다.For example:

def gen_generator():
 yield 1

def gen_value():
 return 1
 
if __name__ == '__main__':
 ret = gen_generator()
 print ret, type(ret) #<generator object gen_generator at 0x02645648> <type 'generator'>
 ret = gen_value()
 print ret, type(ret) # 1 <type 'int'>
위의 코드 를 통 해 알 수 있 듯 이gen_generator함수 가 돌아 온 것 은 generator 인 스 턴 스 입 니 다.
generator 는 다음 과 같은 특별한 점 이 있 습 니 다.
     •교체 기(iterator)프로 토 콜 에 따라 교체 기 프로 토 콜 은 실현__iter__ ,next 인터페이스 가 필요 합 니 다.
     •여러 번 들 어 갈 수 있 고 여러 번 돌아 갈 수 있 으 며 함수 체 의 코드 실행 을 중단 할 수 있 습 니 다.
테스트 코드 를 살 펴 보 겠 습 니 다. 

>>> def gen_example():
... print 'before any yield'
... yield 'first yield'
... print 'between yields'
... yield 'second yield'
... print 'no yield anymore'
... 
>>> gen = gen_example()
>>> gen.next()    #      next
before any yield
'first yield'
>>> gen.next()    #      next
between yields
'second yield'
>>> gen.next()    #      next
no yield anymore
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteratio
gen example 방법 을 호출 하 는 것 은 아무런 내용 도 출력 하지 않 았 습 니 다.함수 체 의 코드 가 아직 실행 되 지 않 았 음 을 설명 합 니 다.generator 의 next 방법 을 호출 하면 generator 는 yield 표현 식 에 실 행 됩 니 다.yield 표현 식 의 내용 을 되 돌려 주 고 이 곳 에서 일시 정지(걸 기)하기 때문에 처음으로 next 를 호출 하여 첫 번 째 문장 을 인쇄 하고"first yield"를 되 돌려 줍 니 다.일시 정 지 는 방법의 국부 변수,포인터 정보,운행 환경 을 다음 호출 next 방법 이 복 구 될 때 까지 저장 합 니 다.두 번 째 next 호출 후 마지막 yield 에서 중단 하고 다시 호출next()방법 을 사용 하면 StopIteration 이상 을 던 집 니 다. 
for 문 구 는 StopIteration 이상 을 자동 으로 캡 처 할 수 있 기 때문에 generator(본질 적 으로 모든 iterator)가 자주 사용 하 는 방법 은 순환 에서 사용 합 니 다.

def generator_example():
 yield 1
 yield 2

if __name__ == '__main__':
 for e in generator_example():
 print e
 # output 1 2
generator function 에서 발생 하 는 generator 는 일반적인 function 과 어떤 차이 가 있 습 니까?
(1)function 은 매번 첫 줄 에서 시작 되 고 generator 는 지난번 yield 에서 시 작 된 곳 에서 실 행 됩 니 다.
(2)function 호출 은 한 번(한 그룹)값 을 되 돌려 주 고 generator 는 여러 번 되 돌려 줍 니 다.
(3)function 은 수 차례 반복 되 지 않 고 호출 될 수 있 으 며,generator 인 스 턴 스 는 yield 의 마지막 값 이나 return 이후 에 계속 호출 할 수 없습니다.
함수 에서 Yild 를 사용 하고 이 함 수 를 호출 하 는 것 은 generator 를 생 성 하 는 방식 입 니 다.또 다른 흔 한 방법 은 사용generator expression,For example:

  >>> gen = (x * x for x in xrange(5))
  >>> print gen
  <generator object <genexpr> at 0x02655710>
generator 응용
generator 기초 응용
왜 generator 를 사용 합 니까?가장 중요 한 이 유 는 필요 에 따라 모든 반환 값 을 한꺼번에 만 드 는 것 이 아니 라'모든 반환 값'을 전혀 모 르 기 때 문 입 니 다.
예 를 들 어 아래 코드 에 대해 서...

RANGE_NUM = 100
 for i in [x*x for x in range(RANGE_NUM)]: #      :       
 # do sth for example
 print i

 for i in (x*x for x in range(RANGE_NUM)): #      : generator    
 # do sth for example
 print i
위의 코드 에서 두 개의 for 문 구 는 출력 이 같 습 니 다.코드 는 글자 그대로 보면 괄호 와 작은 괄호 의 차이 입 니 다.그러나 이 차이 점 은 매우 크다.첫 번 째 방법 반환 값 은 하나의 목록 이 고 두 번 째 방법 은 generator 대상 을 되 돌려 준다.RANGENUM 이 커지 면 첫 번 째 방법 으로 되 돌아 오 는 목록 도 커지 고 사용 하 는 메모리 도 커진다.하지만 두 번 째 방법 에 대해 서 는 별 차이 가 없다.
우 리 는'되 돌아 갈 수 있다'는 무한 한 예 를 다시 보 았 다.

def fib():
 a, b = 1, 1
 while True:
 yield a
 a, b = b, a+b 
이 generator 는 수많은'반환 값'을 생 성 하 는 능력 을 가지 고 있 으 며,사용 자 는 언제 교 체 를 중단 할 지 스스로 결정 할 수 있다.
generator 고급 응용
사용 필드 1:
Generator 는 데이터 흐름 을 만 드 는 데 사용 할 수 있 습 니 다.generator 는 즉시 반환 값 을 만 들 지 않 고 필요 할 때 반환 값 을 만 들 수 있 습 니 다.이 는 주동 적 으로 끌 어 올 리 는 과정(pull)에 해당 합 니 다.예 를 들 어 현재 로그 파일 이 있 고 줄 마다 기록 이 생 깁 니 다.각 기록 에 대해 서로 다른 부서 의 사람들 이 처리 하 는 방식 이 다 를 수 있 지만 우 리 는 공용 을 제공 할 수 있 습 니 다.필요 에 따라 생 성 된 데이터 흐름.

def gen_data_from_file(file_name):
 for line in file(file_name):
 yield line

def gen_words(line):
 for word in (w for w in line.split() if w.strip()):
 yield word

def count_words(file_name):
 word_map = {}
 for line in gen_data_from_file(file_name):
 for word in gen_words(line):
  if word not in word_map:
  word_map[word] = 0
  word_map[word] += 1
 return word_map

def count_total_chars(file_name):
 total = 0
 for line in gen_data_from_file(file_name):
 total += len(line)
 return total
 
if __name__ == '__main__':
 print count_words('test.txt'), count_total_chars('test.txt')
위의 예 는 08 년 피 콘 의 한 강좌 에서 나 왔 다.4567914)데이터 생산자 이 고 countwords count_total_chars 는 데이터 소비자 입 니 다.데 이 터 는 필요 할 때 만 끌 어 올 리 는 것 이지 미리 준비 한 것 이 아니 라 는 것 을 알 수 있다.다른 genwords 에서gen_words gen_data_from_file도 generator 가 생 겼 어 요.
사용 필드 2:
일부 프로 그래 밍 장면 에서 한 가지 일 은 일부 논 리 를 실행 한 다음 에 한 동안 기다 리 거나 특정한 비동기 의 결 과 를 기다 리 거나 특정한 상 태 를 기다 린 다음 에 다른 논 리 를 계속 집행 해 야 할 수도 있다.예 를 들 어 마이크로 서비스 구조 에서 서비스 A 가 논 리 를 집행 한 후에 서비스 B 에 데 이 터 를 요청 한 다음 에 서비스 A 에서 계속 집행 한다.또는 게임 프로 그래 밍 에서 하나의 스 킬 은 여러 단계 로 나 뉘 어 일부 동작(효과)을 실행 한 다음 에 시간 을 기다 린 다음 에 계속 합 니 다.기 다 려 야 하고 막 히 기 를 원 하지 않 는 상황 에 대해 우 리 는 일반적으로 리 셋(callback)방식 을 사용한다.다음은 간단 한 예 를 들 어 보 겠 습 니 다.

 def do(a):
 print 'do', a
 CallBackMgr.callback(5, lambda a = a: post_do(a))
 
 def post_do(a):
 print 'post_do', a
여기 있 는 CallBackMgr 은 5s 후의 시간 을 등록 하고 5s 후에(w for w in line.split() if w.strip()) 함 수 를 호출 합 니 다.이 를 통 해 논리 가 두 함수 로 분 단 된 것 을 알 수 있 고 문맥 의 전달 도 필요 합 니 다(예 를 들 어 여기 있 는 매개 변수 a).이 예 를 yield 로 수정 합 니 다.yield 반환 값 은 기다 리 는 시간 을 의미 합 니 다.

 @yield_dec
 def do(a):
 print 'do', a
 yield 5
 print 'post_do', a
이 decrator 를 통 해 이 generator 를 YieldManager 에 등록 하고 5s 후에 next 방법 을 호출 해 야 합 니 다.Yield 버 전 은 리 셋 과 같은 기능 을 실 현 했 지만 훨씬 뚜렷 해 보 였 다.
다음은 참고 로 간단 한 실현 을 제공 합 니 다.

# -*- coding:utf-8 -*-
import sys
# import Timer
import types
import time

class YieldManager(object):
 def __init__(self, tick_delta = 0.01):
 self.generator_dict = {}
 # self._tick_timer = Timer.addRepeatTimer(tick_delta, lambda: self.tick())

 def tick(self):
 cur = time.time()
 for gene, t in self.generator_dict.items():
  if cur >= t:
  self._do_resume_genetator(gene,cur)

 def _do_resume_genetator(self,gene, cur ):
 try:
  self.on_generator_excute(gene, cur)
 except StopIteration,e:
  self.remove_generator(gene)
 except Exception, e:
  print 'unexcepet error', type(e)
  self.remove_generator(gene)

 def add_generator(self, gen, deadline):
 self.generator_dict[gen] = deadline

 def remove_generator(self, gene):
 del self.generator_dict[gene]

 def on_generator_excute(self, gen, cur_time = None):
 t = gen.next()
 cur_time = cur_time or time.time()
 self.add_generator(gen, t + cur_time)

g_yield_mgr = YieldManager()

def yield_dec(func):
 def _inner_func(*args, **kwargs):
 gen = func(*args, **kwargs)
 if type(gen) is types.GeneratorType:
  g_yield_mgr.on_generator_excute(gen)

 return gen
 return _inner_func

@yield_dec
def do(a):
 print 'do', a
 yield 2.5
 print 'post_do', a
 yield 3
 print 'post_do again', a

if __name__ == '__main__':
 do(1)
 for i in range(1, 10):
 print 'simulate a timer, %s seconds passed' % i
 time.sleep(1)
 g_yield_mgr.tick()
주의사항:
(1)Yield 는 끼 워 넣 을 수 없습니다!

def visit(data):
 for elem in data:
 if isinstance(elem, tuple) or isinstance(elem, list):
  visit(elem) # here value retuened is generator
 else:
  yield elem
  
if __name__ == '__main__':
 for e in visit([1, 2, (3, 4), 5]):
 print e
위의 코드 는 내장 배열 의 모든 요소 에 접근 합 니 다.우리 가 원 하 는 출력 은 1,2,3,4,5 이 고 실제 출력 은 1 입 니 다.  2  5 。왜 일 까요?주석 에서 보 듯 이 visit 는 하나의lambda이기 때문에 네 번 째 줄 은yield_dec로 돌아 가 고 코드 도 이 generator 인 스 턴 스 가 교체 되 지 않 았 습 니 다.그럼 코드 를 바 꾸 고 이 임시 generator 를 교체 하면 됩 니 다.

def visit(data):
 for elem in data:
 if isinstance(elem, tuple) or isinstance(elem, list):
  for e in visit(elem):
  yield e
 else:
  yield elem
또는 python 3.3 에서 사용 할 수 있 습 니 다generator function.이 문법 은pep380에 추 가 된 것 입 니 다.

 def visit(data):
 for elem in data:
  if isinstance(elem, tuple) or isinstance(elem, list):
  yield from visit(elem)
  else:
  yield elem
(2)generator function 에서 return 사용
python doc 에 서 는 return 을 사용 할 수 있다 고 명확 하 게 언급 되 어 있 으 며,generator 가 여기까지 실 행 될 때 StopIteration 이상 을 던 집 니 다.

def gen_with_return(range_num):
 if range_num < 0:
 return
 else:
 for i in xrange(range_num):
  yield i

if __name__ == '__main__':
 print list(gen_with_return(-1))
 print list(gen_with_return(1))
그러나generator object의 return 은 반환 값 을 가 져 올 수 없습니다.

 def gen_with_return(range_num):
 if range_num < 0:
  return 0
 else:
  for i in xrange(range_num):
  yield i
위의 코드 가 잘못 보 고 될 수 있 습 니 다.yield from총결산
이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.

좋은 웹페이지 즐겨찾기