{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## 35. 제너레이터 안에서 throw로 상태를 변화시키지 말라" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "제너레이터 안에서 Exception을 다시 던질 수 있는 throw 메서드가 있다." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "class MyError(Exception):\n", " pass" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def my_generator():\n", " yield 1\n", " yield 2\n", " yield 3" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n" ] }, { "ename": "MyError", "evalue": "test error", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mMyError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m<ipython-input-6-5000f6e47c06>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mit\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mthrow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mMyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'test error'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m<ipython-input-5-dbcd45bc83be>\u001b[0m in \u001b[0;36mmy_generator\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmy_generator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32myield\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mMyError\u001b[0m: test error" ] } ], "source": [ "it = my_generator()\n", "print(next(it))\n", "print(next(it))\n", "print(it.throw(MyError('test error')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "throw를 호출해 제너레이터에 예외를 주입해도, 제너레이터는 try/except 복합문을 사용해 마지막으로 실행된 yield 문을 둘러쌈으로써 이 예외를 잡아낼 수 있다." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def my_generator():\n", " yield 1\n", " \n", " try:\n", " yield 2\n", " except MyError:\n", " print('MyError 발생!')\n", " else:\n", " yield 3\n", " \n", " yield 4" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "MyError 발생!\n", "4\n" ] } ], "source": [ "it = my_generator()\n", "print(next(it))\n", "print(next(it))\n", "print(it.throw(MyError('test error')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 기능은 제너레이터와 제너레이터를 호출하는 쪽 사이에 양방향 통신 수단을 제공한다." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "class Reset(Exception):\n", " pass\n", "\n", "def timer(period):\n", " current = period\n", " while current:\n", " current -= 1\n", " try:\n", " yield current\n", " except Reset:\n", " current = period" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "yield 식에서 Reset 예외가 발생할 때마다 카운터가 period로 재설정된다." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def check_for_reset():\n", " pass" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "def announce(remaining):\n", " print(f'{remaining} 틱 남음')" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "def run():\n", " it = timer(4)\n", " while True:\n", " try:\n", " if check_for_reset():\n", " current = it.throw(Reset())\n", " else:\n", " current = next(it)\n", " except StopIteration:\n", " break\n", " else:\n", " announce(current)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3 틱 남음\n", "2 틱 남음\n", "1 틱 남음\n", "0 틱 남음\n" ] } ], "source": [ "run()" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "class Timer:\n", " def __init__(self, period):\n", " self.current = period\n", " self.period = period\n", " \n", " def reset(self):\n", " self.current = self.period\n", " \n", " def __iter__(self):\n", " while self.current:\n", " self.current -= 1\n", " yield self.current" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "def run():\n", " timer = Timer(4)\n", " for current in timer:\n", " if check_for_reset():\n", " timer.reset()\n", " announce(current)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3 틱 남음\n", "2 틱 남음\n", "1 틱 남음\n", "0 틱 남음\n" ] } ], "source": [ "run()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 기억해야 할 내용\n", "- throw 메서드를 사용하면 제너레이터가 마지막으로 실행한 yield 식의 위치에서 예외를 다시 발생시킬 수 있다.\n", "- throw를 사용하면 가독성이 나빠진다. 예외를 잡아내고 다시 발생시키는 데 준비 코드가 필요하며 내포 단계가 깊어지기 때문이다.\n", "- 제너레이터에서 예외적인 동작을 제공하는 더 나은 방법은 __iter__ 메서드를 구현하는 클래스를 사용하면서 예외적인 경우에 상태를 전이시키는 것이다." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.2" } }, "nbformat": 4, "nbformat_minor": 4 }