{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 26. functools.wrap을 사용해 함수 데코레이터를 정의하라"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "파이썬은 함수에 적용할 수 있는 데코레이터를 정의하는 특별한 구문을 제공한다.\n",
    "\n",
    "데코레이터는 자신이 감싸고 있는 함수가 호출되기 전과 후에 코드를 추가로 실행해준다.\n",
    "\n",
    "이는 데코레이터가 자신이 감싸고 있는 함수의 입력 인자, 반환값, 함수에서 발생한 오류에 접근할 수 있다는 뜻이다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def trace(func):\n",
    "    def wrapper(*args, **kwargs):\n",
    "        result = func(*args, **kwargs)\n",
    "        print(f'{func.__name__}({args!r}, {kwargs!r}) '\n",
    "              f'-> {result!r}')\n",
    "        return result\n",
    "    return wrapper"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이 데코레이터를 함수에 적용할 떄는 @ 기호를 사용한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "@trace\n",
    "def fibonacci(n):\n",
    "    \"\"\"n번쨰 피보나치 수를 반환한다.\"\"\"\n",
    "    if n in (0, 1):\n",
    "        return n\n",
    "    return (fibonacci(n - 2) + fibonacci(n - 1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "fibonacci((0,), {}) -> 0\n",
      "fibonacci((1,), {}) -> 1\n",
      "fibonacci((2,), {}) -> 1\n",
      "fibonacci((1,), {}) -> 1\n",
      "fibonacci((0,), {}) -> 0\n",
      "fibonacci((1,), {}) -> 1\n",
      "fibonacci((2,), {}) -> 1\n",
      "fibonacci((3,), {}) -> 2\n",
      "fibonacci((4,), {}) -> 3\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fibonacci(4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "fibonacci = trace(fibonacci)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "fibonacci((0,), {}) -> 0\n",
      "wrapper((0,), {}) -> 0\n",
      "fibonacci((1,), {}) -> 1\n",
      "wrapper((1,), {}) -> 1\n",
      "fibonacci((2,), {}) -> 1\n",
      "wrapper((2,), {}) -> 1\n",
      "fibonacci((1,), {}) -> 1\n",
      "wrapper((1,), {}) -> 1\n",
      "fibonacci((0,), {}) -> 0\n",
      "wrapper((0,), {}) -> 0\n",
      "fibonacci((1,), {}) -> 1\n",
      "wrapper((1,), {}) -> 1\n",
      "fibonacci((2,), {}) -> 1\n",
      "wrapper((2,), {}) -> 1\n",
      "fibonacci((3,), {}) -> 2\n",
      "wrapper((3,), {}) -> 2\n",
      "fibonacci((4,), {}) -> 3\n",
      "wrapper((4,), {}) -> 3\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fibonacci(4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<function trace.<locals>.wrapper at 0x7f21d86579d0>\n"
     ]
    }
   ],
   "source": [
    "print(fibonacci)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on function wrapper in module __main__:\n",
      "\n",
      "wrapper(*args, **kwargs)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(fibonacci)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "ename": "AttributeError",
     "evalue": "Can't pickle local object 'trace.<locals>.wrapper'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-24-689374fb5a49>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpickle\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mpickle\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfibonacci\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mAttributeError\u001b[0m: Can't pickle local object 'trace.<locals>.wrapper'"
     ]
    }
   ],
   "source": [
    "import pickle\n",
    "\n",
    "pickle.dumps(fibonacci)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "문제를 해결하는 방법은 functools 내장 모듈에 정의된 waps 도우미 함수를 사용하는 것이다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "from functools import wraps\n",
    "\n",
    "def trace(func):\n",
    "    @wraps(func)\n",
    "    def wrapper(*args, **kwargs):\n",
    "        result = func(*args, **kwargs)\n",
    "        print(f'{func.__name__}({args!r}, {kwargs!r}) '\n",
    "              f'-> {result!r}')\n",
    "        return result\n",
    "    return wrapper"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "@trace\n",
    "def fibonacci(n):\n",
    "    \"\"\"n번쨰 피보나치 수를 반환한다.\"\"\"\n",
    "    if n in (0, 1):\n",
    "        return n\n",
    "    return (fibonacci(n - 2) + fibonacci(n - 1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on function fibonacci in module __main__:\n",
      "\n",
      "fibonacci(n)\n",
      "    n번쨰 피보나치 수를 반환한다.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(fibonacci)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "하지만 키워드 인자와 디폴트 값은 예상대로 잘 작동한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'\\x80\\x04\\x95\\x1a\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x08__main__\\x94\\x8c\\tfibonacci\\x94\\x93\\x94.'"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pickle\n",
    "\n",
    "pickle.dumps(fibonacci)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 기억해야 할 내용\n",
    "- 파이썬 데코레이터는 실행 시점에 함수가 다른 함수를 변경할 수 있게 해주는 구문이다.\n",
    "- 데코레이터를 사용하면 디버거 등 인트로스펙션을 사용하는 도구가 잘못 작동할 수 있다.\n",
    "- 직접 데코레이터를 구현할 때 인트로스펙션에서 문제가 생기지 않길 바란다면 functools 내장 모듈의 wraps 데코레이터를 사용하라."
   ]
  }
 ],
 "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
}