{ "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": [ "< [에러와 예외처리](09-에러와 예외처리.ipynb) | [목차](목차.ipynb) | [리스트 내포](11-리스트 내포.ipynb) >" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 반복자" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "데이터 분석의 중요한 한 방법은 비슷한 계산을 자동으로 계속 반복하는 것입니다. 예를 들어 성과 이름으로 분리하고 싶은 사람 이름이 들어 있는 테이블이 있거나, 표준 포맷으로 바꾸어야 하는 날짜의 테이블이 있는 경우입니다.\n", "이에 대한 파이썬의 대답은 *반복자* 문법입니다.\n", "이전에 이미 ``range`` 반복자를 보았습니다:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 2 3 4 5 6 7 8 9 " ] } ], "source": [ "for i in range(10):\n", " print(i, end=' ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "여기서는 조금 더 자세히 알아 보겠습니다. 파이썬 3에서 ``range``는 리스트가 아니고 *반복자*라고 부르는 어떤 것입니다. 이를 배우는 것이 파이썬의 유용한 많은 기능을 이해하는데 핵심적인 역할을 할 것입니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 리스트를 반복하기\n", "반복자는 아마도 리스트를 순회하는 예제를 통해서 가장 쉽게 이해할 수 있습니다.\n", "다음 예를 보죠:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3 5 7 9 11 " ] } ], "source": [ "for value in [2, 4, 6, 8, 10]:\n", " # 어떤 작업을 수행합니다\n", " print(value + 1, end=' ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"``for x in y``\" 문법은 리스트에 있는 각 원소에 대해 어떤 연산을 반복하도록 만들어 줍니다.\n", "이 코드의 문법이 영어의 문장(\"*for [each] value in [the] list*\")과 비슷한 것은 파이썬을 배우고 사용하기 쉬운 언어로 만들기 위한 선택 중 하나일 뿐입니다.\n", "\n", "하지만 보이는 모습이 실제로 일어나는 것과는 다릅니다.\n", "\"``for val in L``\"와 같이 쓰면, 파이썬 인터프리터는 *반복자* 인터페이스를 가졌는지 확인합니다. 여러분이 직접 내장 ``iter`` 함수를 사용하여 확인할 수도 있습니다:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "iter([2, 4, 6, 8, 10])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 반복자가 ``for`` 루프에서 필요한 기능을 제공합니다. ``iter`` 객체는 내장 함수 ``next``로 유효한 다음 객체를 참조할 수 있습니다:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "I = iter([2, 4, 6, 8, 10])" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "print(next(I))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4\n" ] } ], "source": [ "print(next(I))" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n" ] } ], "source": [ "print(next(I))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이렇게 반복자를 둔 이유가 무엇일까요? 파이썬에서 *실제로 리스트가 아닌* 것을 리스트처럼 다룰 수 있기 때문에 아주 유용한 기능입니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ``range()``: 리스트가 항상 리스트는 아니다\n", "이런 간접적인 반복의 가장 흔한 예제는 파이썬 3의 ``range()`` 함수입니다(파이썬 2에서는 ``xrange()``). 이 함수는 리스트가 아니라 특별한 ``range()`` 객체를 반환합니다:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "range(0, 10)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "range(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "리스트와 마찬가지로 ``range``는 반복자를 가집니다:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "iter(range(10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "그래서 파이썬은 이를 *마치* 리스트처럼 다룹니다:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 2 3 4 5 6 7 8 9 " ] } ], "source": [ "for i in range(10):\n", " print(i, end=' ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "간접적인 반복자의 장점은 *전체 리스트가 완전히 생성되지 않는다는 것입니다!*\n", "만약 실제로 만들어졌다면 시스템 메모리를 초과하는 범위를 만들어 봄으로써 알 수 있습니다\n", "(파이썬 2에서는 ``range``가 리스트를 만들기 때문에 다음을 실행하면 좋은 결과를 얻지 못할 것입니다!):" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " ] } ], "source": [ "N = 10 ** 12\n", "for i in range(N):\n", " if i >= 10: break\n", " print(i, end=', ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "``range``가 실제로 1조개의 리스트를 만들었다면 10테라 바이트의 메모리가 필요합니다. 여기서는 단지 10개의 값만 필요하기 때문에 이는 낭비입니다!\n", "\n", "사실 반복자가 끝을 가질 필요가 전혀 없습니다!\n", "파이썬의 ``itertools`` 라이브러리는 무한한 범위를 가진 것처럼 동작하는 ``count`` 함수를 가지고 있습니다:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " ] } ], "source": [ "from itertools import count\n", "\n", "for i in count():\n", " if i >= 10:\n", " break\n", " print(i, end=', ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "``break`` 문을 사용하지 않으면 프로세스 직접 중지시키거나 종료하기 전까지(예를 들어 ``ctrl-C``로) 계속 카운트가 증가할 것입니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 유용한 반복자\n", "반복자 문법은 나중에 보게될 데이터 과학 라이브러리 뿐만 아니라 파이썬의 내장 타입에 광범위하게 사용됩니다.\n", "파이썬 언어에서 유용한 대표적인 반복자 일부를 살펴 보겠습니다:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ``enumerate``\n", "종종 배열에 있는 값 뿐만 아니라 인덱스를 반복해야할 때도 있습니다.\n", "다음과 같이 할지도 모르겠습니다:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 2\n", "1 4\n", "2 6\n", "3 8\n", "4 10\n" ] } ], "source": [ "L = [2, 4, 6, 8, 10]\n", "for i in range(len(L)):\n", " print(i, L[i])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이렇게 해도 되지만 파이썬에서는 ``enumerate`` 반복자를 사용한 더 깔끔한 문법을 제공합니다:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 2\n", "1 4\n", "2 6\n", "3 8\n", "4 10\n" ] } ], "source": [ "for i, val in enumerate(L):\n", " print(i, val)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이것이 리스트에 있는 인덱스와 값을 나열하는 더 파이썬스러운 방법입니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ``zip``\n", "어떨 때는 여러개의 리스트를 동시에 반복해야 할 필요가 있습니다.\n", "앞서 본 파이썬스럽지 않은 예에서처럼 인덱스를 반복할 수 있지만 ``zip`` 반복자를 사용하여 함께 반복할 것을 묶어주는 것이 더 좋습니다:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 3\n", "4 6\n", "6 9\n", "8 12\n", "10 15\n" ] } ], "source": [ "L = [2, 4, 6, 8, 10]\n", "R = [3, 6, 9, 12, 15]\n", "for lval, rval in zip(L, R):\n", " print(lval, rval)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "반복할 수 있는 것은 몇 개든 함께 쓸 수 있습니다. 길이가 다를 경우 가장 짧은 것이 ``zip``의 길이를 결정합니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ``map`` and ``filter``\n", "``map`` 반복자는 함수를 받고 반복자의 값을 적용합니다:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 4 9 16 25 36 49 64 81 " ] } ], "source": [ "# 10까지 수를 제곱합니다\n", "square = lambda x: x ** 2\n", "for val in map(square, range(10)):\n", " print(val, end=' ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "``filter`` 반복자는 비슷하지만 필터 함수가 참으로 평가한 값만 통과시킵니다:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 2 4 6 8 " ] } ], "source": [ "# 10까지 x % 2이 0이 되는 수를 출력합니다\n", "is_even = lambda x: x % 2 == 0\n", "for val in filter(is_even, range(10)):\n", " print(val, end=' ')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "``map``, ``filter`` 함수와 ``reduce`` 함수(파이썬의 ``functools`` 모듈에 있습니다)는 *함수형 프로그래밍* 스타일의 기본 요소입니다. 파이썬 세계의 주류 프로그래밍 스타일은 아니지만 열렬한 지지자도 있습니다(예를 들면 [pytoolz](https://toolz.readthedocs.org/en/latest/) 라이브러리)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 함수 매개변수로서의 반복자\n", "\n", "유연한 매개변수인 ``*args``와 ``**kwargs``를 보았습니다. ``*args``와 ``**kwargs``는 함수에게 시퀀스와 딕셔너리를 전달하는데 사용됩니다. ``*args`` 문법은 시퀀스뿐만이 아니라 반복자와 함께 쓰일 수 있습니다:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 2 3 4 5 6 7 8 9\n" ] } ], "source": [ "print(*range(10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "예를 들어 이전의 ``map`` 예를 다음과 같이 압축해서 쓸 수 있습니다:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 1 4 9 16 25 36 49 64 81\n" ] } ], "source": [ "print(*map(lambda x: x ** 2, range(10)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이런 기법을 사용하면 파이썬 포럼에 올라오는 오래된 질문에 대답을 할 수 있습니다. \"왜 ``zip()``에 반대되는 ``unzip()`` 함수는 없나요?\"\n", "잠시 눈을 감고 생각해 보면 ``zip()``의 반대는 ``zip()``이라는 것을 알게 될 것입니다. \n", "``zip()``은 몇 개의 반복자나 시퀀스로 모두 묶을 수 있다는 것이 핵심입니다. 한 번 살펴 보죠:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "L1 = (1, 2, 3, 4)\n", "L2 = ('a', 'b', 'c', 'd')" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1, 'a') (2, 'b') (3, 'c') (4, 'd')\n" ] } ], "source": [ "z = zip(L1, L2)\n", "print(*z)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1, 2, 3, 4) ('a', 'b', 'c', 'd')\n" ] } ], "source": [ "z = zip(L1, L2)\n", "new_L1, new_L2 = zip(*z)\n", "print(new_L1, new_L2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "잠시 생각해 보세요. 왜 이렇게 동작하는지 이해했다면 파이썬의 반복자를 많이 이해한 것입니다!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 특별한 반복자: ``itertools``\n", "무한 ``range`` 반복자인 ``itertools.count``를 잠시 보았습니다.\n", "``itertools`` 모듈은 유용한 반복자를 모두 담고 있습니다. 모듈에 담긴 내용을 살펴 볼 가치가 충분히 있습니다.\n", "한가지 예로 시퀀스의 모든 순열을 반복해 주는 ``itertools.permutations`` 함수를 살펴 보겠습니다:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 1, 2) (0, 2, 1) (1, 0, 2) (1, 2, 0) (2, 0, 1) (2, 1, 0)\n" ] } ], "source": [ "from itertools import permutations\n", "p = permutations(range(3))\n", "print(*p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "비슷하게 ``itertools.combinations`` 함수는 리스트에서 ``N`` 개를 뽑아 중복되지 않은 조합을 반복합니다:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 1) (0, 2) (0, 3) (1, 2) (1, 3) (2, 3)\n" ] } ], "source": [ "from itertools import combinations\n", "c = combinations(range(4), 2)\n", "print(*c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이는 두 개 이상의 반복 가능한 쌍의 집합을 반복해 주는 ``product`` 반복자와 관련이 있습니다:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('a', 0) ('a', 1) ('a', 2) ('b', 0) ('b', 1) ('b', 2)\n" ] } ], "source": [ "from itertools import product\n", "p = product('ab', range(3))\n", "print(*p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "``itertools``에는 유용한 반복자가 많이 있습니다. 파이썬 [온라인 문서](https://docs.python.org/ko/3/library/itertools.html)에서 전체 리스트와 예제를 볼 수 있습니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "< [에러와 예외처리](09-에러와 예외처리.ipynb) | [목차](목차.ipynb) | [리스트 내포](11-리스트 내포.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 }