{ "cells": [ { "cell_type": "markdown", "id": "3e9aa8bc-bdc1-4f86-a44b-c68b8072c4ef", "metadata": {}, "source": [ "## Decorators\n", "\n", "A common pattern in functional programs, are functions that are built to \"wrap\" other functions.\n", "\n", "Functions are in fact mutable." ] }, { "cell_type": "code", "execution_count": 5, "id": "d8cecafd-c460-446b-8f8f-7a35a9f8c6e2", "metadata": {}, "outputs": [], "source": [ "def print_before_and_after(func):\n", " def newfunc(*args, **kwargs):\n", " print(\"BEFORE\", func)\n", " func(*args, **kwargs)\n", " print(\"AFTER\", func)\n", " return newfunc" ] }, { "cell_type": "code", "execution_count": null, "id": "b3dd55e9-33cb-41c9-840a-7fd2f137f2f0", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 8, "id": "f327b88e-d034-4bd3-9e63-9196ccbb3d84", "metadata": {}, "outputs": [], "source": [ "def inner(a, b, c):\n", " print(\"inner function\", a, b, c)" ] }, { "cell_type": "code", "execution_count": 10, "id": "359d03fc-1d69-4574-a06c-45b55905cecb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "BEFORE \n", "inner function 1 2 3\n", "AFTER \n" ] } ], "source": [ "wrapped_inner = print_before_and_after(inner)\n", "\n", "wrapped_inner(1, 2, 3)\n", "#print(x)" ] }, { "cell_type": "code", "execution_count": 11, "id": "916c438d-f0f3-4b78-9846-f9bf2aab7dcc", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "BEFORE \n", "inner function 1 2 3\n", "AFTER \n" ] } ], "source": [ "# often we want to just replace the function altogether\n", "# with the modified version\n", "inner = print_before_and_after(inner)\n", "inner(1, 2, 3)" ] }, { "cell_type": "code", "execution_count": 14, "id": "508bbf73-1f9f-41dd-9665-549472056211", "metadata": {}, "outputs": [], "source": [ "# another way of writing this is to use decorator syntax\n", "@print_before_and_after\n", "@print_before_and_after\n", "def add_nums(a, b, c):\n", " print(f\"{a} + {b} + {c} =\", a + b + c)\n", "# add_nums = print_before_and_after(add_nums)" ] }, { "cell_type": "code", "execution_count": 15, "id": "86ee63a9-ac5d-44f5-a06a-a89527358141", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "BEFORE .newfunc at 0x103591ea0>\n", "BEFORE \n", "1 + 2 + 3 = 6\n", "AFTER \n", "AFTER .newfunc at 0x103591ea0>\n" ] } ], "source": [ "add_nums(1, 2, 3)" ] }, { "cell_type": "code", "execution_count": 1, "id": "1e9d6428-8f80-4707-b508-c15878459d1f", "metadata": {}, "outputs": [], "source": [ "def cache(func):\n", " inner_cache = {}\n", " \n", " def newfunc(*args, **kwargs):\n", " if args not in inner_cache:\n", " # will not work correctly if there are kwargs\n", " inner_cache[args] = func(*args, **kwargs)\n", " return inner_cache[args]\n", " \n", " return newfunc" ] }, { "cell_type": "code", "execution_count": null, "id": "c50faf15-f5dc-4a1b-a114-c606a0edd0be", "metadata": {}, "outputs": [], "source": [ "@cache\n", "def expensive_calculation(a, b, *, c=0):\n", " print(f\"doing expensive calculation on {a} {b}...\")\n", " return a ** b\n", "#expensive_calculation = cache(expensive_calculation)\n", "\n", "@cache\n", "def cheap_calculation(a, b):\n", " print(f\"doing cheap calculation on {a} {b}...\")\n", " return a + b" ] }, { "cell_type": "code", "execution_count": null, "id": "dadaf583-82fd-4273-870e-7d68ec67c537", "metadata": {}, "outputs": [], "source": [ "expensive_calculation(4, 10)" ] }, { "cell_type": "code", "execution_count": null, "id": "6caa9b63-bc83-44c7-b416-1b5f9c708469", "metadata": { "tags": [] }, "outputs": [], "source": [ "expensive_calculation(4, 10)" ] }, { "cell_type": "code", "execution_count": null, "id": "c6e9a399-9c52-4b38-8fe0-427e0843804a", "metadata": {}, "outputs": [], "source": [ "cheap_calculation(4, 10)" ] }, { "cell_type": "code", "execution_count": null, "id": "8ed9065a-4fb1-4486-ab02-b858d031eb93", "metadata": {}, "outputs": [], "source": [ "expensive_calculation(5, 6)" ] }, { "cell_type": "code", "execution_count": null, "id": "975f7f9e-74c4-4dcf-b396-ad85556e930a", "metadata": {}, "outputs": [], "source": [ "cheap_calculation(4, 10)" ] }, { "cell_type": "code", "execution_count": null, "id": "d3d8c310-4fa6-4b5e-b6e7-e0512adfe8c0", "metadata": {}, "outputs": [], "source": [ "expensive_calculation(5, 6)" ] }, { "cell_type": "code", "execution_count": 31, "id": "b6da10be-562c-4968-86ed-c473db63eccf", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "repeatN() missing 1 required positional argument: 'n'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[31], line 8\u001b[0m\n\u001b[1;32m 4\u001b[0m func(a, b, c)\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m newfunc\n\u001b[1;32m 7\u001b[0m \u001b[38;5;129;43m@repeatN\u001b[39;49m\n\u001b[0;32m----> 8\u001b[0m \u001b[38;5;28;43;01mdef\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;21;43mprint_sum\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 9\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mprint\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;28;43msum\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[0;31mTypeError\u001b[0m: repeatN() missing 1 required positional argument: 'n'" ] } ], "source": [ "def repeat5(func): # the decorator \n", " def newfunc(a, b, c): # the inner function\n", " return func(a, b, c, c, b, a)\n", " return newfunc\n", "\n", "@repeatN\n", "def print_sum(*args):\n", " print(sum(args))\n" ] }, { "cell_type": "code", "execution_count": 29, "id": "7f43e771-92d9-48d3-96ed-b95b1c94e942", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n", "6\n", "6\n", "6\n", "6\n" ] } ], "source": [ "print_sum(1, 2, 3)" ] }, { "cell_type": "code", "execution_count": 10, "id": "4327b95d-b9ab-49e3-a530-b34e0688cff6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sdrawkcab\n", "sdrawkcab\n", "sdrawkcab\n", "sdrawkcab\n", "sdrawkcab\n", "sdrawkcab\n", "sdrawkcab\n", "sdrawkcab\n", "sdrawkcab\n", "sdrawkcab\n" ] } ], "source": [ "# to make a decorator that takes additional arguments\n", "# you need to write a decorator factory function that returns decorators\n", "\n", "total = 123\n", "\n", "def repeat(start, end): # factory: takes integer, returns decorator\n", " def repeat_decorator(func): # decorator: takes function, returns function\n", " def newfunc(*args, **kwargs): # inner function: takes ?, returns ?\n", " for i in range(total):\n", " func(*args, **kwargs)\n", " return newfunc\n", " return repeat_decorator\n", "\n", "@repeat(10)\n", "def print_backwards(s):\n", " print(s[::-1])\n", "\n", "print_backwards(\"backwards\")" ] }, { "cell_type": "code", "execution_count": null, "id": "8b4219ac-7326-47e1-b3ad-68941349db1d", "metadata": {}, "outputs": [], "source": [ "repeat_10 = repeat(10)\n", "print(repeat_10)\n", "print_backwards = repeat_10(print_backwards)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "a1d27a5c-ce00-4023-8efd-1900b6f876e5", "metadata": {}, "outputs": [], "source": [ "# Example: writing our own `partial`\n", "# https://docs.python.org/3/library/functools.html#functools.partial" ] }, { "cell_type": "code", "execution_count": 32, "id": "fb9e74b4-a0a2-4463-add7-45b04430edcd", "metadata": {}, "outputs": [], "source": [ "import functools\n", "print_hello_names = functools.partial(print, \"Hello\", sep=\", \")" ] }, { "cell_type": "code", "execution_count": 33, "id": "e64ec996-4085-4c03-a771-6c1073b04990", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, Scott, Paul, Lauren\n" ] } ], "source": [ "print_hello_names(\"Scott\", \"Paul\", \"Lauren\")\n", "# same as print(\"Hello\", \"Scott\", \"Paul\", \"Lauren\", sep=\", \")" ] }, { "cell_type": "code", "execution_count": 17, "id": "eb0d1ad3-e636-4430-9139-265740adc8a1", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('Hello',)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print_hello_names.args" ] }, { "cell_type": "code", "execution_count": 18, "id": "9e67124b-9014-49d9-af86-54b8a617521f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'sep': ', '}" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print_hello_names.keywords" ] }, { "cell_type": "code", "execution_count": 19, "id": "f4939684-6c92-4c07-8d88-f7c4349d0f02", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print_hello_names.func" ] }, { "cell_type": "code", "execution_count": 36, "id": "1ed27205-7691-46ee-bdc6-f75dae7d53da", "metadata": {}, "outputs": [], "source": [ "# since functions are objects, we can attach arbitrary values to them\n", "def wrapper(func):\n", " def newfunc(*args, **kwargs):\n", " return func(*args, **kwargs)\n", " # we can do whatever we like after defining newfunc, but before returning it\n", " newfunc.xyz = \"hello\"*2\n", " return newfunc" ] }, { "cell_type": "code", "execution_count": 35, "id": "8e02bb23-7278-4799-85d6-9004af65565b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# property is assigned to all wrapped functions\n", "@wrapper\n", "def our_function():\n", " print(\"inside our function\")\n", "\n", "our_function.xyz" ] }, { "cell_type": "code", "execution_count": 40, "id": "93b1ad42-b4a2-4947-a4bb-19c818968e2c", "metadata": {}, "outputs": [], "source": [ "def our_partial(func, /, *args, **keywords):\n", " def newfunc(*fargs, **fkeywords):\n", " newkeywords = {**keywords, **fkeywords}\n", " return func(*args, *fargs, **newkeywords)\n", " # assign these properties from within the closure\n", " newfunc.func = func\n", " newfunc.args = args\n", " newfunc.keywords = keywords\n", " return newfunc" ] }, { "cell_type": "code", "execution_count": 39, "id": "092b1a1d-4300-4895-aa06-4d874bc61159", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Scott, Paul, Lauren, Hello!" ] } ], "source": [ "print_hello_names2 = our_partial(print, \"Hello\", sep=\", \")\n", "print_hello_names2(\"Scott\", \"Paul\", \"Lauren\", end=\"!\")" ] }, { "cell_type": "code", "execution_count": 37, "id": "a0bd355b-63d5-41e2-8238-2e3dff22cb45", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello?Scott?Paul?Lauren!" ] } ], "source": [ "#print_hello_names2 = our_partial(print, \"Hello\", sep=\", \")\n", "print_hello_names2(\"Scott\", \"Paul\", \"Lauren\", end=\"!\", sep=\"?\")" ] }, { "cell_type": "code", "execution_count": 25, "id": "c48a5dfb-0444-4f5e-a4bc-39f0770ff5a3", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('Hello',)" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print_hello_names2.args" ] }, { "cell_type": "code", "execution_count": 26, "id": "f5d68812-918b-42dd-b23f-925a692668aa", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'sep': ', '}" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print_hello_names2.keywords" ] }, { "cell_type": "code", "execution_count": 27, "id": "6b41b480-43e1-49b2-ade7-44d8aa484a09", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print_hello_names2.func" ] }, { "cell_type": "code", "execution_count": null, "id": "ca9e90da-65e6-4e92-91ae-a97340527b38", "metadata": { "tags": [] }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.10.15" } }, "nbformat": 4, "nbformat_minor": 5 }