#!/usr/bin/env python # coding: utf-8 # ## 35. 제너레이터 안에서 throw로 상태를 변화시키지 말라 # 제너레이터 안에서 Exception을 다시 던질 수 있는 throw 메서드가 있다. # In[1]: class MyError(Exception): pass # In[5]: def my_generator(): yield 1 yield 2 yield 3 # In[6]: it = my_generator() print(next(it)) print(next(it)) print(it.throw(MyError('test error'))) # throw를 호출해 제너레이터에 예외를 주입해도, 제너레이터는 try/except 복합문을 사용해 마지막으로 실행된 yield 문을 둘러쌈으로써 이 예외를 잡아낼 수 있다. # In[7]: def my_generator(): yield 1 try: yield 2 except MyError: print('MyError 발생!') else: yield 3 yield 4 # In[8]: it = my_generator() print(next(it)) print(next(it)) print(it.throw(MyError('test error'))) # 이 기능은 제너레이터와 제너레이터를 호출하는 쪽 사이에 양방향 통신 수단을 제공한다. # In[9]: class Reset(Exception): pass def timer(period): current = period while current: current -= 1 try: yield current except Reset: current = period # yield 식에서 Reset 예외가 발생할 때마다 카운터가 period로 재설정된다. # In[10]: def check_for_reset(): pass # In[14]: def announce(remaining): print(f'{remaining} 틱 남음') # In[12]: def run(): it = timer(4) while True: try: if check_for_reset(): current = it.throw(Reset()) else: current = next(it) except StopIteration: break else: announce(current) # In[16]: run() # In[17]: class Timer: def __init__(self, period): self.current = period self.period = period def reset(self): self.current = self.period def __iter__(self): while self.current: self.current -= 1 yield self.current # In[18]: def run(): timer = Timer(4) for current in timer: if check_for_reset(): timer.reset() announce(current) # In[19]: run() # ## 기억해야 할 내용 # - throw 메서드를 사용하면 제너레이터가 마지막으로 실행한 yield 식의 위치에서 예외를 다시 발생시킬 수 있다. # - throw를 사용하면 가독성이 나빠진다. 예외를 잡아내고 다시 발생시키는 데 준비 코드가 필요하며 내포 단계가 깊어지기 때문이다. # - 제너레이터에서 예외적인 동작을 제공하는 더 나은 방법은 __iter__ 메서드를 구현하는 클래스를 사용하면서 예외적인 경우에 상태를 전이시키는 것이다.