diff --git a/13.python-data-model.ipynb b/13.python-data-model.ipynb new file mode 100644 index 0000000..23a9b8e --- /dev/null +++ b/13.python-data-model.ipynb @@ -0,0 +1,1001 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Python Data Model\n", + "\n", + "## Python Data Model\n", + "\n", + "Reference: https://docs.python.org/3/reference/datamodel.html\n", + "\n", + "### StaticArray Example\n", + "\n", + "To demonstrate operator overloading, we'll implement a sequence type seen in other languages known as a *static array*:\n", + "\n", + "- A static array is a sequence type (review: what makes a sequence type) where there is a fixed capacity to number of items the collection can hold.\n", + "\n", + "- Resizing of the array is not allowed after initialization. \n", + "\n", + "- We will define a class ``StaticArray`` that will allow use to use built-in python operators.\n", + "\n", + "We'll be able to use it like this:\n", + "\n", + "```python\n", + ">>> from static_array import StaticArray\n", + ">>> sa = StaticArray([1, 2, 3])\n", + ">>> print(sa * 2)\n", + "# should produce the following output:\n", + "# [1, 2, 3, 1, 2, 3]\n", + ">>> print(sa[1])\n", + "2\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from collections.abc import Iterable\n", + "\n", + "\n", + "class StaticArray:\n", + " def __init__(self, init_val, capacity=5):\n", + " if isinstance(init_val, Iterable):\n", + " self.items = list(init_val)\n", + " self.capacity = len(self.items)\n", + " else:\n", + " self.items = [init_val] * capacity\n", + " self.capacity = capacity\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<__main__.StaticArray object at 0x107899060>\n" + ] + } + ], + "source": [ + "sa = StaticArray([1, 2, 3])\n", + "print(sa)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<__main__.StaticArray object at 0x107899b70>\n" + ] + } + ], + "source": [ + "sa = StaticArray(0, 5)\n", + "print(sa)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "StaticArray([1, 2, 3])\n" + ] + } + ], + "source": [ + "# we can fix that by defining a __repr__ method\n", + "\n", + "\n", + "class StaticArray:\n", + " def __init__(self, init_val, capacity=5):\n", + " if isinstance(init_val, Iterable):\n", + " self.items = list(init_val)\n", + " self.capacity = len(self.items)\n", + " else:\n", + " self.items = [init_val] * capacity\n", + " self.capacity = capacity\n", + "\n", + " def __repr__(self):\n", + " return f\"StaticArray({self.items})\"\n", + "\n", + "\n", + "sa = StaticArray([1, 2, 3])\n", + "print(sa)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "StaticArray([0, 0, 0, 0, 0])\n" + ] + } + ], + "source": [ + "sa = StaticArray(0, 5)\n", + "print(sa)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Emulating Collections & Sequences\n", + "\n", + "**Collections**\n", + "\n", + "- Have a length: `len(obj)`\n", + "- Can be iterated over: `for item in obj`\n", + "- Can query for membership: `item in obj`\n", + "\n", + "**Sequences**\n", + "\n", + "- Everything a collection can do\n", + "- Can be indexed: `obj[0]`\n", + "\n", + "| You Write... | Python Calls... |\n", + "|--------------|-----------------|\n", + "| `len(obj)` | `obj.__len__()` |\n", + "| `for item in obj` | `obj.__iter__()` |\n", + "| `item in obj` | `obj.__contains__(item)` |\n", + "| `obj[i]` | `obj.__getitem__(i)` |\n", + "| `obj[i] = x` | `obj.__setitem__(i, x)` |\n", + "| `del obj[i]` | `obj.__delitem__(i)` |\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "class StaticArray:\n", + " def __init__(self, init_val, capacity=5):\n", + " if isinstance(init_val, Iterable):\n", + " self.items = list(init_val)\n", + " self.capacity = len(self.items)\n", + " else:\n", + " self.items = [init_val] * capacity\n", + " self.capacity = capacity\n", + "\n", + " def __repr__(self):\n", + " return f\"StaticArray({self.items})\"\n", + "\n", + " def __str__(self):\n", + " return f\"StaticArray({self.items})\"\n", + "\n", + " def __len__(self):\n", + " return self.capacity\n", + "\n", + " def __contains__(self, item):\n", + " return item in self.items\n", + "\n", + " def __getitem__(self, index):\n", + " if index >= self.capacity or index < -self.capacity:\n", + " raise IndexError(\"Index out of range\")\n", + " return self.items[index]\n", + "\n", + " def __setitem__(self, index, val):\n", + " if index >= self.capacity or index < -self.capacity:\n", + " raise IndexError(\"Index out of range\")\n", + " self.items[index] = val\n", + "\n", + " def __delitem__(self, index):\n", + " raise NotImplementedError(\"StaticArray does not support deletion\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sa = StaticArray([1, \"hi\", 3.14, True])\n", + "len(sa)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "42 in sa\n", + "\"hi\" in sa" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sa[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "ename": "IndexError", + "evalue": "Index out of range", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[10], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43msa\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m42\u001b[39;49m\u001b[43m]\u001b[49m \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhello\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", + "Cell \u001b[0;32mIn[6], line 29\u001b[0m, in \u001b[0;36mStaticArray.__setitem__\u001b[0;34m(self, index, val)\u001b[0m\n\u001b[1;32m 27\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__setitem__\u001b[39m(\u001b[38;5;28mself\u001b[39m, index, val):\n\u001b[1;32m 28\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m index \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcapacity \u001b[38;5;129;01mor\u001b[39;00m index \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m-\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcapacity:\n\u001b[0;32m---> 29\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mIndexError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mIndex out of range\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 30\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mitems[index] \u001b[38;5;241m=\u001b[39m val\n", + "\u001b[0;31mIndexError\u001b[0m: Index out of range" + ] + } + ], + "source": [ + "sa[42] = \"hello\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Numeric Operators\n", + "\n", + "| You Write... | Python Calls... |\n", + "|--------------|-----------------|\n", + "| `x + y` | `x.__add__(y)` |\n", + "| `x - y` | `x.__sub__(y)` |\n", + "| `x * y` | `x.__mul__(y)` |\n", + "| `x / y` | `x.__truediv__(y)` |\n", + "| `x // y` | `x.__floordiv__(y)` |\n", + "| `x % y` | `x.__mod__(y)` |\n", + "| `x ** y` | `x.__pow__(y)` |\n", + "| `x @ y` | `x.__matmul__(y)` |\n", + "\n", + "### Reverse / Reflected / Right Operators\n", + "\n", + "| You Write... | Python Calls... |\n", + "|--------------|-----------------|\n", + "| `x + y` | `y.__radd__(x)` |\n", + "| `x - y` | `y.__rsub__(x)` |\n", + "| `x * y` | `y.__rmul__(x)` |\n", + "| `x / y` | `y.__rtruediv__(x)` |\n", + "| `x // y` | `y.__rfloordiv__(x)` |\n", + "| `x % y` | `y.__rmod__(x)` |\n", + "| `x ** y` | `y.__rpow__(x)` |\n", + "| `x @ y` | `y.__rmatmul__(x)` |\n", + "\n", + "![](images/reverse_operators.png)\n", + "\n", + "### Comparison Operators\n", + "\n", + "| You Write... | Python Calls... |\n", + "|--------------|-----------------| \n", + "| `x < y` | `x.__lt__(y)` |\n", + "| `x <= y` | `x.__le__(y)` |\n", + "| `x > y` | `x.__gt__(y)` |\n", + "| `x >= y` | `x.__ge__(y)` |\n", + "| `x == y` | `x.__eq__(y)` |\n", + "| `x != y` | `x.__ne__(y)` |\n", + "\n", + "\n", + "### Iteration\n", + "\n", + "Iterable vs. Iterator (Review)\n", + "\n", + "Objects like lists, tuples, and strings are *iterable*.\n", + "\n", + "To keep track of the position within a given iteration (for loop, calls to `next`), Python uses an *iterator*." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iterator 1 next() 1\n", + "iterator 1 next() 2\n", + "iterator 2 next() 1\n", + "iterator 1 next() 3\n" + ] + } + ], + "source": [ + "ll = [1, 2, 3, 4]\n", + "iterator = iter(ll)\n", + "print(\"iterator 1 next()\", next(iterator))\n", + "print(\"iterator 1 next()\", next(iterator))\n", + "iterator2 = iter(ll)\n", + "print(\"iterator 2 next()\", next(iterator2))\n", + "print(\"iterator 1 next()\", next(iterator))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To be iterable, a class needs an `__iter__` method that returns an iterator.\n", + "\n", + "An iterator is an object with a `__next__` method that returns the next item in the iteration. It should raise `StopIteration` when there are no more items.\n", + "\n", + "Common Pattern: If a class only needs to be iterable once, it can return itself as the iterator, thus fulfilling both roles." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "class SimpleRange:\n", + " def __init__(self, n):\n", + " self.current = 0\n", + " self.n = n\n", + "\n", + " def __iter__(self):\n", + " return self\n", + "\n", + " def __next__(self):\n", + " if self.current >= self.n:\n", + " raise StopIteration\n", + " else:\n", + " self.current += 1\n", + " return self.current - 1\n", + "\n", + " def __repr__(self):\n", + " return f\"SimpleRange({self.n}, current={self.current})\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "2\n", + "3\n", + "4\n" + ] + }, + { + "ename": "StopIteration", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[13], line 4\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i \u001b[38;5;129;01min\u001b[39;00m sr:\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28mprint\u001b[39m(i)\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;28;43mnext\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43msr\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[12], line 11\u001b[0m, in \u001b[0;36mSimpleRange.__next__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__next__\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcurrent \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mn:\n\u001b[0;32m---> 11\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m\n\u001b[1;32m 12\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 13\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcurrent \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n", + "\u001b[0;31mStopIteration\u001b[0m: " + ] + } + ], + "source": [ + "sr = SimpleRange(5)\n", + "for i in sr:\n", + " print(i)\n", + "next(sr)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SimpleRange(5, current=0)\n" + ] + } + ], + "source": [ + "sr = SimpleRange(5)\n", + "siter = iter(sr)\n", + "print(siter)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "siter is sr" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SimpleRange(5, current=1)\n" + ] + } + ], + "source": [ + "next(siter)\n", + "print(siter)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Iteration Advice \n", + "\n", + "1. Do not implement the ``__next__()`` in a class that should only be an iterable. \n", + "2. In order to support multiple traversals, the iterator must be a seperate object. \n", + "3. A common design pattern is to delegate iteration to a seperate class that is iterable.\n", + "\n", + "For example, defining an ``StaticArrayIterator`` class that is in charge iterating through the objects within an ``StaticArray`` object. " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "# Adding __iter__ to StaticArray\n", + "class StaticArrayIterator:\n", + " def __init__(self, values):\n", + " self.values = values\n", + " self.position = 0\n", + "\n", + " def __next__(self):\n", + " if self.position >= len(self.values):\n", + " raise StopIteration\n", + " item = self.values[self.position]\n", + " self.position += 1\n", + " return item\n", + "\n", + " def __repr__(self):\n", + " return f\"iterating over {self.values}, at position {self.position}\"\n", + "\n", + "\n", + "# Adding __iter__\n", + "\n", + "\n", + "class StaticArray:\n", + " def __init__(self, capacity, initial=None):\n", + " self._items = [initial] * capacity\n", + " self._capacity = capacity\n", + " self._iter_position = 0\n", + "\n", + " @classmethod\n", + " def from_iterable(self, iterable):\n", + " new_array = StaticArray(len(iterable))\n", + " for idx, item in enumerate(iterable):\n", + " new_array._items[idx] = item\n", + " return new_array\n", + "\n", + " def __repr__(self):\n", + " # __repr__ is the unambiguous string representation\n", + " # of an object\n", + " return f\"StaticArray({self._capacity}, {self._items})\"\n", + "\n", + " def __str__(self):\n", + " return repr(self._items)\n", + "\n", + " # Sequence Operations ####\n", + "\n", + " def __len__(self):\n", + " return self._capacity\n", + "\n", + " def __contains__(self, x):\n", + " return x in self._items\n", + "\n", + " def __getitem__(self, i):\n", + " if i >= self._capacity or i < -self._capacity:\n", + " raise IndexError # an invalid index\n", + " return self._items[i]\n", + "\n", + " def __setitem__(self, i, x):\n", + " if i >= self._capacity or i < -self._capacity:\n", + " raise IndexError # an invalid index\n", + " self._items[i] = x\n", + "\n", + " def __delitem__(self, i):\n", + " raise NotImplementedError(\"Cannot delete from a static array\")\n", + "\n", + " # Iterable Operations ####\n", + " def __iter__(self):\n", + " return StaticArrayIterator(self._items.copy())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Context Managers / `with`\n", + "\n", + "We also saw this idea of needing to clean up after ourselves when we used `with` to open files.\n", + "\n", + "```python\n", + "\n", + "with open(filename) as f:\n", + " # do things with f\n", + " g(f)\n", + "# f is guaranteed to be closed even if \n", + "# exceptions are raised within with block\n", + "```\n", + "\n", + "### Yet another set of dunder methods..." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "class DatabaseConnection:\n", + " def __init__(self, username, password):\n", + " # connect to database\n", + " self.username = username\n", + " self.password = password\n", + " self.connected = True\n", + "\n", + " def __enter__(self):\n", + " print(\"__enter__\")\n", + " # must return self!\n", + " return self\n", + "\n", + " def __exit__(self, exc_type, exc_val, exc_traceback):\n", + " print(\"__exit__\")\n", + " if exc_type:\n", + " print(\"rolling back changes\")\n", + " self.connected = False\n", + "\n", + " def query(self, sql):\n", + " print(\"ran query\", sql)\n", + "\n", + " def __repr__(self):\n", + " return f\"Connection connected={self.connected}\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ran query SELECT * FROM users;\n" + ] + }, + { + "ename": "ZeroDivisionError", + "evalue": "division by zero", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[19], line 4\u001b[0m\n\u001b[1;32m 2\u001b[0m db\u001b[38;5;241m.\u001b[39mquery(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mSELECT * FROM users;\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 3\u001b[0m \u001b[38;5;66;03m# do something dangerous\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;241;43m1\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\n", + "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero" + ] + } + ], + "source": [ + "db = DatabaseConnection(\"hello\", \"world\")\n", + "db.query(\"SELECT * FROM users;\")\n", + "# do something dangerous\n", + "1 / 0" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connection connected=True\n" + ] + } + ], + "source": [ + "# our connection is possibly left in a broken state\n", + "print(db)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "__enter__\n", + "ran query SELECT * from users;\n", + "__exit__\n", + "rolling back changes\n" + ] + }, + { + "ename": "ZeroDivisionError", + "evalue": "division by zero", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[21], line 4\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m DatabaseConnection(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhello\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mworld\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m db:\n\u001b[1;32m 2\u001b[0m \u001b[38;5;66;03m# __enter__\u001b[39;00m\n\u001b[1;32m 3\u001b[0m db\u001b[38;5;241m.\u001b[39mquery(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mSELECT * from users;\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;241;43m1\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;66;03m# __exit__\u001b[39;00m\n", + "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero" + ] + } + ], + "source": [ + "with DatabaseConnection(\"hello\", \"world\") as db:\n", + " # __enter__\n", + " db.query(\"SELECT * from users;\")\n", + " 1 / 0\n", + " # __exit__" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# changes were rolled back, and our connection is safe\n", + "db.connected\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Callable Objects Examples\n", + "\n", + "Functions have a few attributes like `__name__` and `__doc__` that we can use to introspect on them." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "add\n", + "Adds two numbers\n" + ] + } + ], + "source": [ + "def add(x, y):\n", + " \"\"\"Adds two numbers\"\"\"\n", + " return x + y\n", + "\n", + "\n", + "print(add.__name__)\n", + "print(add.__doc__)\n", + "\n", + "x = add" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'add'" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.__name__" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "class Example:\n", + " def __init__(self, name):\n", + " self.name = name\n", + " def __call__(self, *args):\n", + " print(self.name, \"got\", args)\n", + "\n", + "example = Example(\"one\")" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "one got ()\n" + ] + } + ], + "source": [ + "example()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "They also have a `__call__` method that allows us to make our own objects callable. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "class Memoized:\n", + " def __init__(self, func):\n", + " self.cache = {}\n", + " self.wrapped_func = func\n", + "\n", + " def __call__(self, *args):\n", + " if args not in self.cache:\n", + " self.cache[args] = self.wrapped_func(*args)\n", + " return self.cache[args]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "running expensive_func\n", + "6\n", + "6\n" + ] + } + ], + "source": [ + "@Memoized\n", + "def expensive_func(a, b, c):\n", + " print(\"running expensive_func\")\n", + " return a + b + c\n", + "\n", + "#expensive_func = Memoized(expensive_func)\n", + "\n", + "\n", + "print(expensive_func(1, 2, 3))\n", + "print(expensive_func(1, 2, 3))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "class PartialFunc:\n", + " # simplified functools.partial\n", + "\n", + " def __init__(self, func, *args, **kwargs):\n", + " self.func = func\n", + " self.args = args\n", + " self.kwargs = kwargs\n", + "\n", + " def __call__(self, *args, **kwargs):\n", + " temp_kwargs = self.kwargs.copy()\n", + " temp_kwargs.update(kwargs)\n", + " return self.func(*(self.args + args), **temp_kwargs)\n", + "\n", + " @property\n", + " def __name__(self):\n", + " return f\"{self.func.__name__}(args={self.args} kwargs={self.kwargs})\"\n", + "\n", + " @property\n", + " def __doc__(self):\n", + " return self.func.__doc__\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "15\n", + "add(args=(5,) kwargs={})\n", + "Adds two numbers\n" + ] + } + ], + "source": [ + "def add(x, y):\n", + " \"\"\"Adds two numbers\"\"\"\n", + " return x + y\n", + "\n", + "add_5 = PartialFunc(add, 5)\n", + "print(add_5(10))\n", + "\n", + "print(add_5.__name__)\n", + "print(add_5.__doc__)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "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": 4 +}