{
 "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
}