diff options
| author | Simeon Simeonov | 2023-01-18 21:12:31 +0100 |
|---|---|---|
| committer | Simeon Simeonov | 2023-01-18 21:12:31 +0100 |
| commit | 6dcd1727eb9d5dbfcb8f6e9599ada10d33063bac (patch) | |
| tree | 57d6aa95763fd57835316cca7ce1ae33f3f19ec2 /notebooks/python | |
| parent | a33898d9db2f0e0e9b3da6f1b329b4045b65af11 (diff) | |
Remove some old presentations
Diffstat (limited to 'notebooks/python')
| -rw-r--r-- | notebooks/python/python_intro.ipynb | 2 | ||||
| -rw-r--r-- | notebooks/python/python_oo.ipynb | 107 |
2 files changed, 79 insertions, 30 deletions
diff --git a/notebooks/python/python_intro.ipynb b/notebooks/python/python_intro.ipynb index 3c49a14..0168839 100644 --- a/notebooks/python/python_intro.ipynb +++ b/notebooks/python/python_intro.ipynb | |||
| @@ -1253,7 +1253,7 @@ | |||
| 1253 | "name": "python", | 1253 | "name": "python", |
| 1254 | "nbconvert_exporter": "python", | 1254 | "nbconvert_exporter": "python", |
| 1255 | "pygments_lexer": "ipython3", | 1255 | "pygments_lexer": "ipython3", |
| 1256 | "version": "3.10.8" | 1256 | "version": "3.11.1" |
| 1257 | } | 1257 | } |
| 1258 | }, | 1258 | }, |
| 1259 | "nbformat": 4, | 1259 | "nbformat": 4, |
diff --git a/notebooks/python/python_oo.ipynb b/notebooks/python/python_oo.ipynb index b56aa26..1c655d0 100644 --- a/notebooks/python/python_oo.ipynb +++ b/notebooks/python/python_oo.ipynb | |||
| @@ -179,7 +179,8 @@ | |||
| 179 | "\"The Zen of Python\" (import this) is still relevant and should be followed :)\n", | 179 | "\"The Zen of Python\" (import this) is still relevant and should be followed :)\n", |
| 180 | "\n", | 180 | "\n", |
| 181 | "\"Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.\" - PEP484\n", | 181 | "\"Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.\" - PEP484\n", |
| 182 | "\n" | 182 | "\n", |
| 183 | "Class (type) names should normally use the *CapWords* convention - CapitalizedWords (or *CapWords*, or *CamelCase*). This is also sometimes known as *StudlyCaps*. ASCII characters and English names only!" | ||
| 183 | ] | 184 | ] |
| 184 | }, | 185 | }, |
| 185 | { | 186 | { |
| @@ -225,17 +226,17 @@ | |||
| 225 | "name": "stdout", | 226 | "name": "stdout", |
| 226 | "output_type": "stream", | 227 | "output_type": "stream", |
| 227 | "text": [ | 228 | "text": [ |
| 228 | "car1.get_obj_info_str() = \"I am <__main__.Car object at 0x7f5630542810> with id 140008154736656 from <class '__main__.Car'> with id 94904583728688\"\n", | 229 | "car1.get_obj_info_str() = \"I am <__main__.Car object at 0x7fc7add96f10> with id 140495591927568 from <class '__main__.Car'> with id 94628241379728\"\n", |
| 229 | "car2.get_obj_info_str() = \"I am <__main__.Car object at 0x7f5633ec8190> with id 140008215052688 from <class '__main__.Car'> with id 94904583728688\"\n", | 230 | "car2.get_obj_info_str() = \"I am <__main__.Car object at 0x7fc7aef459d0> with id 140495610468816 from <class '__main__.Car'> with id 94628241379728\"\n", |
| 230 | "car1.model = 'BMW', car1.reg_nr = 'EC76183', car1.extras = ['GPSnav', 'Sound system'], id(car1.cls_extras) = 140008154734272, id(car1.__init__) = 140008154735680\n", | 231 | "car1.model = 'BMW', car1.reg_nr = 'EC76183', car1.extras = ['GPSnav', 'Sound system'], id(car1.cls_extras) = 140495591909184, id(car1.get_obj_info_str) = 140495591927808\n", |
| 231 | "car2.model = 'Scoda', car2.reg_nr = 'BD77655', car2.extras = ['GPSnav'], id(car2.cls_extras) = 140008154734272, id(car2.__init__) = 140008154736768\n", | 232 | "car2.model = 'Scoda', car2.reg_nr = 'BD77655', car2.extras = ['GPSnav'], id(car2.cls_extras) = 140495591909184, id(car2.get_obj_info_str) = 140495591927936\n", |
| 232 | "id(Car.cls_extras) = 140008154734272, id(Car.__init__) = 140008154484864\n", | 233 | "id(Car.cls_extras) = 140495591909184, id(Car.get_obj_info_str) = 140495591682944\n", |
| 233 | "car1.cls_extras = ['GPSnav', 'Sound system'], id(car1.cls_extras) = 140008154734272\n", | 234 | "car1.cls_extras = ['GPSnav', 'Sound system'], id(car1.cls_extras) = 140495591909184\n", |
| 234 | "car2.cls_extras = ['GPSnav', 'Sound system'], id(car2.cls_extras) = 140008154734272\n", | 235 | "car2.cls_extras = ['GPSnav', 'Sound system'], id(car2.cls_extras) = 140495591909184\n", |
| 235 | "True\n", | 236 | "True\n", |
| 236 | "hasattr(car1, 'import_tax_paid') = True\n", | 237 | "hasattr(car1, 'import_tax_paid') = True\n", |
| 237 | "hasattr(car2, 'import_tax_paid') = False\n", | 238 | "hasattr(car2, 'import_tax_paid') = False\n", |
| 238 | "id(car1.__class__) = 94904583728688, id(car2.__class__) = 94904583728688, id(Car) = 94904583728688\n" | 239 | "id(car1.__class__) = 94628241379728, id(car2.__class__) = 94628241379728, id(Car) = 94628241379728\n" |
| 239 | ] | 240 | ] |
| 240 | } | 241 | } |
| 241 | ], | 242 | ], |
| @@ -244,6 +245,7 @@ | |||
| 244 | "\n", | 245 | "\n", |
| 245 | " cls_extras = [] # class attribute - shared by all instances\n", | 246 | " cls_extras = [] # class attribute - shared by all instances\n", |
| 246 | " # cls_extras: list = [] is also possible\n", | 247 | " # cls_extras: list = [] is also possible\n", |
| 248 | " car_count = 0\n", | ||
| 247 | "\n", | 249 | "\n", |
| 248 | " def __init__(self, model: str, reg_nr: str, extras: list):\n", | 250 | " def __init__(self, model: str, reg_nr: str, extras: list):\n", |
| 249 | " \"\"\"\n", | 251 | " \"\"\"\n", |
| @@ -262,24 +264,40 @@ | |||
| 262 | "\n", | 264 | "\n", |
| 263 | " # self.cls_extras = extras # will create a *NEW instance attribute* called 'cls_extras'\n", | 265 | " # self.cls_extras = extras # will create a *NEW instance attribute* called 'cls_extras'\n", |
| 264 | " \n", | 266 | " \n", |
| 267 | " Car.car_count += 1 # increase Car.car_count by 1 every time a car is created \n", | ||
| 268 | " \n", | ||
| 269 | " def __del__(self):\n", | ||
| 270 | " \"\"\"\n", | ||
| 271 | " Called when the instance is about to be destroyed.\n", | ||
| 272 | "\n", | ||
| 273 | " This is also called a finalizer or (improperly) a destructor.\n", | ||
| 274 | " \"\"\"\n", | ||
| 275 | " Car.car_count -= 1 # decrease by 1 every time a Car object is destroyed\n", | ||
| 276 | "\n", | ||
| 265 | " def get_obj_info_str(self): # regular method that becomes instance attribute\n", | 277 | " def get_obj_info_str(self): # regular method that becomes instance attribute\n", |
| 266 | " return f\"I am {self} with id {id(self)} from {self.__class__} with id {id(self.__class__)}\"\n", | 278 | " return f\"I am {self} with id {id(self)} from {self.__class__} with id {id(self.__class__)}\"\n", |
| 267 | "\n", | 279 | "\n", |
| 268 | "car1 = Car(\"BMW\", \"EC76183\", [\"GPSnav\", \"Sound system\"])\n", | 280 | "car1 = Car(\"BMW\", \"EC76183\", [\"GPSnav\", \"Sound system\"])\n", |
| 269 | "car2 = Car(\"Scoda\", \"BD77655\", [\"GPSnav\"])\n", | 281 | "car2 = Car(\"Scoda\", \"BD77655\", [\"GPSnav\"])\n", |
| 270 | "\n", | 282 | "\n", |
| 271 | "print(f\"{car1.get_obj_info_str() = }\")\n", | 283 | "print(f\"{car1.get_obj_info_str() = }\") # same as calling Car.get_object_info_str(car1)\n", |
| 272 | "print(f\"{car2.get_obj_info_str() = }\")\n", | 284 | "print(f\"{car2.get_obj_info_str() = }\")\n", |
| 273 | "\n", | 285 | "\n", |
| 274 | "# default behavior for `object.attr`:\n", | 286 | "# default behavior for `object.attr`:\n", |
| 275 | "# getter:\n", | 287 | "# getter:\n", |
| 276 | "# - checks if 'attr' is an instance attribute\n", | 288 | "# - checks if 'attr' is an instance attribute\n", |
| 277 | "# - checks if 'attr' is a class attribute\n", | 289 | "# - checks if 'attr' is a class attribute (through the method resolution order - MRO)\n", |
| 278 | "# - raises AttributeError\n", | 290 | "# - raises AttributeError\n", |
| 279 | "# setter: (re)defines an instance attribute\n", | 291 | "# setter:\n", |
| 280 | "print(f\"{car1.model = }, {car1.reg_nr = }, {car1.extras = }, {id(car1.cls_extras) = }, {id(car1.__init__) = }\")\n", | 292 | "# - (re)defines an instance attribute\n", |
| 281 | "print(f\"{car2.model = }, {car2.reg_nr = }, {car2.extras = }, {id(car2.cls_extras) = }, {id(car2.__init__) = }\")\n", | 293 | "\n", |
| 282 | "print(f\"{id(Car.cls_extras) = }, {id(Car.__init__) = }\")\n", | 294 | "# Details not covered in this course:\n", |
| 295 | "# Car.x is translated to Car.__dict__[\"x\"] (...through the MRO)\n", | ||
| 296 | "# car1.x is translated to car1.__dict__[\"x\"] (if not found ... Car.x (see above), if not found car1.__getattr__(\"x\") is called)\n", | ||
| 297 | "\n", | ||
| 298 | "print(f\"{car1.model = }, {car1.reg_nr = }, {car1.extras = }, {id(car1.cls_extras) = }, {id(car1.get_obj_info_str) = }\")\n", | ||
| 299 | "print(f\"{car2.model = }, {car2.reg_nr = }, {car2.extras = }, {id(car2.cls_extras) = }, {id(car2.get_obj_info_str) = }\")\n", | ||
| 300 | "print(f\"{id(Car.cls_extras) = }, {id(Car.get_obj_info_str) = }\")\n", | ||
| 283 | "car1.cls_extras.extend(car1.extras)\n", | 301 | "car1.cls_extras.extend(car1.extras)\n", |
| 284 | "# car1.cls_extras = car1.extras # N.B. This will create a *NEW instance attribute* called 'cls_extras'\n", | 302 | "# car1.cls_extras = car1.extras # N.B. This will create a *NEW instance attribute* called 'cls_extras'\n", |
| 285 | "print(f\"{car1.cls_extras = }, {id(car1.cls_extras) = }\")\n", | 303 | "print(f\"{car1.cls_extras = }, {id(car1.cls_extras) = }\")\n", |
| @@ -289,7 +307,7 @@ | |||
| 289 | "car1.import_tax_paid = True # 'import_tax_paid' will only be present in car1\n", | 307 | "car1.import_tax_paid = True # 'import_tax_paid' will only be present in car1\n", |
| 290 | "print(car1.import_tax_paid)\n", | 308 | "print(car1.import_tax_paid)\n", |
| 291 | "print(f\"{hasattr(car1, 'import_tax_paid') = }\") # Out: True\n", | 309 | "print(f\"{hasattr(car1, 'import_tax_paid') = }\") # Out: True\n", |
| 292 | "print(f\"{hasattr(car2, 'import_tax_paid') = }\") # Out: False\n", | 310 | "print(f\"{hasattr(car2, 'import_tax_paid') = }\") # Out: False - no 'import_tax_paid' attribute in object car2\n", |
| 293 | "\n", | 311 | "\n", |
| 294 | "# .__class__ instance attribute pointing to its class (type) will be created\n", | 312 | "# .__class__ instance attribute pointing to its class (type) will be created\n", |
| 295 | "print(f\"{id(car1.__class__) = }, {id(car2.__class__) = }, {id(Car) = }\")" | 313 | "print(f\"{id(car1.__class__) = }, {id(car2.__class__) = }, {id(Car) = }\")" |
| @@ -705,6 +723,7 @@ | |||
| 705 | "name": "stdout", | 723 | "name": "stdout", |
| 706 | "output_type": "stream", | 724 | "output_type": "stream", |
| 707 | "text": [ | 725 | "text": [ |
| 726 | "2:2\n", | ||
| 708 | "__mul__ called\n", | 727 | "__mul__ called\n", |
| 709 | "vector * 3 = Vector(start=Point(x=2, y=2), end=Point(x=12, y=18))\n", | 728 | "vector * 3 = Vector(start=Point(x=2, y=2), end=Point(x=12, y=18))\n", |
| 710 | "__rmul__ called\n", | 729 | "__rmul__ called\n", |
| @@ -771,6 +790,7 @@ | |||
| 771 | " # __rmul__ = __mul__\n", | 790 | " # __rmul__ = __mul__\n", |
| 772 | "\n", | 791 | "\n", |
| 773 | "vector = Vector(Point(2, 2), Point(4, 6))\n", | 792 | "vector = Vector(Point(2, 2), Point(4, 6))\n", |
| 793 | "print(f\"{vector['start']}\")\n", | ||
| 774 | "print(f\"{vector * 3 = }\")\n", | 794 | "print(f\"{vector * 3 = }\")\n", |
| 775 | "print(f\"{3 * vector = }\")" | 795 | "print(f\"{3 * vector = }\")" |
| 776 | ] | 796 | ] |
| @@ -863,7 +883,7 @@ | |||
| 863 | "id": "d1d17f26-de6e-4510-b0f0-3ae02761ae24", | 883 | "id": "d1d17f26-de6e-4510-b0f0-3ae02761ae24", |
| 864 | "metadata": {}, | 884 | "metadata": {}, |
| 865 | "source": [ | 885 | "source": [ |
| 866 | "# Example 2\n", | 886 | "# Example 3\n", |
| 867 | "\n", | 887 | "\n", |
| 868 | "We will create a small and incomplete Animal class hierarchy" | 888 | "We will create a small and incomplete Animal class hierarchy" |
| 869 | ] | 889 | ] |
| @@ -973,7 +993,7 @@ | |||
| 973 | " super().__init__(\n", | 993 | " super().__init__(\n", |
| 974 | " weight=weight, tooth_replacement=True, alive=alive, **kwargs\n", | 994 | " weight=weight, tooth_replacement=True, alive=alive, **kwargs\n", |
| 975 | " )\n", | 995 | " )\n", |
| 976 | " self_conservation_status = conservation_status\n", | 996 | " self._conservation_status = conservation_status\n", |
| 977 | "\n", | 997 | "\n", |
| 978 | "\n", | 998 | "\n", |
| 979 | "class Cow(Mammal):\n", | 999 | "class Cow(Mammal):\n", |
| @@ -1104,6 +1124,7 @@ | |||
| 1104 | " \"\"\"\n", | 1124 | " \"\"\"\n", |
| 1105 | "\n", | 1125 | "\n", |
| 1106 | "\n", | 1126 | "\n", |
| 1127 | "# not good! The user of JSONMixin should not have to explicitly know about / use DictMixin\n", | ||
| 1107 | "class JSONConvertableTigerShark(TigerShark, DictMixin, JSONMixin):\n", | 1128 | "class JSONConvertableTigerShark(TigerShark, DictMixin, JSONMixin):\n", |
| 1108 | " \"\"\"A tiger shark class that provides JSON conversion functionality\"\"\"\n", | 1129 | " \"\"\"A tiger shark class that provides JSON conversion functionality\"\"\"\n", |
| 1109 | "\n", | 1130 | "\n", |
| @@ -1143,14 +1164,7 @@ | |||
| 1143 | "MRO is monotonic when the following is true: if C1 precedes C2 in the linearization of C, then C1 precedes C2 in the linearization of any subclass of C.\n", | 1164 | "MRO is monotonic when the following is true: if C1 precedes C2 in the linearization of C, then C1 precedes C2 in the linearization of any subclass of C.\n", |
| 1144 | "\n", | 1165 | "\n", |
| 1145 | "Not all classes admit a linearization. There are cases, in complicated hierarchies, where it is not possible to derive a class such that its linearization respects all the desired properties. \n", | 1166 | "Not all classes admit a linearization. There are cases, in complicated hierarchies, where it is not possible to derive a class such that its linearization respects all the desired properties. \n", |
| 1146 | "`TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y` is raised then.\n", | 1167 | "`TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y` is raised then." |
| 1147 | "\n", | ||
| 1148 | "\n", | ||
| 1149 | "## The Liskov substitution principle (LSP):\n", | ||
| 1150 | "\n", | ||
| 1151 | "Functions that use references to base class objects must be able to use objects of derived classes without knowing it.\n", | ||
| 1152 | "\n", | ||
| 1153 | "In other words: an object (such as a class) may be replaced by a sub-object (such as a class that extends the first class) without breaking the program.\n" | ||
| 1154 | ] | 1168 | ] |
| 1155 | }, | 1169 | }, |
| 1156 | { | 1170 | { |
| @@ -1233,6 +1247,14 @@ | |||
| 1233 | ] | 1247 | ] |
| 1234 | }, | 1248 | }, |
| 1235 | { | 1249 | { |
| 1250 | "cell_type": "markdown", | ||
| 1251 | "id": "875e7ffd-4feb-4ef0-b912-0a939c6c2dd1", | ||
| 1252 | "metadata": {}, | ||
| 1253 | "source": [ | ||
| 1254 | "... **N.B.** The most important thing to note here is that if `super()` is used in `Grandfather1` for this MRO (f.i. object of type `Simeon`) , none of its parents or siblings is called, but `Father` that was probably \"not even born\" when `Grandfather1` was created. :)" | ||
| 1255 | ] | ||
| 1256 | }, | ||
| 1257 | { | ||
| 1236 | "cell_type": "code", | 1258 | "cell_type": "code", |
| 1237 | "execution_count": 12, | 1259 | "execution_count": 12, |
| 1238 | "id": "95a1d0cc-0006-4ddc-b08d-d0b8b02acfba", | 1260 | "id": "95a1d0cc-0006-4ddc-b08d-d0b8b02acfba", |
| @@ -1274,7 +1296,6 @@ | |||
| 1274 | " \"\"\"Produces JSON content for production plans\"\"\"\n", | 1296 | " \"\"\"Produces JSON content for production plans\"\"\"\n", |
| 1275 | " \n", | 1297 | " \n", |
| 1276 | " def to_json(self, **kwargs):\n", | 1298 | " def to_json(self, **kwargs):\n", |
| 1277 | " # super().get_data(**kwargs)\n", | ||
| 1278 | " self.get_data(**kwargs)\n", | 1299 | " self.get_data(**kwargs)\n", |
| 1279 | " prod_plans = self.extract_production_plans(**kwargs)\n", | 1300 | " prod_plans = self.extract_production_plans(**kwargs)\n", |
| 1280 | " return json.dumps(self.__do_some_magic(prod_plans), indent=kwargs.get(\"indent\", 2))\n", | 1301 | " return json.dumps(self.__do_some_magic(prod_plans), indent=kwargs.get(\"indent\", 2))\n", |
| @@ -1295,7 +1316,10 @@ | |||
| 1295 | "\n", | 1316 | "\n", |
| 1296 | "\n", | 1317 | "\n", |
| 1297 | "cool_ts_proprocessor = CoolTSPreprocessor()\n", | 1318 | "cool_ts_proprocessor = CoolTSPreprocessor()\n", |
| 1298 | "print(cool_ts_proprocessor.to_json(indent=4))" | 1319 | "print(cool_ts_proprocessor.to_json(indent=4))\n", |
| 1320 | "\n", | ||
| 1321 | "# but we want to test CoolTSPreprocessor without the annoying ConnectionManager.get_data method being used\n", | ||
| 1322 | "# simply (temporary or canditionally) replacing ConnectionManager with MockConnectionManager will go against the O in SOLID" | ||
| 1299 | ] | 1323 | ] |
| 1300 | }, | 1324 | }, |
| 1301 | { | 1325 | { |
| @@ -1311,6 +1335,7 @@ | |||
| 1311 | "Using mock data\n", | 1335 | "Using mock data\n", |
| 1312 | "Extracting production plans from data\n", | 1336 | "Extracting production plans from data\n", |
| 1313 | "some magic is happening here...\n", | 1337 | "some magic is happening here...\n", |
| 1338 | "{}\n", | ||
| 1314 | "(<class '__main__.MockedButStillCoolTSPreprocessor'>, <class '__main__.CoolTSPreprocessor'>, <class '__main__.MockConnectionManager'>, <class '__main__.ConnectionManager'>, <class 'object'>)\n" | 1339 | "(<class '__main__.MockedButStillCoolTSPreprocessor'>, <class '__main__.CoolTSPreprocessor'>, <class '__main__.MockConnectionManager'>, <class '__main__.ConnectionManager'>, <class 'object'>)\n" |
| 1315 | ] | 1340 | ] |
| 1316 | } | 1341 | } |
| @@ -1320,12 +1345,36 @@ | |||
| 1320 | " pass\n", | 1345 | " pass\n", |
| 1321 | "\n", | 1346 | "\n", |
| 1322 | "mocked_but_still_cool_ts_preprocessor = MockedButStillCoolTSPreprocessor()\n", | 1347 | "mocked_but_still_cool_ts_preprocessor = MockedButStillCoolTSPreprocessor()\n", |
| 1323 | "mocked_but_still_cool_ts_preprocessor.to_json(indent=4)\n", | 1348 | "print(mocked_but_still_cool_ts_preprocessor.to_json(indent=4))\n", |
| 1324 | "print(MockedButStillCoolTSPreprocessor.__mro__)" | 1349 | "print(MockedButStillCoolTSPreprocessor.__mro__)" |
| 1325 | ] | 1350 | ] |
| 1326 | }, | 1351 | }, |
| 1327 | { | 1352 | { |
| 1328 | "cell_type": "markdown", | 1353 | "cell_type": "markdown", |
| 1354 | "id": "738b9084-27b9-4837-a927-e2087267d483", | ||
| 1355 | "metadata": {}, | ||
| 1356 | "source": [ | ||
| 1357 | "# Quick tips and final words\n", | ||
| 1358 | "\n", | ||
| 1359 | "- Avoid the classical pitfall: \"I am solving simple problems by simple means (the way I am used to) and could probably use OO when solving more complex problems\"! Solving simple problems is the best way to get used to the OO way of thinking / designing code.\n", | ||
| 1360 | "\n", | ||
| 1361 | "- Take some time to analyze and design your solution and discuss it with other team members and more experienced programmers. Do not rely only on QA (merge requests)!\n", | ||
| 1362 | "\n", | ||
| 1363 | "- Learn to think in terms of objects, like in real world objects (f.i. Car, Animal, TimeSeries, ProductionPlan, DataSource...)!\n", | ||
| 1364 | "\n", | ||
| 1365 | "- Learn to think in terms of abstract classes (types): Classes that will only serve as a foundation to their child classes, but will themselves never be directly instanciated!\n", | ||
| 1366 | "\n", | ||
| 1367 | "- Do not create \"thin\" classes / objects that simply containin some relevant attributes, protected by corresponding properties! The entire functionality (business logic) of the object should be encapsulated there.\n", | ||
| 1368 | "\n", | ||
| 1369 | "- Always make sure that you are not violating some of the SOLID principles (and others described above)! Run \"mental tests\" to test your current design!\n", | ||
| 1370 | "\n", | ||
| 1371 | "- Consider composition (\"has-a\" relationship) before considering inhiritance (\"is-a\" relationship)!\n", | ||
| 1372 | "\n", | ||
| 1373 | "- Read and learn from free software projects written in Python (f.i. on github.com)" | ||
| 1374 | ] | ||
| 1375 | }, | ||
| 1376 | { | ||
| 1377 | "cell_type": "markdown", | ||
| 1329 | "id": "540de317-ba77-4f5c-97ba-f1c0bec071fb", | 1378 | "id": "540de317-ba77-4f5c-97ba-f1c0bec071fb", |
| 1330 | "metadata": {}, | 1379 | "metadata": {}, |
| 1331 | "source": [ | 1380 | "source": [ |
