{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 24. None과 독스트링을 사용해 동적인 디폴트 인자를 지정하라" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "종종 키워드 인자의 값으로 정적으로 정해지지 않는 타입의 값을 써야 할 때가 있다." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from time import sleep\n", "from datetime import datetime" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def log(message, when=datetime.now()):\n", " print(f'{when}: {message}')" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2021-02-23 00:57:48.924599: 안녕\n" ] } ], "source": [ "log('안녕')" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2021-02-23 00:57:48.924599: 다시 안녕!\n" ] } ], "source": [ "log('다시 안녕!')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "디폴트 인자는 함수가 정의되는 시점에 한번 호출되므로 타임스탬프는 고정" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "디폴트 값으로 None을 지정하고 실제 동작을 독스트링에 문서화해야 함" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def log(message, when=None):\n", " \"\"\"메시지와 타임스탬프를 로그에 남긴다.\n", " \n", " Args:\n", " message: 출력할 메시지.\n", " when : 메시지가 발생한 시각(datetime).\n", " 디폴트 값은 현재 시간이다.\n", " \"\"\"\n", " if when is None:\n", " when = datetime.now()\n", " print(f'{when}: {message}')" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2021-02-23 01:00:32.705004: 안녕\n" ] } ], "source": [ "log('안녕')" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2021-02-23 01:00:35.008693: 다시 안녕!\n" ] } ], "source": [ "log('다시 안녕!')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "디폴트 인자 값으로 None을 사용하는 것은 인자가 가변적인 경우 특히 중요하다.\n", "\n", "예를 들어 JSON 데이터로 인코딩된 값을 읽으려고 하는데, 데이터 디코딩에 실패하면 디폴트로 빈 딕셔너리를 반환하고 싶다." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "import json" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "def decode(data, default={}):\n", " try:\n", " return json.loads(data)\n", " except ValueError:\n", " return default" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "foo = decode('잘못된 데이터')" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "foo['stuff'] = 5" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "bar = decode('또 잘못된 데이터')" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "bar['meep'] = 1" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'stuff': 5, 'meep': 1}\n" ] } ], "source": [ "print(foo)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'stuff': 5, 'meep': 1}\n" ] } ], "source": [ "print(bar)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "딕셔너리가 디폴트 파라미터로 같기 때문에 동일한 객체를 쓰게된다." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "def decode(data, default=None):\n", " \"\"\"문자열로부터 JSON 데이터를 읽어온다.\n", " \n", " Args:\n", " data: 디코딩할 JSON 데이터.\n", " default: 디코딩 실패 시 반환할 값이다.\n", " 디폴트 값은 빈 딕셔너리다.\n", " \"\"\"\n", " try:\n", " return json.loads(data)\n", " except ValueError:\n", " if default is None:\n", " default = {}\n", " return default" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "foo = decode('잘못된 데이터')\n", "foo['stuff'] = 5\n", "bar = decode('또 잘못된 데이터')\n", "bar['meep'] = 1" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'stuff': 5}\n", "{'meep': 1}\n" ] } ], "source": [ "print(foo)\n", "print(bar)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 방법은 타입 애너테이션을 사용해도 잘 작동한다." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "from typing import Optional" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "def log_typed(message: str, when: Optional[datetime]=None) -> None:\n", " \"\"\"메시지와 타임스탬프를 로그에 남긴다.\n", " \n", " Args:\n", " message: 출력할 메시지.\n", " when : 메시지가 발생한 시각(datetime).\n", " 디폴트 값은 현재 시간이다.\n", " \"\"\"\n", " if when is None:\n", " when = datetime.now()\n", " print(f'{when}: {message}')" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2021-02-23 01:06:29.832369: 안녕\n" ] } ], "source": [ "log_typed('안녕')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 기억해야 할 내용\n", "- 디폴트 인자 값은 그 인자가 포함된 함수 정의가 속한 모듈이 로드되는 시점에 단 한 번만 평가 된다. 이로 인해 동적인 값({}, [], datetime.now() 등)의 경우 이상한 동작이 일어날 수 있다.\n", "- 동적인 값을 가질 수 있는 키워드 인자의 디폴트 값을 표현할 때는 None을 사용하라, 그리고 함수의 독스트링에 실제 동적인 디폴트 인자가 어떻게 동작하는지 문서화해두라.\n", "- 타입 애너테이션을 사용할 때도 None을 사용해 키워드 인자의 디폴트 값을 표현하는 방식을 적용할 수 있다." ] } ], "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 }