#!/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__ 메서드를 구현하는 클래스를 사용하면서 예외적인 경우에 상태를 전이시키는 것이다.