1009 lines
26 KiB
Plaintext
1009 lines
26 KiB
Plaintext
{
|
|
"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": 61,
|
|
"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": 62,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"<__main__.StaticArray object at 0x1079a50f0>\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"sa = StaticArray([1, 2, 3])\n",
|
|
"print(sa)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 63,
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"<__main__.StaticArray object at 0x107b550f0>\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"sa = StaticArray(0, 5)\n",
|
|
"print(sa)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 64,
|
|
"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": 65,
|
|
"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": 67,
|
|
"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": 68,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"4"
|
|
]
|
|
},
|
|
"execution_count": 68,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"sa = StaticArray([1, \"hi\", 3.14, True])\n",
|
|
"len(sa)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 70,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"True"
|
|
]
|
|
},
|
|
"execution_count": 70,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"42 in sa\n",
|
|
"\"hi\" in sa"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 72,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"True"
|
|
]
|
|
},
|
|
"execution_count": 72,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"sa[3]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 73,
|
|
"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[73], 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[67], 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": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"for i in iterable:\n",
|
|
" print(i)\n",
|
|
"\n",
|
|
"iterator = iter(iterable)\n",
|
|
"while True:\n",
|
|
" print(next(iterator))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 56,
|
|
"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",
|
|
" print(f\"next was called, moving {self.current} to {self.current+1}\")\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": 59,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"0 0\n",
|
|
"0 1\n",
|
|
"0 2\n",
|
|
"1 0\n",
|
|
"1 1\n",
|
|
"1 2\n",
|
|
"2 0\n",
|
|
"2 1\n",
|
|
"2 2\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"sr = range(3)\n",
|
|
"for i in sr:\n",
|
|
" for j in sr:\n",
|
|
" print(i, j)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 57,
|
|
"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",
|
|
"eclass 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
|
|
}
|