51042-notes/08.decorators.ipynb
2024-10-30 21:39:04 -05:00

637 lines
15 KiB
Plaintext

{
"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 <function inner at 0x103591240>\n",
"inner function 1 2 3\n",
"AFTER <function inner at 0x103591240>\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 <function inner at 0x103591240>\n",
"inner function 1 2 3\n",
"AFTER <function inner at 0x103591240>\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 <function print_before_and_after.<locals>.newfunc at 0x103591ea0>\n",
"BEFORE <function add_nums at 0x1035923b0>\n",
"1 + 2 + 3 = 6\n",
"AFTER <function add_nums at 0x1035923b0>\n",
"AFTER <function print_before_and_after.<locals>.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": [
"<function print>"
]
},
"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": [
"<function print>"
]
},
"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
}