{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## 33. yield from을 사용해 여러 제너레이터를 합성하라" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "화면상 이동 변위를 만들어낼 떄 사용할 두 가지 제너레이터를 정의한 코드다." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "def move(period, speed):\n", " for _ in range(period):\n", " yield speed" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def pause(delay):\n", " for _ in range(delay):\n", " yield 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "최종 애니메이션을 만들려면 move와 pause를 합성해서 변위 시퀀스를 하나만들어야 한다.\n", "\n", "애니메이션의 각 단계마다 제너레이터를 호출해서 차례로 이터레이션하고 각 이터레이션에서 나오는 변위를 순서대로 내보내는 방식으로 다음과 같이 시퀀스를 만든다." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def animation():\n", " for delta in move(4, 5.0):\n", " yield delta\n", " for delta in pause(3):\n", " yield delta\n", " for delta in move(2, 3.0):\n", " yield delta" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def render(delta):\n", " print(f'Delta: {delta:.1f}')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def run(func):\n", " for delta in func():\n", " render(delta)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Delta: 5.0\n", "Delta: 5.0\n", "Delta: 5.0\n", "Delta: 5.0\n", "Delta: 0.0\n", "Delta: 0.0\n", "Delta: 0.0\n", "Delta: 3.0\n", "Delta: 3.0\n" ] } ], "source": [ "run(animation)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 코드의 문제점은 animate가 너무 반복적이라는 것이다.\n", "\n", "for 문과 yield 식이 반복되면서 잡음이 늘고 가독성이 줄어든다.\n", "\n", "이 문제의 해법은 yield from 식을 사용하는 것이다.\n", "\n", "이는 고급 제너레이터 기능으로, 제어를 부모 제너레이터에게 전달하기 전에 내포된 제너레이터가 모든 값을 내보낸다." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "def animate_composed():\n", " yield from move(4, 5.0)\n", " yield from pause(3)\n", " yield from move(2, 3.0)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Delta: 5.0\n", "Delta: 5.0\n", "Delta: 5.0\n", "Delta: 5.0\n", "Delta: 0.0\n", "Delta: 0.0\n", "Delta: 0.0\n", "Delta: 3.0\n", "Delta: 3.0\n" ] } ], "source": [ "run(animate_composed)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "동일한 결과.\n", "\n", "yield from은 근본적으로 파이썬 인터프리터가 여러분 대신 for 루프를 내포시키고 yield 식을 처리하도록 만든다.\n", "\n", "이로 인해 성능도 더 좋아진다." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "수동 내포: 2.66s\n" ] } ], "source": [ "import timeit\n", "\n", "def child():\n", " for i in range(1_000_000):\n", " yield i\n", "\n", "def slow():\n", " for i in child():\n", " yield i\n", " \n", "def fast():\n", " yield from child()\n", " \n", "baseline = timeit.timeit(\n", "stmt='for _ in slow(): pass',\n", "globals=globals(),\n", "number=50)\n", "\n", "print(f'수동 내포: {baseline:.2f}s')" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "합성 사용: 2.26s\n" ] } ], "source": [ "comparison = timeit.timeit(\n", "stmt='for _ in fast(): pass',\n", "globals=globals(),\n", "number=50)\n", "\n", "print(f'합성 사용: {comparison:.2f}s')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 기억해야 할 내용\n", "- yield from 식을 사용하면 여러 내장 제너레이터를 모아서 제너레이터 하나로 합성할 수 있다.\n", "- 직접 내포된 제너레이터를 이터레이션하면서 각 제너레이터의 출력을 내보내는 것보다 yield from을 사용하는 것이 성능 면에서 더 좋다." ] } ], "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 }