{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 10. 대입식(:=)을 사용해 반복을 피하라"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "python3.8에서 도입된 Assignment expression, 왈러스 연산자 라고도 부른다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "일반 대입문은 a = b  \n",
    "왈러스 연산자는 a := b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "대입문을 쓸수 없는 위치에서 변수에 값을 대입할 수 있음"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "fresh_fruit = {\n",
    "    '사과': 10,\n",
    "    '바나나': 8,\n",
    "    '레몬': 5,\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_lemnonade(count):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def out_of_stock():\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "count = fresh_fruit.get('레몬', 0)\n",
    "if count:\n",
    "    make_lemnonade(count)\n",
    "else:\n",
    "    out_of_stock()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "count 변수는 if 문의 첫번째 블록에서만 사용됨\n",
    "\n",
    "이처럼 파이썬에서는 값을 가져와서 그 값이 0이 아닌지 검사한 후 사용하는 패턴이 자주 발생\n",
    "\n",
    "이를 위해 가독성이 떨어지는 꼼수를 사용했지만 대입식을 이용하면 해결이 된다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "if count := fresh_fruit.get('레몬', 0):\n",
    "    make_lemnonade(count)\n",
    "else:\n",
    "    out_of_stock()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**대입 후 평가가 왈러스 연산자의 핵심**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_cider(count):\n",
    "    print('make_cider')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "count = fresh_fruit.get('사과', 0)\n",
    "if count >= 4:\n",
    "    make_cider(count)\n",
    "else:\n",
    "    out_of_stock()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "make_cider\n"
     ]
    }
   ],
   "source": [
    "if (count := fresh_fruit.get('사과', 0)) >= 4:\n",
    "    make_cider(count)\n",
    "else:\n",
    "    out_of_stock()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "대입 결과와 4를 비교하기 위해 대입식을 괄호로 둘러싸야 한다.\n",
    "\n",
    "**둘러싸지 않을 경우 비교가 먼저 되고 그 값이 count에 대입된다.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10\n",
      "make_cider\n"
     ]
    }
   ],
   "source": [
    "if (count := fresh_fruit.get('사과', 0)) >= 4:\n",
    "    print(count)\n",
    "    make_cider(count)\n",
    "else:\n",
    "    out_of_stock()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "make_cider\n"
     ]
    }
   ],
   "source": [
    "if count := fresh_fruit.get('사과', 0) >= 4:\n",
    "    print(count)\n",
    "    make_cider(count)\n",
    "else:\n",
    "    out_of_stock()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "조건에 따라 현재 위치를 둘러싸는 영역에 있는 변수에 값을 대입하고 그 변수를 바로 함수 호출에 사용하는 경우를 들 수 있다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "def slice_bananas(count):\n",
    "    print('slice bananas')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "class OutOfBananas(Exception):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_smoothies(count):\n",
    "    print('make smoothies')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "slice bananas\n",
      "make smoothies\n"
     ]
    }
   ],
   "source": [
    "prices = 0\n",
    "count = fresh_fruit.get('바나나', 0)\n",
    "if count >= 2:\n",
    "    pieces = slice_bananas(count)\n",
    "\n",
    "try:\n",
    "    smoothies = make_smoothies(pieces)\n",
    "except OutOfBananas:\n",
    "    out_of_stock()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "로직을 수행하는 다른 방식은 pieces = 0 대입문을 else 블록에 넣는 것이다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "slice bananas\n",
      "make smoothies\n"
     ]
    }
   ],
   "source": [
    "count = fresh_fruit.get('바나나', 0)\n",
    "if count >= 2:\n",
    "    pieces = slice_bananas(count)\n",
    "else:\n",
    "    pieces = 0\n",
    "\n",
    "try:\n",
    "    smoothies = make_smoothies(pieces)\n",
    "except OutOfBananas:\n",
    "    out_of_stock()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "대부분 첫번째를 선호한다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "왈러스 연산자를 이용해보자"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "slice bananas\n",
      "make smoothies\n"
     ]
    }
   ],
   "source": [
    "pieces = 0\n",
    "if (count := fresh_fruit.get('바나나', 0)) >= 2:\n",
    "    pieces = slice_bananas(count)\n",
    "    \n",
    "try:\n",
    "    smoothies = make_smoothies(pieces)\n",
    "except OutOfBananas:\n",
    "    out_of_stock()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "slice bananas\n",
      "make smoothies\n"
     ]
    }
   ],
   "source": [
    "if (count := fresh_fruit.get('바나나', 0)) >= 2:\n",
    "    pieces = slice_bananas(count)\n",
    "else:\n",
    "    pieces = 0\n",
    "    \n",
    "try:\n",
    "    smoothies = make_smoothies(pieces)\n",
    "except OutOfBananas:\n",
    "    out_of_stock()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "파이썬에는 유연한 switch/case 문이 없다\n",
    "\n",
    "대신 if, elif, else 문을 깊게 내포시키는 방법이 있다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "slice bananas\n",
      "make smoothies\n"
     ]
    }
   ],
   "source": [
    "count = fresh_fruit.get('바나나', 0)\n",
    "if count >= 2:\n",
    "    pieces = slice_bananas(count)\n",
    "    to_enjoy = make_smoothies(pieces)\n",
    "else:\n",
    "    count = fresh_fruit.get('사과', 0)\n",
    "    if count >= 4:\n",
    "        to_enjoy = make_cider(count)\n",
    "    else:\n",
    "        count = fresh_fruit.get('레몬', 0)\n",
    "        if count:\n",
    "            to_enjoy = make_lemonade(count)\n",
    "        else:\n",
    "            to_enjoy = '아무것도 없음'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "왈러스 연산자를 이용해보자"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "slice bananas\n",
      "make smoothies\n"
     ]
    }
   ],
   "source": [
    "if (count := fresh_fruit.get('바나나', 0)) >= 2:\n",
    "    pieces = slice_bananas(count)\n",
    "    to_enjoy = make_smoothies(pieces)\n",
    "elif (count := fresh_fruit.get('사과', 0)) >= 4:\n",
    "    to_enjoy = make_cider(count)\n",
    "elif count := fresh_fruit.get('레몬', 0):\n",
    "    to_enjoy = make_lemonade(count)\n",
    "else:\n",
    "    to_enjoy = '아무것도 없음'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "do/while 루프가 없다는 점도 당황스럽다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "\n",
    "def pick_fruit():\n",
    "    if random.randint(1,10) > 2:   # 80% 확률로 새 과일 보충\n",
    "        return {\n",
    "            '사과': random.randint(0,10),\n",
    "            '바나나': random.randint(0,10),\n",
    "            '레몬': random.randint(0,10),\n",
    "        }\n",
    "    else:\n",
    "        return None\n",
    "\n",
    "def make_juice(fruit, count):\n",
    "    if fruit == '사과':\n",
    "        return [('사과주스', count/4)]\n",
    "    elif fruit == '바나나':\n",
    "        return [('바나나스무디',count/2)]\n",
    "    elif fruit == '레몬':\n",
    "        return [('레모네이드',count/1)]\n",
    "    else:\n",
    "        return []"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "bottles = []\n",
    "fresh_fruit = pick_fruit()\n",
    "while fresh_fruit:\n",
    "    for fruit, count in fresh_fruit.items():\n",
    "        batch = make_juice(fruit, count)\n",
    "        bottles.extend(batch)\n",
    "    fresh_fruit = pick_fruit()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('사과주스', 1.5), ('바나나스무디', 4.5), ('레모네이드', 0.0)]\n"
     ]
    }
   ],
   "source": [
    "print(bottles)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이 코드는 fresh_fruit = pick_fruit() 호출을 두 번 (한번은 루프 직전에 초기화) 호출하고, 다른 한번은 루프의 끝에서 사용한다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "코드 재사용을 향상시키기 위한 전략은 **무한 루프-중간에서 끝내기(loop-and-a-half)** 관용어를 사용하는 것이다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이 관용어를 사용하면 코드 반복을 없앨 수 있지만, while 루프를 맹목적인 무한 루프로 만들기 때문에 while 루프의 유용성이 줄어든다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "bottles = []\n",
    "while True: # 무한루프\n",
    "    fresh_fruit = pick_fruit()\n",
    "    if not fresh_fruit: # 중간에서 끝내기\n",
    "        break\n",
    "\n",
    "    for fruit, count in fresh_fruit.items():\n",
    "        batch = make_juice(fruit, count)\n",
    "        bottles.extend(batch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('사과주스', 1.75), ('바나나스무디', 4.5), ('레모네이드', 4.0), ('사과주스', 1.5), ('바나나스무디', 3.5), ('레모네이드', 2.0), ('사과주스', 0.25), ('바나나스무디', 1.5), ('레모네이드', 7.0), ('사과주스', 0.0), ('바나나스무디', 2.5), ('레모네이드', 1.0)]\n"
     ]
    }
   ],
   "source": [
    "print(bottles)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**왈러스 연산자를 사용하면 while 루프에서 매번 fresh_fruit 변수에 대입하고 조건을 감사할 수 있다.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "bottles = []\n",
    "while fresh_fruit := pick_fruit():\n",
    "    for fruit, count in fresh_fruit.items():\n",
    "        batch = make_juice(fruit, count)\n",
    "        bottles.extend(batch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('사과주스', 2.25), ('바나나스무디', 1.5), ('레모네이드', 3.0), ('사과주스', 0.25), ('바나나스무디', 3.0), ('레모네이드', 4.0), ('사과주스', 1.5), ('바나나스무디', 1.5), ('레모네이드', 8.0), ('사과주스', 1.5), ('바나나스무디', 2.0), ('레모네이드', 1.0), ('사과주스', 0.75), ('바나나스무디', 0.5), ('레모네이드', 5.0), ('사과주스', 1.0), ('바나나스무디', 4.5), ('레모네이드', 7.0)]\n"
     ]
    }
   ],
   "source": [
    "print(bottles)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 기억해야 할 내용\n",
    "- 대입식에서는 왈러스 연산자(:=)를 사용해 하나의 식 안에서 변수 이름에 값을 대입하면서 이 값을 평가할 수 있고, 중복을 줄일 수 있다.\n",
    "- 대입식이 더 큰 식의 일부분으로 쓰일 때는 괄호로 둘러싸야 한다.\n",
    "- 파이썬에서는 switch/case 문이나 do/while 루프를 쓸 수 없지만, 대입식을 사용하면 이런 기능을 더 깔끔하게 흉내 낼 수 있다."
   ]
  }
 ],
 "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
}