{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "*이 노트북은 제이크 반더플라스(Jake VanderPlas)의 [A Whirlwind Tour of Python](http://www.oreilly.com/programming/free/a-whirlwind-tour-of-python.csp)(OReilly Media, 2016)를 기반으로 만들어졌습니다. 이 내용은 [CC0](https://github.com/jakevdp/WhirlwindTourOfPython/blob/master/LICENSE) 라이센스를 따릅니다. 전체 노트북의 목록은 https://github.com/rickiepark/WhirlwindTourOfPython 에서 볼 수 있습니다.*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "< [리스트 내포](11-리스트 내포.ipynb) | [목차](목차.ipynb) | [모듈과 패키지](13-모듈과 패키지.ipynb) >" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 제너레이터" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "여기에서 *제너레이터 표현식*과 *제너레이터 함수*를 포함하여 파이썬 제너레이터를 자세히 배워 보겠습니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 제너레이터 표현식" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "리스트 내포와 제너레이터 표현식의 차이는 이따금 헷갈립니다. 이 둘의 차이를 간단히 살펴 보겠습니다:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 리스트 내포는 대괄호를 사용하고 제너레이터 표현식은 소괄호를 사용합니다\n", "다음은 대표적인 리스트 내포입니다:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[n ** 2 for n in range(12)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "반면 다음은 대표적인 제너레이터 표현식입니다:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ " at 0x7f15e4b23200>" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(n ** 2 for n in range(12))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "제너레이터 표현식을 출력하면 내용이 출력되지 않습니다. 제너레이터 표현식의 내용을 출력하는 방법은 ``list`` 생성 함수에 전달하는 것입니다:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "G = (n ** 2 for n in range(12))\n", "list(G)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 리스트는 값들의 모음이고, 제너레이터는 값을 만들어내는 한 방식입니다\n", "리스트를 만들 때 실제 일련의 어떤 값들을 만드므로 연관되어 일정량의 메모리가 소모됩니다.\n", "제너레이터를 만들 때는 일련의 값들을 만들지 않고 이런 값들을 만드는 방법을 만듭니다.\n", "둘 다 같은 반복자 인터페이스에 사용할 수 있습니다:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 4 9 16 25 36 49 64 81 100 121 " ] } ], "source": [ "L = [n ** 2 for n in range(12)]\n", "for val in L:\n", " print(val, end=' ')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 4 9 16 25 36 49 64 81 100 121 " ] } ], "source": [ "G = (n ** 2 for n in range(12))\n", "for val in G:\n", " print(val, end=' ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "차이는 제너레이터 표현식은 필요할 때까지 실제로 값을 계산하지 않는다는 점입니다.\n", "메모리를 효율적으로 사용할 수 있을 뿐만 아니라 계산 비용도 절감할 수 있습니다!\n", "리스트의 크기는 가용 메모리 범위로 제한되지만 제너레이터의 크기는 제한이 없다는 뜻이 됩니다!\n", "\n", "무한 제너레이터 표현식의 한 예는 ``itertools``에 정의된 ``count`` 반복자를 사용하여 만들 수 있습니다:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "count(0)" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from itertools import count\n", "count()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 2 3 4 5 6 7 8 9 10 " ] } ], "source": [ "for i in count():\n", " print(i, end=' ')\n", " if i >= 10: break" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "``count`` 반복자는 멈출 때까지 영원히 카운팅할 것입니다. 영원히 실행되는 제너레이터를 만드는 것도 쉽습니다:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 11 13 17 19 23 29 31 37 41 " ] } ], "source": [ "factors = [2, 3, 5, 7]\n", "G = (i for i in count() if all(i % n > 0 for n in factors))\n", "for val in G:\n", " print(val, end=' ')\n", " if val > 40: break" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "적절히 ``factors`` 리스트를 확장히키면 에라토스테네스의 체 알고리즘을 사용하여 소수 제너레이터를 구성할 수 있습니다. 조금 후에 이에 대해 더 자세히 알아 보겠습니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 리스트는 여러번 반복할 수 있지만 제너레이터 표현식은 한번만 사용됩니다\n", "이것이 제너레이터 표현식의 장점 중 하나입니다.\n", "리스트를 사용하면 다음과 같이 쉽게 할 수 있습니다:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 4 9 16 25 36 49 64 81 100 121 \n", "0 1 4 9 16 25 36 49 64 81 100 121 " ] } ], "source": [ "L = [n ** 2 for n in range(12)]\n", "for val in L:\n", " print(val, end=' ')\n", "print()\n", "\n", "for val in L:\n", " print(val, end=' ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "하지만 제너레이터 표현식은 한 번 반복하면 사라집니다:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "G = (n ** 2 for n in range(12))\n", "list(G)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(G)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이런 기능 덕택에 반복이 중지되고 다시 시작될 수 있기 때문에 매우 유용합니다:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 4 9 16 25 36 \n", "그 사이에 무언가 다른 일을 합니다\n", "49 64 81 100 121 " ] } ], "source": [ "G = (n**2 for n in range(12))\n", "for n in G:\n", " print(n, end=' ')\n", " if n > 30: break\n", "\n", "print(\"\\n그 사이에 무언가 다른 일을 합니다\")\n", "\n", "for n in G:\n", " print(n, end=' ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "디스크에 있는 데이터 파일들을 다룰 때 매우 유용합니다. 배치 처리를 할 때 제너레이터가 처리한 파일들을 기억할 수 있습니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 제너레이터 함수: ``yield``를 사용\n", "이전 절에서 리스트 내포는 비교적 간단한 리스트를 만드는데 좋고 보통의 ``for`` 루프는 아주 복잡한 상황에 더 잘 맞습니다.\n", "제너레이터 표현도 동일합니다. ``yield`` 문을 사용하는 *제너레이터 함수*를 사용하여 더 복잡한 제너레이터를 만들 수 있습니다.\n", "\n", "동일한 리스트를 만드는 두 가지 방법이 다음에 나타나 있습니다:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]\n", "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]\n" ] } ], "source": [ "L1 = [n ** 2 for n in range(12)]\n", "\n", "L2 = []\n", "for n in range(12):\n", " L2.append(n ** 2)\n", "\n", "print(L1)\n", "print(L2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "비슷하게 동일한 제너레이터를 만드는 두 가지 방법이 있습니다:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 4 9 16 25 36 49 64 81 100 121\n", "0 1 4 9 16 25 36 49 64 81 100 121\n" ] } ], "source": [ "G1 = (n ** 2 for n in range(12))\n", "\n", "def gen():\n", " for n in range(12):\n", " yield n ** 2\n", "\n", "G2 = gen()\n", "print(*G1)\n", "print(*G2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "제너레이터 함수는 한 번 값을 리턴하는 ``return`` 대신 (무한도 가능한) 시퀀스를 반환하는 ``yield``를 사용한 함수입니다.\n", "제너레이터 표현식과 마찬가지로 제너레이터의 상태는 부분 반복 간에 유지됩니다. 새로운 제너레이터를 얻고 싶으면 간단히 함수를 다시 호출하면 됩니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 예제: 소수 제너레이터\n", "다음 제가 제일 좋아하는 제너레이터 함수의 예를 보겠습니다. 무한한 소수를 생성하는 함수입니다.\n", "전통적인 알고리즘은 다음과 같이 작동하는 *에라토스테네스의 체*입니다:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]\n" ] } ], "source": [ "# 후보 리스트를 생성합니다\n", "L = [n for n in range(2, 40)]\n", "print(L)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[2, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39]\n" ] } ], "source": [ "# 첫 번째 값의 모든 배수를 제거합니다\n", "L = [n for n in L if n == L[0] or n % L[0] > 0]\n", "print(L)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[2, 3, 5, 7, 11, 13, 17, 19, 23, 25, 29, 31, 35, 37]\n" ] } ], "source": [ "# 두 번째 값의 모든 배수를 제거합니다\n", "L = [n for n in L if n == L[1] or n % L[1] > 0]\n", "print(L)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]\n" ] } ], "source": [ "# 세 번째 값의 모든 배수를 제거합니다\n", "L = [n for n in L if n == L[2] or n % L[2] > 0]\n", "print(L)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "아주 충분히 큰 리스트에서 충분한 시간동안 이를 반복하면 원하는 만큼의 소수를 생성할 수 있습니다.\n", "\n", "이 로직을 제러레이터 함수에 캡슐화해 보죠:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97\n" ] } ], "source": [ "def gen_primes(N):\n", " \"\"\"N까지의 소수를 생성합니다\"\"\"\n", " primes = set()\n", " for n in range(2, N):\n", " if all(n % p > 0 for p in primes):\n", " primes.add(n)\n", " yield n\n", "\n", "print(*gen_primes(100))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이것이 전부입니다!\n", "계산이 더 효율적인 에라토스테네스의 체의 구현은 아니지만 제너레이터 함수 문법이 복잡한 시퀀스를 만드는데 얼마나 편리한지 보여줍니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "< [리스트 내포](11-리스트 내포.ipynb) | [목차](목차.ipynb) | [모듈과 패키지](13-모듈과 패키지.ipynb) >" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "TensorFlow 2.3 on Python 3.6 (CUDA 10.1)", "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.6.9" } }, "nbformat": 4, "nbformat_minor": 1 }