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