diff options
| author | Simeon Simeonov | 2022-10-12 15:33:50 +0200 |
|---|---|---|
| committer | Simeon Simeonov | 2022-10-12 15:33:50 +0200 |
| commit | 4290701502d378a7dcb3414d32fabe2b8e88edd0 (patch) | |
| tree | de17dea61c20b26e728e1c154112581de95b53a8 | |
| parent | 615cd66a873853f335177ec8b307c35be17d4b36 (diff) | |
Update Python OO and SQLAlchemy notebooks
| -rw-r--r-- | notebooks/python/python_oo.ipynb | 479 | ||||
| -rw-r--r-- | notebooks/sqlalchemy/sqlalchemy.ipynb | 5 |
2 files changed, 454 insertions, 30 deletions
diff --git a/notebooks/python/python_oo.ipynb b/notebooks/python/python_oo.ipynb index 0b33a09..7ce5c73 100644 --- a/notebooks/python/python_oo.ipynb +++ b/notebooks/python/python_oo.ipynb | |||
| @@ -95,6 +95,37 @@ | |||
| 95 | }, | 95 | }, |
| 96 | { | 96 | { |
| 97 | "cell_type": "markdown", | 97 | "cell_type": "markdown", |
| 98 | "id": "1b8cf468-202b-47d5-ab21-25e6e96e0f46", | ||
| 99 | "metadata": {}, | ||
| 100 | "source": [ | ||
| 101 | "# Best practices and general principles\n", | ||
| 102 | "\n", | ||
| 103 | "## SOLID\n", | ||
| 104 | "\n", | ||
| 105 | "- **S**ingle responsibility principle - a class should have only a single responsibility or a single job or a single purpose. We should strictly avoid using generalized classes where the entire implementation is given in the same class. It also states that the responsibility should be entirely encapsulated by the class, module, or function.\n", | ||
| 106 | "\n", | ||
| 107 | "- **O**pen/closed principle - entities like classes, modules, functions, etc. should be open for extension and the classes should be closed for modification. This means that we should be able to extend a class behavior, without modifying it.\n", | ||
| 108 | "\n", | ||
| 109 | "- **L**iskov’s substitution principle - derived or child classes must be substitutable for their base or parent classes. This principle ensures that any class that is the child of a parent class should be usable in place of its parent without any unexpected behavior.\n", | ||
| 110 | "\n", | ||
| 111 | "- **I**nterface segregation principle - This is the first principle that applies to an interfaces. It is similar to the single responsibility principle. It states that we should not force any client to implement an interface that is irrelevant to them. The main goal of this concept is to focus on avoiding fat interface and give preference to many small client-specific interfaces.\n", | ||
| 112 | "\n", | ||
| 113 | "- **D**ependency inversion principle - high-level modules/classes should not depend on low-level modules/classes but rather, they should depend upon abstractions. We also need to ensure that the abstraction should not depend upon details but the details should depend upon abstractions.\n", | ||
| 114 | "\n", | ||
| 115 | "\n", | ||
| 116 | "## Other\n", | ||
| 117 | "\n", | ||
| 118 | "- fewer arguments - write methods in such a way that the number of arguments is as minimal as possible. We can always use the values from other objects in the same class instead of asking the user the same input multiple times.\n", | ||
| 119 | "\n", | ||
| 120 | "- avoid global and non-deterministic behavior - \"we need to ensure that the global behavior of the variables and objects are minimized. This can be visualized with an example of creating an animal cheetah. The color of the animal doesn’t change after its creation. So, we need to ensure that the attribute is not global and is unreachable to make sure data clashes don’t occur. Therefore, the use of global variables or objects needs to be avoided. We can use the concept of encapsulation on the data members to solve this issue.\"\n", | ||
| 121 | "\n", | ||
| 122 | "- reducing conditional statements - The usage of conditional statements must be reduced as much as possible. Using too many conditional statements in the program increases the complexity as well as the code cannot be reused. Instead, we can make use of interfaces and abstract classes and implement the conditional logic in different methods which can be reused and also, the single responsibility of the methods and classes is maintained. Wherever we need to reuse the same conditioning, we simply call the method where it is implemented instead of writing the code again.\n", | ||
| 123 | "\n", | ||
| 124 | "- autonomy principle - " | ||
| 125 | ] | ||
| 126 | }, | ||
| 127 | { | ||
| 128 | "cell_type": "markdown", | ||
| 98 | "id": "9e15c843-f65b-4bd3-a536-e0217b3947f8", | 129 | "id": "9e15c843-f65b-4bd3-a536-e0217b3947f8", |
| 99 | "metadata": {}, | 130 | "metadata": {}, |
| 100 | "source": [ | 131 | "source": [ |
| @@ -114,7 +145,11 @@ | |||
| 114 | "Two objects with non-overlapping lifetimes may have the same *id()* value. In *CPython* this is the address of the object in memory.\n", | 145 | "Two objects with non-overlapping lifetimes may have the same *id()* value. In *CPython* this is the address of the object in memory.\n", |
| 115 | "\n", | 146 | "\n", |
| 116 | "Python has automatic memory management using reference counting. When an object no longer has\n", | 147 | "Python has automatic memory management using reference counting. When an object no longer has\n", |
| 117 | "any references, the garbage collector kicks inn and removes the object from memory." | 148 | "any references, the garbage collector kicks inn and removes the object from memory.\n", |
| 149 | "\n", | ||
| 150 | "\"The Zen of Python\" (import this) is still relevant and should be followed :)\n", | ||
| 151 | "\n", | ||
| 152 | "\"Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.\" - PEP484" | ||
| 118 | ] | 153 | ] |
| 119 | }, | 154 | }, |
| 120 | { | 155 | { |
| @@ -129,7 +164,7 @@ | |||
| 129 | }, | 164 | }, |
| 130 | { | 165 | { |
| 131 | "cell_type": "code", | 166 | "cell_type": "code", |
| 132 | "execution_count": 7, | 167 | "execution_count": 154, |
| 133 | "id": "391e816e-c53b-454b-9e9f-3587234f1fea", | 168 | "id": "391e816e-c53b-454b-9e9f-3587234f1fea", |
| 134 | "metadata": {}, | 169 | "metadata": {}, |
| 135 | "outputs": [ | 170 | "outputs": [ |
| @@ -137,21 +172,11 @@ | |||
| 137 | "name": "stdout", | 172 | "name": "stdout", |
| 138 | "output_type": "stream", | 173 | "output_type": "stream", |
| 139 | "text": [ | 174 | "text": [ |
| 140 | "2:8\n", | 175 | "my_first_point = <Point(x=2, y=8)>\n", |
| 141 | "2:8\n", | 176 | "my_first_point = <Point(x=2, y=8)>\n", |
| 142 | "2\n", | 177 | "2\n", |
| 143 | "2\n" | 178 | "2\n", |
| 144 | ] | 179 | "8\n" |
| 145 | }, | ||
| 146 | { | ||
| 147 | "ename": "AttributeError", | ||
| 148 | "evalue": "'Point' object has no attribute '__y'", | ||
| 149 | "output_type": "error", | ||
| 150 | "traceback": [ | ||
| 151 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", | ||
| 152 | "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", | ||
| 153 | "Cell \u001b[0;32mIn [7], line 69\u001b[0m\n\u001b[1;32m 66\u001b[0m \u001b[38;5;66;03m# we \"should not\" be accessing private and protected attributes directly\u001b[39;00m\n\u001b[1;32m 67\u001b[0m \u001b[38;5;28mprint\u001b[39m(my_first_point\u001b[38;5;241m.\u001b[39m_x)\n\u001b[0;32m---> 69\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[43mmy_first_point\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__y\u001b[49m)\n", | ||
| 154 | "\u001b[0;31mAttributeError\u001b[0m: 'Point' object has no attribute '__y'" | ||
| 155 | ] | 180 | ] |
| 156 | } | 181 | } |
| 157 | ], | 182 | ], |
| @@ -164,16 +189,16 @@ | |||
| 164 | " Called after the instance has been created (by __new__()), but before\n", | 189 | " Called after the instance has been created (by __new__()), but before\n", |
| 165 | " it is returned to the caller. The arguments are those passed to the\n", | 190 | " it is returned to the caller. The arguments are those passed to the\n", |
| 166 | " class constructor expression.\n", | 191 | " class constructor expression.\n", |
| 167 | " \n", | 192 | "\n", |
| 168 | " If a base class has an __init__() method, the derived class’s\n", | 193 | " If a base class has an __init__() method, the derived class’s\n", |
| 169 | " __init__() method, if any, must explicitly call it to ensure proper\n", | 194 | " __init__() method, if any, must explicitly call it to ensure proper\n", |
| 170 | " initialization of the base class part of the instance;\n", | 195 | " initialization of the base class part of the instance;\n", |
| 171 | " for example: super().__init__([args...]).\n", | 196 | " for example: super().__init__([args...]).\n", |
| 172 | " \"\"\"\n", | 197 | " \"\"\"\n", |
| 173 | " self._x = x # _ indicates \"protected\" attribute\n", | 198 | " self._x = x # _ indicates \"protected\" attribute\n", |
| 174 | " self.__y = y # not a typo: __ indicates \"private\" attribute\n", | 199 | " self.__y = y # not a typo: __ indicates \"private\" attribute.\n", |
| 175 | "\n", | 200 | "\n", |
| 176 | " def __repr__(self):\n", | 201 | " def __repr__(self) -> str:\n", |
| 177 | " \"\"\"\n", | 202 | " \"\"\"\n", |
| 178 | " Called by the repr() built-in function to compute the “official”\n", | 203 | " Called by the repr() built-in function to compute the “official”\n", |
| 179 | " string representation of an object. If at all possible, this should\n", | 204 | " string representation of an object. If at all possible, this should\n", |
| @@ -181,7 +206,7 @@ | |||
| 181 | " object with the same value (given an appropriate environment). If this\n", | 206 | " object with the same value (given an appropriate environment). If this\n", |
| 182 | " is not possible, a string of the form <...some useful description...>\n", | 207 | " is not possible, a string of the form <...some useful description...>\n", |
| 183 | " should be returned. The return value must be a string object.\n", | 208 | " should be returned. The return value must be a string object.\n", |
| 184 | " \n", | 209 | "\n", |
| 185 | " If a class defines __repr__() but not __str__(), then __repr__() is\n", | 210 | " If a class defines __repr__() but not __str__(), then __repr__() is\n", |
| 186 | " also used when an “informal” string representation of instances of\n", | 211 | " also used when an “informal” string representation of instances of\n", |
| 187 | " that class is required. This is typically used for debugging, so it is\n", | 212 | " that class is required. This is typically used for debugging, so it is\n", |
| @@ -189,7 +214,7 @@ | |||
| 189 | " \"\"\"\n", | 214 | " \"\"\"\n", |
| 190 | " return f\"<Point(x={self._x!r}, y={self.__y!r})>\"\n", | 215 | " return f\"<Point(x={self._x!r}, y={self.__y!r})>\"\n", |
| 191 | "\n", | 216 | "\n", |
| 192 | " def __str__(self):\n", | 217 | " def __str__(self) -> str:\n", |
| 193 | " \"\"\"\n", | 218 | " \"\"\"\n", |
| 194 | " Called by str(object) and the built-in functions format() and print()\n", | 219 | " Called by str(object) and the built-in functions format() and print()\n", |
| 195 | " to compute the “informal” or nicely printable string representation of\n", | 220 | " to compute the “informal” or nicely printable string representation of\n", |
| @@ -217,27 +242,43 @@ | |||
| 217 | "\n", | 242 | "\n", |
| 218 | "my_first_point = Point(2, 8)\n", | 243 | "my_first_point = Point(2, 8)\n", |
| 219 | "\n", | 244 | "\n", |
| 220 | "print(f\"{my_first_point}\")\n", | 245 | "print(f\"{my_first_point = }\")\n", |
| 221 | "print(f\"{my_first_point}\")\n", | 246 | "print(f\"{my_first_point = !r}\")\n", |
| 222 | "print(my_first_point.x)\n", | 247 | "print(my_first_point.x)\n", |
| 223 | "\n", | 248 | "\n", |
| 224 | "# we \"should not\" be accessing private and protected attributes directly\n", | 249 | "# we \"should not\" be accessing private and protected attributes directly\n", |
| 225 | "print(my_first_point._x)\n", | 250 | "print(my_first_point._x)\n", |
| 226 | "\n", | 251 | "\n", |
| 227 | "print(my_first_point.__y) # will not work\n", | 252 | "# print(my_first_point.__y) # will not work\n", |
| 228 | "# Out: AttributeError: 'Point' object has no attribute '__y'\n", | 253 | "# Out: AttributeError: 'Point' object has no attribute '__y'\n", |
| 229 | "\n", | 254 | "\n", |
| 230 | "# will work, but should not be used by a sane programmer:\n", | 255 | "# will work, but should not be used by a sane programmer:\n", |
| 231 | "# print(my_first_point._Point__y)" | 256 | "print(my_first_point._Point__y)" |
| 232 | ] | 257 | ] |
| 233 | }, | 258 | }, |
| 234 | { | 259 | { |
| 235 | "cell_type": "code", | 260 | "cell_type": "code", |
| 236 | "execution_count": null, | 261 | "execution_count": 155, |
| 237 | "id": "e82feefb-025d-4cbb-a8ac-ddc3cc01269c", | 262 | "id": "e82feefb-025d-4cbb-a8ac-ddc3cc01269c", |
| 238 | "metadata": {}, | 263 | "metadata": {}, |
| 239 | "outputs": [], | 264 | "outputs": [ |
| 265 | { | ||
| 266 | "name": "stdout", | ||
| 267 | "output_type": "stream", | ||
| 268 | "text": [ | ||
| 269 | "my_first_vector = <Vector (start=<Point(x=0, y=0)>, end=<Point(x=9, y=12)>>\n", | ||
| 270 | "my_first_vector = <Vector (start=<Point(x=0, y=0)>, end=<Point(x=12, y=12)>>\n", | ||
| 271 | "other_vector = <Vector (start=<Point(x=0, y=0)>, end=<Point(x=12, y=12)>>\n", | ||
| 272 | "other_vector.length = Decimal('16.97056274847714058562026469')\n", | ||
| 273 | "False\n", | ||
| 274 | "False\n", | ||
| 275 | "True\n" | ||
| 276 | ] | ||
| 277 | } | ||
| 278 | ], | ||
| 240 | "source": [ | 279 | "source": [ |
| 280 | "import decimal\n", | ||
| 281 | "\n", | ||
| 241 | "class Vector:\n", | 282 | "class Vector:\n", |
| 242 | " \"\"\"Basic class for representing vectors in 2D space\"\"\"\n", | 283 | " \"\"\"Basic class for representing vectors in 2D space\"\"\"\n", |
| 243 | "\n", | 284 | "\n", |
| @@ -245,8 +286,388 @@ | |||
| 245 | " self._start = start\n", | 286 | " self._start = start\n", |
| 246 | " self._end = end\n", | 287 | " self._end = end\n", |
| 247 | "\n", | 288 | "\n", |
| 248 | " def __str__(self):\n", | 289 | " def __repr__(self) -> str:\n", |
| 249 | " return f\"{self._start} -> {self._end}\"\n" | 290 | " return f\"<Vector (start={self._start!r}, end={self._end!r}>\"\n", |
| 291 | "\n", | ||
| 292 | " def __str__(self) -> str:\n", | ||
| 293 | " return f\"{self._start} -> {self._end}\"\n", | ||
| 294 | "\n", | ||
| 295 | " @property\n", | ||
| 296 | " def end(self) -> Point:\n", | ||
| 297 | " \"\"\"getter property end\"\"\"\n", | ||
| 298 | " return self._end\n", | ||
| 299 | "\n", | ||
| 300 | " @end.setter\n", | ||
| 301 | " def end(self, value: Point):\n", | ||
| 302 | " \"\"\"setter property end\"\"\"\n", | ||
| 303 | " self._end = value\n", | ||
| 304 | "\n", | ||
| 305 | " @property\n", | ||
| 306 | " def length(self) -> decimal.Decimal:\n", | ||
| 307 | " \"\"\"length of a Vector property\"\"\"\n", | ||
| 308 | " # length: sqrt(a^2 + b^2)\n", | ||
| 309 | " return (\n", | ||
| 310 | " decimal.Decimal(self._end.x - self._start.x) ** 2\n", | ||
| 311 | " + decimal.Decimal(self._end.y - self._start.y) ** 2\n", | ||
| 312 | " ) ** decimal.Decimal(\"0.5\")\n", | ||
| 313 | "\n", | ||
| 314 | " @property\n", | ||
| 315 | " def start(self) -> Point:\n", | ||
| 316 | " \"\"\"getter property start\"\"\"\n", | ||
| 317 | " return self._start\n", | ||
| 318 | "\n", | ||
| 319 | " @start.setter\n", | ||
| 320 | " def start(self, value: Point):\n", | ||
| 321 | " \"\"\"setter property start\"\"\"\n", | ||
| 322 | " self._start = value\n", | ||
| 323 | "\n", | ||
| 324 | "\n", | ||
| 325 | "my_first_vector = Vector(Point(0, 0), Point(9, 12))\n", | ||
| 326 | "print(f\"{my_first_vector = !r}\")\n", | ||
| 327 | "my_first_vector.end = Point(12, 12)\n", | ||
| 328 | "print(f\"{my_first_vector = !r}\")\n", | ||
| 329 | "other_vector = Vector(Point(0, 0), Point(12, 12))\n", | ||
| 330 | "print(f\"{other_vector = !r}\")\n", | ||
| 331 | "print(f\"{other_vector.length = }\")\n", | ||
| 332 | "\n", | ||
| 333 | "# the objects do not evaluate as \"alike\" because:\n", | ||
| 334 | "# - no comparison operator has been (re)defined\n", | ||
| 335 | "# - they have different IDs\n", | ||
| 336 | "print(Point(1, 2) == Point(1, 2))\n", | ||
| 337 | "print(my_first_vector == other_vector)\n", | ||
| 338 | "print(bool(Point(0, 0)))" | ||
| 339 | ] | ||
| 340 | }, | ||
| 341 | { | ||
| 342 | "cell_type": "code", | ||
| 343 | "execution_count": 156, | ||
| 344 | "id": "71a90ec6-3cc0-441e-a866-c2c2b9984559", | ||
| 345 | "metadata": {}, | ||
| 346 | "outputs": [ | ||
| 347 | { | ||
| 348 | "name": "stdout", | ||
| 349 | "output_type": "stream", | ||
| 350 | "text": [ | ||
| 351 | "my_first_vector = <Vector (start=<Point(x=0, y=0)>, end=<Point(x=9, y=12)>>\n", | ||
| 352 | "my_first_vector = <Vector (start=<Point(x=0, y=0)>, end=<Point(x=12, y=12)>>\n", | ||
| 353 | "other_vector = <Vector (start=<Point(x=0, y=0)>, end=<Point(x=12, y=12)>>\n", | ||
| 354 | "other_vector.length = Decimal('16.97056274847714058562026469')\n", | ||
| 355 | "True\n", | ||
| 356 | "True\n", | ||
| 357 | "False\n" | ||
| 358 | ] | ||
| 359 | } | ||
| 360 | ], | ||
| 361 | "source": [ | ||
| 362 | "# Equality and truth value testing\n", | ||
| 363 | "\n", | ||
| 364 | "class Point(Point): # N.B. Inheritance syntax used only for presentational purposes\n", | ||
| 365 | " \n", | ||
| 366 | " def __init__(self, x: int, y: int):\n", | ||
| 367 | " \"\"\"\n", | ||
| 368 | " Redefines the previous experimental constructor\n", | ||
| 369 | " \n", | ||
| 370 | " self._y is now used instead of self.__y\n", | ||
| 371 | " \"\"\"\n", | ||
| 372 | " self._x = x\n", | ||
| 373 | " self._y = y\n", | ||
| 374 | "\n", | ||
| 375 | " def __bool__(self):\n", | ||
| 376 | " \"\"\"\n", | ||
| 377 | " Called to implement truth value testing and the built-in operation\n", | ||
| 378 | " bool(); should return False or True. When this method is not defined,\n", | ||
| 379 | " __len__() is called, if it is defined, and the object is considered\n", | ||
| 380 | " true if its result is nonzero. If a class defines neither __len__()\n", | ||
| 381 | " nor __bool__(), all its instances are considered true.\n", | ||
| 382 | " \"\"\"\n", | ||
| 383 | " return bool(self._x or self._y)\n", | ||
| 384 | "\n", | ||
| 385 | " def __eq__(self, other) -> bool:\n", | ||
| 386 | " \"\"\"\n", | ||
| 387 | " x==y calls x.__eq__(y),\n", | ||
| 388 | "\n", | ||
| 389 | " A rich comparison method may return the singleton NotImplemented if it\n", | ||
| 390 | " does not implement the operation for a given pair of arguments.\n", | ||
| 391 | " By convention, False and True are returned for a successful comparison.\n", | ||
| 392 | " However, these methods can return any value\n", | ||
| 393 | " \"\"\"\n", | ||
| 394 | " return self._x == other.x and self._y == other.y\n", | ||
| 395 | "\n", | ||
| 396 | " # \"fix\" self.__y -> self._y\n", | ||
| 397 | " def __repr__(self) -> str:\n", | ||
| 398 | " return f\"<Point(x={self._x!r}, y={self._y!r})>\"\n", | ||
| 399 | "\n", | ||
| 400 | " def __str__(self) -> str:\n", | ||
| 401 | " return f\"{self._x}:{self._y}\"\n", | ||
| 402 | "\n", | ||
| 403 | " @property\n", | ||
| 404 | " def y(self) -> int:\n", | ||
| 405 | " \"\"\"getter property y\"\"\"\n", | ||
| 406 | " return self._y\n", | ||
| 407 | "\n", | ||
| 408 | "\n", | ||
| 409 | "class Vector(Vector):\n", | ||
| 410 | " \n", | ||
| 411 | " def __eq__(self, other):\n", | ||
| 412 | " return self._start == other.start and self._end == other.end\n", | ||
| 413 | "\n", | ||
| 414 | "\n", | ||
| 415 | "my_first_vector = Vector(Point(0, 0), Point(9, 12))\n", | ||
| 416 | "print(f\"{my_first_vector = !r}\")\n", | ||
| 417 | "my_first_vector.end = Point(12, 12)\n", | ||
| 418 | "print(f\"{my_first_vector = !r}\")\n", | ||
| 419 | "other_vector = Vector(Point(0, 0), Point(12, 12))\n", | ||
| 420 | "print(f\"{other_vector = !r}\")\n", | ||
| 421 | "print(f\"{other_vector.length = }\")\n", | ||
| 422 | "\n", | ||
| 423 | "print(Point(1, 2) == Point(1, 2))\n", | ||
| 424 | "print(my_first_vector == other_vector)\n", | ||
| 425 | "print(bool(Point(0, 0)))" | ||
| 426 | ] | ||
| 427 | }, | ||
| 428 | { | ||
| 429 | "cell_type": "markdown", | ||
| 430 | "id": "696cf992-03f0-49a4-b7ab-1adfcf1f099d", | ||
| 431 | "metadata": {}, | ||
| 432 | "source": [ | ||
| 433 | "# Iterators\n", | ||
| 434 | "\n", | ||
| 435 | "Objects representing a stream of data.\n", | ||
| 436 | "\n", | ||
| 437 | "Repeated calls to the iterator’s `__next__()` method (or passing it to the built-in function `next()`) return successive items in the stream. \n", | ||
| 438 | "When no more data are available a `StopIteration` exception is raised instead. \n", | ||
| 439 | "At this point, the iterator object is exhausted and any further calls to its `__next__()` method just raise `StopIteration` again.\n", | ||
| 440 | "\n", | ||
| 441 | "Iterators are required to have an `__iter__()` method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted.\n", | ||
| 442 | "\n", | ||
| 443 | "One notable exception is code which attempts multiple iteration passes. A container object (such as a list) produces a fresh new iterator each time you pass it to the `iter()` function or use it in a for loop.\n", | ||
| 444 | "\n", | ||
| 445 | "Attempting this with an iterator will just return the same exhausted iterator object used in the previous iteration pass, making it appear like an empty container.\n", | ||
| 446 | "\n", | ||
| 447 | "\n", | ||
| 448 | "## Generators\n", | ||
| 449 | "\n", | ||
| 450 | "Python’s generators provide a convenient way to implement the iterator protocol. If a container object’s `__iter__()` method is implemented as a generator, it will automatically return an iterator object (technically, a generator object) supplying the `__iter__()` and `__next__()` methods.\n", | ||
| 451 | "\n", | ||
| 452 | "More information about generators can be found in the documentation for the `yield` expression." | ||
| 453 | ] | ||
| 454 | }, | ||
| 455 | { | ||
| 456 | "cell_type": "code", | ||
| 457 | "execution_count": 157, | ||
| 458 | "id": "a4d2735d-d167-4f43-ad55-cb3d13ece2dc", | ||
| 459 | "metadata": {}, | ||
| 460 | "outputs": [ | ||
| 461 | { | ||
| 462 | "name": "stdout", | ||
| 463 | "output_type": "stream", | ||
| 464 | "text": [ | ||
| 465 | "list(vector) = [<Point(x=1, y=1)>, <Point(x=4, y=6)>]\n", | ||
| 466 | "1:1\n", | ||
| 467 | "4:6\n", | ||
| 468 | "Exhausted\n" | ||
| 469 | ] | ||
| 470 | } | ||
| 471 | ], | ||
| 472 | "source": [ | ||
| 473 | "# Iterators and generators\n", | ||
| 474 | "\n", | ||
| 475 | "class Vector(Vector):\n", | ||
| 476 | " \"\"\"3rd. edition of our wonderful class\"\"\"\n", | ||
| 477 | " \n", | ||
| 478 | " def __iter__(self):\n", | ||
| 479 | " \"\"\"\n", | ||
| 480 | " This method is called when an iterator is required for a container.\n", | ||
| 481 | " This method should return a new iterator object that can iterate over\n", | ||
| 482 | " all the objects in the container. For mappings, it should iterate\n", | ||
| 483 | " over the keys of the container.\n", | ||
| 484 | " \"\"\"\n", | ||
| 485 | " for element in (self._start, self._end):\n", | ||
| 486 | " yield element\n", | ||
| 487 | "\n", | ||
| 488 | "vector = Vector(Point(1, 1), Point(4, 6))\n", | ||
| 489 | "print(f\"{list(vector) = }\")\n", | ||
| 490 | "\n", | ||
| 491 | "# testing / playing with the iterator manually\n", | ||
| 492 | "vector_iterator = iter(vector)\n", | ||
| 493 | "print(next(vector_iterator, \"Exhausted\"))\n", | ||
| 494 | "print(next(vector_iterator, \"Exhausted\"))\n", | ||
| 495 | "print(next(vector_iterator, \"Exhausted\"))" | ||
| 496 | ] | ||
| 497 | }, | ||
| 498 | { | ||
| 499 | "cell_type": "code", | ||
| 500 | "execution_count": 158, | ||
| 501 | "id": "a6ead5e6-e987-4bda-bf48-30f91877278d", | ||
| 502 | "metadata": {}, | ||
| 503 | "outputs": [ | ||
| 504 | { | ||
| 505 | "name": "stdout", | ||
| 506 | "output_type": "stream", | ||
| 507 | "text": [ | ||
| 508 | "len(vector) = 7\n", | ||
| 509 | "start_point in vector = True\n", | ||
| 510 | "Point(0, 0) in vector = True\n" | ||
| 511 | ] | ||
| 512 | } | ||
| 513 | ], | ||
| 514 | "source": [ | ||
| 515 | "# Membership and length\n", | ||
| 516 | "\n", | ||
| 517 | "class Vector(Vector):\n", | ||
| 518 | " \n", | ||
| 519 | " def __contains__(self, point: Point) -> bool:\n", | ||
| 520 | " \"\"\"\n", | ||
| 521 | " Called to implement membership test operators. Should return True if\n", | ||
| 522 | " item is in self, False otherwise. For mapping objects, this should\n", | ||
| 523 | " consider the keys of the mapping rather than the values or the\n", | ||
| 524 | " key-item pairs.\n", | ||
| 525 | "\n", | ||
| 526 | " For objects that don’t define __contains__(), the membership test\n", | ||
| 527 | " first tries iteration via __iter__(), then the old sequence iteration\n", | ||
| 528 | " protocol via __getitem__(), see this section in the language\n", | ||
| 529 | " reference.\n", | ||
| 530 | " \"\"\"\n", | ||
| 531 | " # ont of the following two strategies may be employed:\n", | ||
| 532 | " # return point is self._start or point is self._end\n", | ||
| 533 | " # return point == self._start or point == self._end\n", | ||
| 534 | " return point == self._start or point == self._end\n", | ||
| 535 | "\n", | ||
| 536 | " def __len__(self) -> int:\n", | ||
| 537 | " \"\"\"\n", | ||
| 538 | " Called to implement the built-in function len().\n", | ||
| 539 | " Should return the length of the object, an integer >= 0.\n", | ||
| 540 | " Also, an object that doesn’t define a __bool__() method and whose\n", | ||
| 541 | " __len__() method returns zero is considered to be false in a Boolean context.\n", | ||
| 542 | "\n", | ||
| 543 | " CPython implementation detail: In CPython, the length is required to be at most sys.maxsize.\n", | ||
| 544 | " If the length is larger than sys.maxsize some features (such as len())\n", | ||
| 545 | " may raise OverflowError. To prevent raising OverflowError by truth\n", | ||
| 546 | " value testing, an object must define a __bool__() method.\n", | ||
| 547 | " \"\"\"\n", | ||
| 548 | " return int(self.length)\n", | ||
| 549 | "\n", | ||
| 550 | "start_point = Point(0, 0)\n", | ||
| 551 | "vector = Vector(start_point, Point(4, 6))\n", | ||
| 552 | "print(f\"{len(vector) = }\") # \"real length\" 7.21...\n", | ||
| 553 | "print(f\"{start_point in vector = }\")\n", | ||
| 554 | "print(f\"{Point(0, 0) in vector = }\") # False if 'is' is used instead of '==' in __contains__\n" | ||
| 555 | ] | ||
| 556 | }, | ||
| 557 | { | ||
| 558 | "cell_type": "code", | ||
| 559 | "execution_count": 159, | ||
| 560 | "id": "11f6a3fb-64d7-40a9-a130-27548ec4b802", | ||
| 561 | "metadata": {}, | ||
| 562 | "outputs": [ | ||
| 563 | { | ||
| 564 | "name": "stdout", | ||
| 565 | "output_type": "stream", | ||
| 566 | "text": [ | ||
| 567 | "__mul__ called\n", | ||
| 568 | "vector * 3 = <Vector (start=<Point(x=2, y=2)>, end=<Point(x=12, y=18)>>\n", | ||
| 569 | "__rmul__ called\n", | ||
| 570 | "3 * vector = <Vector (start=<Point(x=2, y=2)>, end=<Point(x=12, y=18)>>\n" | ||
| 571 | ] | ||
| 572 | } | ||
| 573 | ], | ||
| 574 | "source": [ | ||
| 575 | "# Emulating numeric types\n", | ||
| 576 | "\n", | ||
| 577 | "class Vector(Vector):\n", | ||
| 578 | "\n", | ||
| 579 | " def __getitem__(self, key):\n", | ||
| 580 | " \"\"\"\n", | ||
| 581 | " Called to implement evaluation of self[key].\n", | ||
| 582 | " For sequence types, the accepted keys should be integers and slice objects.\n", | ||
| 583 | "\n", | ||
| 584 | " Note that the special interpretation of negative indexes (if the class\n", | ||
| 585 | " wishes to emulate a sequence type) is up to the __getitem__() method.\n", | ||
| 586 | " If key is of an inappropriate type, TypeError may be raised; if of a\n", | ||
| 587 | " value outside the set of indexes for the sequence (after any special\n", | ||
| 588 | " interpretation of negative values), IndexError should be raised.\n", | ||
| 589 | " For mapping types, if key is missing (not in the container),\n", | ||
| 590 | " KeyError should be raised.\n", | ||
| 591 | " \"\"\"\n", | ||
| 592 | " if key in (\"start\", 0):\n", | ||
| 593 | " return self._start\n", | ||
| 594 | " if key in (\"end\", 1):\n", | ||
| 595 | " return self._end\n", | ||
| 596 | " raise KeyError\n", | ||
| 597 | "\n", | ||
| 598 | " def __mul__(self, other):\n", | ||
| 599 | " \"\"\"\n", | ||
| 600 | " Implements the binary arithmetic operation: *\n", | ||
| 601 | "\n", | ||
| 602 | " For instance, to evaluate the expression vector1 * vector2,\n", | ||
| 603 | " where vector1 is an instance of a class that has an __mul__() method,\n", | ||
| 604 | " vector1.__mul__(vector2) is called.\n", | ||
| 605 | "\n", | ||
| 606 | " If the method does not support the operation with the supplied\n", | ||
| 607 | " arguments, it should return NotImplemented.\n", | ||
| 608 | " \"\"\"\n", | ||
| 609 | " print(\"__mul__ called\")\n", | ||
| 610 | " if not isinstance(other, int):\n", | ||
| 611 | " return NotImplemented\n", | ||
| 612 | " return Vector(self._start, Point(self._end.x * other, self._end.y * other))\n", | ||
| 613 | "\n", | ||
| 614 | " def __rmul__(self, other):\n", | ||
| 615 | " \"\"\"\n", | ||
| 616 | " Implements the binary arithmetic operation '*' with reflected (swapped) operands.\n", | ||
| 617 | " This method is only called if the left operand does not support the corresponding\n", | ||
| 618 | " operation and the operands are of different types.\n", | ||
| 619 | " \n", | ||
| 620 | " For instance, to evaluate the expression int1 * vector1, where vector1\n", | ||
| 621 | " is an instance of a class that has an __rmul__() method,\n", | ||
| 622 | " vector1.__rmul__(int1) is called if int1.__int__(vector1)\n", | ||
| 623 | " returns NotImplemented.\n", | ||
| 624 | " \"\"\"\n", | ||
| 625 | " print(\"__rmul__ called\")\n", | ||
| 626 | " return Vector(self._start, Point(self._end.x * other, self._end.y * other))\n", | ||
| 627 | "\n", | ||
| 628 | " # __rmul__ = __mul__\n", | ||
| 629 | "\n", | ||
| 630 | "vector = Vector(Point(2, 2), Point(4, 6))\n", | ||
| 631 | "print(f\"{vector * 3 = }\")\n", | ||
| 632 | "print(f\"{3 * vector = }\")" | ||
| 633 | ] | ||
| 634 | }, | ||
| 635 | { | ||
| 636 | "cell_type": "code", | ||
| 637 | "execution_count": null, | ||
| 638 | "id": "c345a69f-02da-40e7-bb37-c0511b6af096", | ||
| 639 | "metadata": {}, | ||
| 640 | "outputs": [], | ||
| 641 | "source": [ | ||
| 642 | "# Class methods and static methods\n", | ||
| 643 | "\n", | ||
| 644 | "class Vector(Vector):\n", | ||
| 645 | " \n", | ||
| 646 | " @classmethod\n", | ||
| 647 | " def from_str(cls, obj_str: str) -> cls:\n", | ||
| 648 | " \"\"\"\n", | ||
| 649 | " " | ||
| 650 | ] | ||
| 651 | }, | ||
| 652 | { | ||
| 653 | "cell_type": "markdown", | ||
| 654 | "id": "540de317-ba77-4f5c-97ba-f1c0bec071fb", | ||
| 655 | "metadata": {}, | ||
| 656 | "source": [ | ||
| 657 | "# Additional reading\n", | ||
| 658 | "\n", | ||
| 659 | "- [Descriptors](https://docs.python.org/3/reference/datamodel.html#implementing-descriptors)\n", | ||
| 660 | "\n", | ||
| 661 | "- [Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)\n", | ||
| 662 | "\n", | ||
| 663 | "- `__call__`\n", | ||
| 664 | "\n", | ||
| 665 | "- `__getattr__`\n", | ||
| 666 | "\n", | ||
| 667 | "- `__hash__`\n", | ||
| 668 | "\n", | ||
| 669 | "\n", | ||
| 670 | "... preferably everything in [Data model - https://docs.python.org/3/reference/datamodel.html](https://docs.python.org/3/reference/datamodel.html)" | ||
| 250 | ] | 671 | ] |
| 251 | } | 672 | } |
| 252 | ], | 673 | ], |
diff --git a/notebooks/sqlalchemy/sqlalchemy.ipynb b/notebooks/sqlalchemy/sqlalchemy.ipynb index 51b00ab..8bb8333 100644 --- a/notebooks/sqlalchemy/sqlalchemy.ipynb +++ b/notebooks/sqlalchemy/sqlalchemy.ipynb | |||
| @@ -288,7 +288,9 @@ | |||
| 288 | "*Table* object can be instructed to load information about itself from the corresponding database schema object already existing within the database.\n", | 288 | "*Table* object can be instructed to load information about itself from the corresponding database schema object already existing within the database.\n", |
| 289 | "This process is called *reflection*.\n", | 289 | "This process is called *reflection*.\n", |
| 290 | "\n", | 290 | "\n", |
| 291 | "If the DB schema is already defined and maintained \"somewhere else\", it may be useful to \"reflect\" the schema instead of explicitly defining it." | 291 | "If the DB schema is already defined and maintained \"somewhere else\", it may be useful to \"reflect\" the schema instead of explicitly defining it.\n", |
| 292 | "\n", | ||
| 293 | "**N.B. This is not the recommended way to use SQLAlchemy**" | ||
| 292 | ] | 294 | ] |
| 293 | }, | 295 | }, |
| 294 | { | 296 | { |
| @@ -357,6 +359,7 @@ | |||
| 357 | } | 359 | } |
| 358 | ], | 360 | ], |
| 359 | "source": [ | 361 | "source": [ |
| 362 | "## N.B. This example does NOT represent SQLAlchemy - best practice\n", | ||
| 360 | "from sqlalchemy.ext.declarative import declarative_base\n", | 363 | "from sqlalchemy.ext.declarative import declarative_base\n", |
| 361 | "\n", | 364 | "\n", |
| 362 | "Base = declarative_base()\n", | 365 | "Base = declarative_base()\n", |
