제너레이터 안에서 Exception을 다시 던질 수 있는 throw 메서드가 있습니다.
어떤 제너레이터에 대해 throw가 호출되면 이 제너레이터는 값을 내놓은 yield로부터 평소처럼
제너레이터 실행을 계속하는 대신, throw가 제공한 Exception을 다시 던집니다.
class MyError(Exception):
pass
def my_generator():
yield 1
yield 2
yield 3
it = my_generator()
print(next(it)) # 1을 내놓음
print(next(it)) # 2를 내놓음
print(it.throw(MyError('test error')))
>>>
1
2
Traceback ...
MyError: test error
throw를 호출해 제너레이터에 예외를 주입해도,
제너레이터는 try/except 복합문을 사용해 마지막으로 실행된 yield 문을 둘러쌈으로써
이 예외를 잡아낼 수 있습니다.
def my_generator():
yield 1
try:
yield 2
except MyError:
print('MyError 발생!')
else:
yield 3
yield 4
it = my_generator()
print(next(it)) # 1을 내놓음
print(next(it)) # 2를 내놓음
print(it.throw(MyError('test error')))
>>>
1
2
MyError 발생!
4
제너레이터와 제너레이터를 호출하는 쪽 사이에 양방향 통신 수단 제공 (유용함)
throw 메서드에 의존하는 제너레이터를 통해 타이머를 구현하는 코드
class Reset(Exception):
pass
def timer(period):
current = period
while current:
current -= 1
try:
yield current
except Reset:
current = period
yield 식에서 Reset 예외가 발생할 때마다 카운터가 period로 재설정됩니다.
run 함수는 throw를 사용해 타이머를 재설정하는 예외를 주입하거나, 제너레이터 출력에 대해
announce 함수를 호출합니다.
def check_for_reset():
# 외부 이벤트를 폴링한다
...
def announce(remaining):
print(f'{remaining} 틱 남음')
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)
run()
>>>
3틱 남음
2틱 남음
1틱 남음
3틱 남음
2틱 남음
3틱 남음
2틱 남음
1틱 남음
0틱 남음
각 내포 단계마다 StopIteration 예외를 잡아내거나 throw를 할지, next나 announce를 호출할지
결정하는데, 이로 인해 코드에 잡음이 많습니다.
(더 단순한 접근 방법)
이터러블 컨테이너 객체를 사용해 상태가 있는 클로저를 정의하는 것 입니다.
이러한 클래스를 사용해 timer 제너레이터를 재정의한 코드는 다음과 같습니다.
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
run 메서드에서 for를 사용해 훨씬 단순하게 이터레이션을 수행할 수 있고,
내포 수준이 줄어들어 코드가 훨씬 읽기 쉽습니다.
def run():
timer = Timer(4)
for current in timer:
if check_for_reset():
timer.reset()
announce(current)
run()
>>>
3틱 남음
2틱 남음
1틱 남음
3틱 남음
2틱 남음
3틱 남음
2틱 남음
1틱 남음
0틱 남음
제너레이터와 예외를 섞어서 만들어야 하는 작업이 있다면,
비동기 기능을 사용하면 더 좋게 구현할 수 있는 경우도 많습니다.
(결론)
예외적인 경우를 처리해야 한다면 throw를 전혀 사용하지 말고 이터러블 클래스를 사용할 것을 권합니다.
# 기억해야할 내용
▶ throw 메서드를 사용하면 제너레이터가 마지막으로 실행한 yield 식의 위치에서 예외를 다시 발생시킬 수 있다.
▶ throw를 사용하면 가독성이 나빠진다. 예외를 잡아내고 다시 발생시키는데 준비 코드가 필요하며 내포 단계가 깊어지기 때문이다.
▶ 제너레이터에서 예외적인 동작을 제공하는 더 나은 방법은 __iter__ 메서드를 구현하는 클래스를 사용하면서 예외적인 경우에 상태를 전이시키는 것이다.
'파이썬 코딩의 기술' 카테고리의 다른 글
파이썬코딩의 기술 - BW 37: 내장 타입을 여러 단계로 내포시키보다는 클래스를 합성하라 (0) | 2023.03.04 |
---|---|
파이썬코딩의기술 - BW36 : 이터레이터나 제너레이터를 다룰 때는 itertools를 사용하라 (0) | 2023.02.28 |
파이썬코딩의기술 - BW16 in을 사용하고 딕셔너리 키가 없을때 KeyError를 처리하기보다는 get을 사용하라 (0) | 2023.02.22 |
파이썬코딩의기술 - BW15 딕셔너리 삽입 순서에 의존할 때는 조심하라 (0) | 2023.02.22 |
파이썬코딩의기술 - BW14 복잡한 기준을 사용해 정렬할 때는 key 파라미터를 사용하라 (0) | 2023.02.20 |