{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## 29. 대입식을 사용해 컴프리헨션 안에서 반복 작업을 피하라" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "컴프리헨션에서 같은 계산을 여러 위치에서 공유하는 경우가 흔하다." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "stock = {\n", " '못': 125,\n", " '나사못': 35,\n", " '나비너트': 8,\n", " '와셔': 24\n", "}" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "order = ['나사못', '나비너트', '클립']" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def get_batches(count, size):\n", " return count // size" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'나사못': 4, '나비너트': 1}\n" ] } ], "source": [ "result = {}\n", "for name in order:\n", " count = stock.get(name, 0)\n", " batches = get_batches(count, 8)\n", " if batches:\n", " result[name] = batches\n", "\n", "print(result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "여기서 딕셔너리 컴프리헨션을 사용하면 이 루프의 로직을 더 간결하게 표현할 수 있다." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'나사못': 4, '나비너트': 1}\n" ] } ], "source": [ "found = {name: get_batches(stock.get(name, 0), 8)\n", " for name in order\n", " if get_batches(stock.get(name, 0), 8)}\n", "print(found)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 코드는 앞의 코드보다 짧지만 반복된다는 단점이 있다.\n", "\n", "아래와 같은 실수를 할 수 있다." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'나사못': 8, '나비너트': 2}\n" ] } ], "source": [ "found = {name: get_batches(stock.get(name, 0), 4) ## <- 4\n", " for name in order\n", " if get_batches(stock.get(name, 0), 8)}\n", "print(found)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이러한 문제에 대한 쉬운 해법은 파이썬 3.8에 도입된 왈러스 연산자(:=)를 사용하는 것이다." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "found = {name: batches for name in order if (batches := get_batches(stock.get(name, 0), 8))}" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'나사못': 4, '나비너트': 1}" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "found" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "batches" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "대입식을 컴프리헨션의 값 식에 사용해도 문법적으로 올바르다.\n", "\n", "하지만 컴프리헨션의 다른 부분에서 이 변수를 읽으려고 하면 컴프리헨션이 평가되는 순서 때문에 실행 시점에 오류가 발생할 것이다." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'tenth' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m<ipython-input-13-fc56e5c9d356>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m result = {name: (tenth := count // 10)\n\u001b[0m\u001b[1;32m 2\u001b[0m for name, count in stock.items() if tenth > 0}\n", "\u001b[0;32m<ipython-input-13-fc56e5c9d356>\u001b[0m in \u001b[0;36m<dictcomp>\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 1\u001b[0m result = {name: (tenth := count // 10)\n\u001b[0;32m----> 2\u001b[0;31m for name, count in stock.items() if tenth > 0}\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'tenth' is not defined" ] } ], "source": [ "result = {name: (tenth := count // 10)\n", " for name, count in stock.items() if tenth > 0}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "대입식을 조건 쪽으로 옮기면 사용 가능하다" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "result = {name: tenth for name, count in stock.items()\n", " if (tenth := count // 10) > 0}" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'못': 12, '나사못': 3, '와셔': 2}\n" ] } ], "source": [ "print(result)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tenth" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "컴프리헨션이 값 부분에서 왈러스 연산자를 사용할 때 그 값에 대한 조건 부분이 없다면 루프 밖 영역으로 루프 변수가 누출된다." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[62, 17, 4, 12]의 마지막 원소는 12\n" ] } ], "source": [ "half = [(last := count // 2) for count in stock.values()]\n", "print(f'{half}의 마지막 원소는 {last}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이러한 루프 변수 누출은 일반적인 for 루프에서 발생하는 루프 변수 누출과 비슷하다." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[125, 35, 8, 24]의 마지막 원소는 24\n" ] } ], "source": [ "for count in stock.values():\n", " pass\n", "print(f'{list(stock.values())}의 마지막 원소는 {count}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "하지만 컴프리헨션의 루프 변수인 경우에는 비슷한 누출이 생기지 않는다." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "half = [count // 2 for count in stock.values()]" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[62, 17, 4, 12]\n" ] } ], "source": [ "print(half)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "24\n" ] } ], "source": [ "print(count) # 원래 에러나옴" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "따라서 컴프리헨션에서 대입식을 조건에만 사용하는 것을 권장한다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "제너레이터의 경우에도 똑같다" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "found = ((name, batches) for name in order\n", " if (batches := get_batches(stock.get(name, 0), 8)))" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('나사못', 4)\n" ] } ], "source": [ "print(next(found))" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('나비너트', 1)\n" ] } ], "source": [ "print(next(found))" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "<generator object <genexpr> at 0x7feeb760a4a0>" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "found" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 기억해야 할 내용\n", "- 대입식을 통해 컴프리헨션이나 제너레이터 식의 조건 부분에서 사용한 값을 같은 컴프리헨션이나 제너레이터의 다른 위치에서 재사용할 수 있다. 이를 통해 가독성과 성능을 향상시킬 수 있다.\n", "- 조건이 아닌 부분에도 대입식을 사용할 수 있지만, 그런 형태의 사용은 피해야 한다." ] } ], "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 }