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