이 글은 주로 Python의 Yield와 Generator 관련 정보를 얕은 것부터 더 깊은 것까지 설명합니다. 글의 소개는 매우 자세하며 필요한 모든 사람이 참조할 수 있는 내용이 있습니다.
머리말
이 글에서는 어떤 제너레이터, 어떻게 생성하는지 등의 내용을 포함하여 간단한 것부터 깊은 것까지 Yield와 Generator를 자세히 소개합니다. 발전기 및 발전기 기능, 발전기의 기본 및 고급 응용 시나리오, 발전기 사용 시 주의 사항입니다. 이 글에는 Enhanced Generator나 pep342 관련 내용이 포함되어 있지 않습니다. 이 부분은 나중에 소개하겠습니다.
생성기 기본
파이썬의 함수 정의에서 항복 표현식이 나타나면 실제로 정의된 것은 생성기입니다. 함수이며, 이 generator function
호출의 반환 값은 생성기입니다. 이 일반적인 함수 호출은 다릅니다. 예:
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에는 다음과 같은 Special이 있습니다.
•__iter__
을 구현해야 하는 반복자 프로토콜을 따르고 다음 인터페이스
•여러 번 입력할 수 있으며 여러 번 반환하면 일시 중지될 수 있습니다. 함수 본문의 코드 실행
테스트 코드를 살펴보겠습니다.
>>> 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 예제 메서드를 호출하면 아무것도 출력되지 않습니다. 본문 코드가 아직 실행을 시작하지 않았습니다. 제너레이터의 다음 메소드가 호출되면 제너레이터는 항복 표현식을 실행하고, 항복 표현식의 내용을 반환한 다음 이 위치에서 일시 중지(중지)하므로 next에 대한 첫 번째 호출은 첫 번째 문장을 인쇄하고 "를 반환합니다. 첫 번째 수확량". 일시 중지한다는 것은 다음 메서드 호출이 재개될 때까지 메서드의 지역 변수, 포인터 정보 및 실행 환경이 저장된다는 의미입니다. next를 두 번째로 호출한 후 마지막 항복에서 일시 중지됩니다. next()
메서드가 다시 호출되면 StopIteration 예외가 발생합니다.
for 문은 StopIteration 예외를 자동으로 포착할 수 있기 때문에 생성기(기본적으로 모든 반복자)의 더 일반적인 방법은 이를 루프에서 사용하는 것입니다.
def generator_example(): yield 1 yield 2 if __name__ == '__main__': for e in generator_example(): print e # output 1 2
생성기는 다음에 의해 생성됩니다. 제너레이터 함수는 일반 함수와 어떻게 다른가요?
(1) 함수는 매번 첫 번째 줄부터 실행되고, 제너레이터는 마지막 Yield 시작부터 실행됩니다.
(2) 함수 호출은 한 번에 하나의 값(세트)을 반환하는 반면, 생성기는
을 여러 번 반환할 수 있습니다. (3) 함수는 수없이 반복적으로 호출될 수 있지만
함수에서 Yield를 사용한 후 함수를 호출하는 것은 생성기를 생성하는 방법입니다. 또 다른 일반적인 방법은 generator expression
을 사용하는 것입니다. 예:
>>> gen = (x * x for x in xrange(5)) >>> print gen <generator object <genexpr> at 0x02655710>
발전기 애플리케이션
발전기 기본 애플리케이션
생성기를 사용하는 가장 중요한 이유는 모든 반환 값을 한 번에 생성하는 대신 요청에 따라 결과를 생성하고 "반환"할 수 있다는 것입니다. 게다가 때로는 "모든 반환 값"을 전혀 알 수 없습니다. .
예를 들어 다음 코드의 경우
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 문의 출력은 동일합니다. 코드는 문자 그대로 대괄호와 소문자를 의미합니다. 괄호의 차이점. 그러나 이 차이점은 매우 다릅니다. 첫 번째 메서드는 목록을 반환하고 두 번째 메서드는 생성기 개체를 반환합니다. RANGE_NUM이 커질수록 첫 번째 메서드에서 반환되는 목록도 커지고 차지하는 메모리도 커지지만 두 번째 메서드에서는 차이가 없습니다.
무한 횟수로 "반환"할 수 있는 또 다른 예를 살펴보겠습니다.
def fib(): a, b = 1, 1 while True: yield a a, b = b, a+b
이 생성기는 셀 수 없이 많은 "반환 값"을 생성하는 기능이 있으며 사용자는 언제 중지할지 결정할 수 있습니다. 반복
Generator의 고급 응용
사용 시나리오 1:
Generator를 사용하여 다음 작업을 수행할 수 있습니다. 데이터 스트림 생성, 생성기 반환 값을 즉시 생성하지 않고 필요할 때까지 기다립니다. 예를 들어 로그 파일이 있고 각 줄에 대한 레코드가 생성됩니다. 각 기록마다 서로 다른 부서의 사람들이 이를 다르게 처리할 수 있지만 우리는 공통된 주문형 데이터 흐름을 제공할 수 있습니다.
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')
위의 예는 2008년 PyCon 강의에서 나온 것입니다. gen_words gen_data_from_file
은 데이터 생산자이고 count_words count_total_chars는 데이터 소비자입니다. 보시다시피 데이터는 미리 준비되지 않고 필요할 때만 가져옵니다. 또한 gen_words의 (w for w in line.split() if w.strip())
도 생성기
를 생성합니다. 사용 시나리오 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之后再调用lambda
函数,可见一段逻辑被分裂到两个函数,而且还需要上下文的传递(如这里的参数a)。我们用yield来修改一下这个例子,yield返回值代表等待的时间。
@yield_dec def do(a): print 'do', a yield 5 print 'post_do', a
这里需要实现一个YieldManager, 通过yield_dec
这个decrator将do这个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是一个generator function
,所以第4行返回的是generator object
,而代码也没这个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
或者在python3.3中 可以使用yield from
,这个语法是在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 function
中的return是不能带任何返回值的
def gen_with_return(range_num): if range_num < 0: return 0 else: for i in xrange(range_num): yield i
上面的代码会报错:SyntaxError: 'return' with argument inside generator
总结
위 내용은 Python의 Yield 및 Generator에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!