{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 16. in을 사용하고 딕셔너리 키가 없을 때 KeyError를 처리하기보다는 get을 사용하라"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "딕셔너리의 내용은 동적이므로 어떤 키에 접근하거나 키를 삭제할 때 그 키가 딕셔너리에 없을 수도 있다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "counters = {\n",
    "    '품퍼니켈': 2,\n",
    "    '사워도우': 1,\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "투표를 할때 없는 키에 대해서는 아래와 같이 처리해야 한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "key = '밀'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "if key in counters:\n",
    "    count = counters[key]\n",
    "else:\n",
    "    count = 0\n",
    "\n",
    "counters[key] = count + 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "예외를 활용한 방법도 있다"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    count = counters[key]\n",
    "except KeyError:\n",
    "    count = 0\n",
    "\n",
    "counters[key] = count + 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이런 겨우에는 dict 내장 메서드인 **get** 을 사용하자\n",
    "\n",
    "get의 두번째 인자는 첫번째 인자인 키가 딕셔너리에 들어 있지 않을 떄 돌려주는 디폴트 값이다"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "count = counters.get(key, 0)\n",
    "counters[key] = count + 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "다양한 방법으로 표현할 수 있으나, 대부분 대입을 중복 사용해야 하므로 코드 가독성이 떨어진다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "if key not in counters:\n",
    "    counters[key] = 0\n",
    "counters[key] += 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "if key in counters:\n",
    "    counters[key] += 1\n",
    "else:\n",
    "    counters[key] = 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    counters[key] += 1\n",
    "except KeyError:\n",
    "    counters[key] = 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "get을 쓰자"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "그리고 위와 같은 경우 collection 모듈에 존재하는 Counter를 고려해보자"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "딕셔너리에 저장된 값이 리스트처럼 더 복잡한 값일 경우\n",
    "\n",
    "또한 특표수만 세는 것이 아니라 어떤 사람이 어떤 유형의 빵에 투표했는지도 알고 싶을 때\n",
    "\n",
    "이런 경우에는 각 키마다 이름이 들어 있는 리스트를 연관 시킬 수 있다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "votes = {\n",
    "    '바게트': ['철수', '순이'],\n",
    "    '치아바타': ['하니', '유리'],\n",
    "}\n",
    "key = '브리오슈'\n",
    "who = '단이'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'바게트': ['철수', '순이'], '치아바타': ['하니', '유리'], '브리오슈': ['단이']}\n"
     ]
    }
   ],
   "source": [
    "if key in votes:\n",
    "    names = votes[key]\n",
    "else:\n",
    "    votes[key] = names = []\n",
    "\n",
    "names.append(who)\n",
    "print(votes)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이 예제는 키가 존재하지 않을 때 맹목적으로 빈 리스트를 디폴트 값으로 대입할 수 있다.\n",
    "\n",
    "이중 대입문인 votes[key] = names = [] 는 키 대입을 두 줄이 아니라 한 줄로 처리한다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "names = []  \n",
    "votest[key] = names"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "딕셔너리 값이 리스트인 경우 KeyError 예외가 발생하므로 예외처리를 이용하면 더 간편하다"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    names = votes[key]\n",
    "except KeyError:\n",
    "    votes[key] = names = []\n",
    "\n",
    "names.append(who)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "마찬가지로 get을 사용 할 수 있다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "names = votes.get(key)\n",
    "if names is None:\n",
    "    votes[key] = names = []\n",
    "\n",
    "names.append(who)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "names = votes.get(key, [])\n",
    "names.append(who)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "invalid syntax (<ipython-input-19-256beb912819>, line 1)",
     "output_type": "error",
     "traceback": [
      "\u001b[1;36m  File \u001b[1;32m\"<ipython-input-19-256beb912819>\"\u001b[1;36m, line \u001b[1;32m1\u001b[0m\n\u001b[1;33m    if (names := votes.get(key)) is None:\u001b[0m\n\u001b[1;37m              ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n"
     ]
    }
   ],
   "source": [
    "if (names := votes.get(key)) is None:\n",
    "    votes[key] = names = []\n",
    "    \n",
    "# 파이썬 버전문제\n",
    "names.append(who)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "dict 타입은 이 패턴을 더 간단히 사용할 수 있게 해주는 setdefault 메서드를 제공한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "names = votes.setdefault(key, [])\n",
    "names.append(who)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "키가 없으면 setdefault에 전달된 디폴트 값이 별도로 복사되지 않고 딕셔너리에 직접 대입된다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "이전: {'foo': []}\n",
      "이후:  {'foo': ['hello']}\n"
     ]
    }
   ],
   "source": [
    "data = {}\n",
    "key = 'foo'\n",
    "value = []\n",
    "data.setdefault(key, value)\n",
    "print('이전:', data)\n",
    "value.append('hello')\n",
    "print('이후: ', data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "따라서 매 호출마다 새로 만들어줘야 한다.\n",
    "\n",
    "처음 예제에서는 왜 setdefault를 사용하지 않았을까?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "count = counters.setdefault(key, 0)\n",
    "counters[key] = count + 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "setdefault의 경우 위에서 처럼 대입을 두번 한다.\n",
    "\n",
    "하지만 get은 대입을 한번만 한다.\n",
    "\n",
    "따라서 get이 더 효율적이다"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "setdefault를 쓰기보다는 defaultdict이 더 좋을 수 있다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 기억해야 할 내용\n",
    "- 딕셔너리 키가 없는 경우를 처리하는 방법으로는 in식을 사용하는 방법, KeyError 예외를 사용하는 방법, get 메서드를 사용하는 방법, setdefault 메서드를 사용하는 방법이 있다.\n",
    "- 카운터와 같이 기본적인 타입의 값이 들어가는 딕셔너리를 다룰 때는 get 가 가장 좋고, 딕셔너리에 넣을 값을 만드는 비용이 비싸거나 만드는 과정에 예외가 발생할 수 있는 경우에도 get 메서드를 사용하는 편이 낫다.\n",
    "- 해결하려는 문제에 dict의 setdefault 메서드를 사용하는 방법이 가장 적합해 보인다면 setdefault 대신 defaultdict을 사용할지를 고려해라"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**get은 default 값을 리턴해주고, setdefault는 key에 대한 value를 직접 대입해준다**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "t = {\n",
    "    \"a\": 1, \n",
    "    \"b\": 2\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'a': 1, 'b': 2}"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.get(\"a\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "t.get(\"c\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.get(\"c\", 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t.setdefault(\"c\", 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'a': 1, 'b': 2, 'c': 3}"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t"
   ]
  }
 ],
 "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
}