{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# More on Decorators\n", "> A Summary of lecture \"Writing functions in Python\", via datacamp\n", "\n", "- toc: true \n", "- badges: true\n", "- comments: true\n", "- author: Chanseok Kang\n", "- categories: [Python, Datacamp]\n", "- image: images/logo.png" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Real-world examples" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Print the return type" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foo() returned type \n", "42\n", "foo() returned type \n", "[1, 2, 3]\n", "foo() returned type \n", "{'a': 42}\n" ] } ], "source": [ "def print_return_type(func):\n", " # Define wrapper(), the decorated function\n", " def wrapper(*args, **kwargs):\n", " # Call the function being decorated\n", " result = func(*args, **kwargs)\n", " print('{}() returned type {}'.format(func.__name__, type(result)))\n", " return result\n", " # Return the decorated function\n", " return wrapper\n", "\n", "@print_return_type\n", "def foo(value):\n", " return value\n", "\n", "print(foo(42))\n", "print(foo([1, 2, 3]))\n", "print(foo({'a': 42}))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Counter" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "calling foo()\n", "calling foo()\n", "foo() was called 2 times.\n" ] } ], "source": [ "def counter(func):\n", " def wrapper(*args, **kwargs):\n", " wrapper.count += 1\n", " # Call the function being decorated and return the result\n", " return func()\n", " wrapper.count = 0\n", " # Return the new decorated function\n", " return wrapper\n", "\n", "# Decorate foo() with the counter() decorator\n", "@counter\n", "def foo():\n", " print('calling foo()')\n", " \n", "foo()\n", "foo()\n", "\n", "print('foo() was called {} times.'.format(foo.count))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Decorators and metadata" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Preserving docstrings when decorating functions" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "hello\n", "30\n", "Print 'hello' and then call the decorated function.\n" ] } ], "source": [ "def add_hello(func):\n", " def wrapper(*args, **kwargs):\n", " \"\"\"Print 'hello' and then call the decorated function.\"\"\"\n", " print('hello')\n", " return func(*args, **kwargs)\n", " return wrapper\n", "\n", "# Decorate print_sum() with the add_hello() decorator\n", "@add_hello\n", "def print_sum(a, b):\n", " \"\"\"Adds two numbers and prints the sum\"\"\"\n", " print(a + b)\n", " \n", "print_sum(10, 20)\n", "print(print_sum.__doc__)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "hello\n", "30\n", "Adds two numbers and prints the sum\n" ] } ], "source": [ "# Import the function to read docstring of decorator, not wrapper\n", "from functools import wraps\n", "\n", "def add_hello(func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " \"\"\"Print 'hello' and then call the decorated function.\"\"\"\n", " print('hello')\n", " return func(*args, **kwargs)\n", " return wrapper\n", "\n", "# Decorate print_sum() with the add_hello() decorator\n", "@add_hello\n", "def print_sum(a, b):\n", " \"\"\"Adds two numbers and prints the sum\"\"\"\n", " print(a + b)\n", " \n", "print_sum(10, 20)\n", "print(print_sum.__doc__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Measure decorator overhead" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "def check_inputs(a, *args, **kwargs):\n", " for value in a:\n", " time.sleep(0.01)\n", " print('Finished checking inputs')\n", "\n", "def check_outputs(a, *args, **kwargs):\n", " for value in a:\n", " time.sleep(0.01)\n", " print('Finished checking outputs')" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "def check_everything(func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " check_inputs(*args, **kwargs)\n", " result = func(*args, **kwargs)\n", " check_outputs(result)\n", " return result\n", " return wrapper" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Finished checking inputs\n", "Finished checking outputs\n", "Decorated time: 1.51825s\n", "Undecorated time: 0.00025s\n" ] } ], "source": [ "import time\n", "\n", "@check_everything\n", "def duplicate(my_list):\n", " \"\"\"Return a new list that repeats the input twice\"\"\"\n", " return my_list + my_list\n", "\n", "t_start = time.time()\n", "duplicated_list = duplicate(list(range(50)))\n", "t_end = time.time()\n", "decorated_time = t_end - t_start\n", "\n", "t_start = time.time()\n", "# Call the original function instead of the decorated one\n", "duplicated_list = duplicate.__wrapped__(list(range(50)))\n", "t_end = time.time()\n", "undecorated_time = t_end - t_start\n", "\n", "print('Decorated time: {:.5f}s'.format(decorated_time))\n", "print('Undecorated time: {:.5f}s'.format(undecorated_time))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Decorators that takes arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "15\n", "15\n", "15\n" ] } ], "source": [ "def run_n_times(n):\n", " \"\"\"Define and return a decorator\"\"\"\n", " def decorator(func):\n", " def wrapper(*args, **kwargs):\n", " for i in range(n):\n", " func(*args, **kwargs)\n", " return wrapper\n", " return decorator\n", " \n", "@run_n_times(3)\n", "def print_sum(a, b):\n", " print(a + b)\n", "\n", "print_sum(10, 5)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "50\n", "50\n", "50\n" ] } ], "source": [ "run_three_times = run_n_times(3)\n", "\n", "@run_three_times\n", "def print_sum(a, b):\n", " print(a + b)\n", " \n", "print_sum(20, 30)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### run_n_times()" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "def run_n_times(n):\n", " \"\"\"Define and return a decorator\"\"\"\n", " def decorator(func):\n", " def wrapper(*args, **kwargs):\n", " for i in range(n):\n", " func(*args, **kwargs)\n", " return wrapper\n", " return decorator" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "35\n", "35\n", "35\n", "35\n", "35\n", "35\n", "35\n", "35\n", "35\n", "35\n" ] } ], "source": [ "# Make print_sum() run 10 times with the run_n_times() decorator\n", "@run_n_times(10)\n", "def print_sum(a, b):\n", " print(a + b)\n", " \n", "print_sum(15, 20)" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "104\n", "104\n", "104\n", "104\n", "104\n" ] } ], "source": [ "# Use run_n_times() to create the run_five_times() decorator\n", "run_five_times = run_n_times(5)\n", "\n", "@run_five_times\n", "def print_sum(a, b):\n", " print(a + b)\n", " \n", "print_sum(4, 100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### HTML Generator" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "def bold(func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " msg = func(*args, **kwargs)\n", " return '{}'.format(msg)\n", " return wrapper\n", "\n", "def italics(func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " msg = func(*args, **kwargs)\n", " return '{}'.format(msg)\n", " return wrapper" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "def html(open_tag, close_tag):\n", " def decorator(func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " msg = func(*args, **kwargs)\n", " return '{}{}{}'.format(open_tag, msg, close_tag)\n", " # Return the decorated function\n", " return wrapper\n", " # Return the decorator\n", " return decorator" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello Alice!\n" ] } ], "source": [ "# Make hello() return bolded text\n", "@html('', '')\n", "def hello(name):\n", " return 'Hello {}!'.format(name)\n", "\n", "print(hello('Alice'))" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Goodbye Alice.\n" ] } ], "source": [ "# Make goodbye() return italicized text\n", "@html('', '')\n", "def goodbye(name):\n", " return 'Goodbye {}.'.format(name)\n", "\n", "print(goodbye('Alice'))" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "
\n", "Hello Alice!\n", "Goodbye Alice.\n", "
\n" ] } ], "source": [ "# Wrap the result of hello_goodbye() in
and
\n", "@html('
', '
')\n", "def hello_goodbye(name):\n", " return '\\n{}\\n{}\\n'.format(hello(name), goodbye(name))\n", "\n", "print(hello_goodbye('Alice'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## timeout(): a real world example" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import signal\n", "def raise_timeout(*args, **kwargs):\n", " raise TimeoutError()\n", " \n", "# When an 'alarm' signal goes off, call raise_timeout()\n", "signal.signal(signalnum=signal.SIGALRM, handler=raise_timeout)\n", "\n", "# Set off an alarm in 5 seconds\n", "signal.alarm(5)\n", "\n", "# Cancel the alarm\n", "signal.alarm(0)" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "def timeout_in_5s(func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " # Set an alarm for 5 seconds\n", " signal.alarm(5)\n", " try:\n", " # Call the decorated func\n", " return func(*args, **kwargs)\n", " finally:\n", " # Cancel alarm\n", " signal.alarm(0)\n", " return wrapper" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "@timeout_in_5s\n", "def foo():\n", " time.sleep(10)\n", " print('foo!')" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "ename": "TimeoutError", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTimeoutError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m\u001b[0m in \u001b[0;36mwrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0;31m# Call the decorated func\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 9\u001b[0m \u001b[0;32mfinally\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;31m# Cancel alarm\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36mfoo\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mtimeout_in_5s\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'foo!'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36mraise_timeout\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0msignal\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mraise_timeout\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTimeoutError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;31m# When an 'alarm' signal goes off, call raise_timeout()\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTimeoutError\u001b[0m: " ] } ], "source": [ "foo()" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [], "source": [ "def timeout(n_seconds):\n", " def decorator(func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " # Set an alarm for n seconds\n", " signal.alarm(n_seconds)\n", " try:\n", " return func(*args, **kwargs)\n", " finally:\n", " signal.alarm(0)\n", " return wrapper\n", " return decorator" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "@timeout(20)\n", "def bar():\n", " time.sleep(10)\n", " print('bar!')" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "bar!\n" ] } ], "source": [ "bar()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Tag your functions" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('test', 'this is a tag')\n" ] } ], "source": [ "def tag(*tags):\n", " # Define a new decorator, named \"decorator\", to return\n", " def decorator(func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " # Call the function being decorated and return the result\n", " return func(*args, **kwargs)\n", " wrapper.tags = tags\n", " return wrapper\n", " # Return the new decorator\n", " return decorator\n", "\n", "@tag('test', 'this is a tag')\n", "def foo():\n", " pass\n", "\n", "print(foo.tags)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Check the return type" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foo() did not return a dict\n" ] } ], "source": [ "def returns_dict(func):\n", " def wrapper(*args, **kwargs):\n", " result = func(*args, **kwargs)\n", " assert(type(result) == dict)\n", " return result\n", " return wrapper\n", "\n", "@returns_dict\n", "def foo(value):\n", " return value\n", "\n", "try:\n", " print(foo([1, 2, 3]))\n", "except AssertionError:\n", " print('foo() did not return a dict')" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foo() did not return a dict!\n" ] } ], "source": [ "def returns(return_type):\n", " # Complete the returns() decorator\n", " def decorator(func):\n", " def wrapper(*args, **kwargs):\n", " result = func(*args, **kwargs)\n", " assert(type(result) == return_type)\n", " return result\n", " return wrapper\n", " return decorator\n", " \n", "@returns(dict)\n", "def foo(value):\n", " return value\n", "\n", "try:\n", " print(foo([1,2,3]))\n", "except AssertionError:\n", " print('foo() did not return a dict!')" ] } ], "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.7.6" } }, "nbformat": 4, "nbformat_minor": 4 }