summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimeon Simeonov2022-10-26 16:09:32 +0200
committerSimeon Simeonov2022-10-26 16:09:32 +0200
commite4c798c37d0ee961d44a1e8316617374e2053016 (patch)
tree3152a6ea1647a75235731023ff3ba26e17a9bff6
parent4290701502d378a7dcb3414d32fabe2b8e88edd0 (diff)
Add inheritance slides in python-OO
-rw-r--r--notebooks/python/python_oo.ipynb513
1 files changed, 485 insertions, 28 deletions
diff --git a/notebooks/python/python_oo.ipynb b/notebooks/python/python_oo.ipynb
index 7ce5c73..2440efb 100644
--- a/notebooks/python/python_oo.ipynb
+++ b/notebooks/python/python_oo.ipynb
@@ -33,6 +33,24 @@
33 }, 33 },
34 { 34 {
35 "cell_type": "markdown", 35 "cell_type": "markdown",
36 "id": "be3062ad-9437-4ba2-9975-c2837b0af9dc",
37 "metadata": {},
38 "source": [
39 "# The big picture - the course so far\n",
40 "\n",
41 "- Basics: About the language, the Python eco-system, types, modules, functions, scopes, decorators, string formatting\n",
42 "\n",
43 "- **Object-oriented programming in Python: How Python \"really works\"**\n",
44 "\n",
45 "- Control flow: if / for / while / try, iterators, \"tactical programming\" tips\n",
46 "\n",
47 "- A brief tour through Python's standard library\n",
48 "\n",
49 "- Code and application design and best practices: How to design your code\n"
50 ]
51 },
52 {
53 "cell_type": "markdown",
36 "id": "d6d3d0e7-c1d5-4119-940e-0f94ee7542ec", 54 "id": "d6d3d0e7-c1d5-4119-940e-0f94ee7542ec",
37 "metadata": {}, 55 "metadata": {},
38 "source": [ 56 "source": [
@@ -147,9 +165,35 @@
147 "Python has automatic memory management using reference counting. When an object no longer has\n", 165 "Python has automatic memory management using reference counting. When an object no longer has\n",
148 "any references, the garbage collector kicks inn and removes the object from memory.\n", 166 "any references, the garbage collector kicks inn and removes the object from memory.\n",
149 "\n", 167 "\n",
168 "Access modifiers in Python are not enforced by the classic 'public', 'protected' and 'private' keywords, but rather by convention.\n",
169 "\n",
150 "\"The Zen of Python\" (import this) is still relevant and should be followed :)\n", 170 "\"The Zen of Python\" (import this) is still relevant and should be followed :)\n",
151 "\n", 171 "\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" 172 "\"Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.\" - PEP484\n",
173 "\n"
174 ]
175 },
176 {
177 "cell_type": "markdown",
178 "id": "4a57a560-bd01-48dc-99b8-2af816f9c83b",
179 "metadata": {},
180 "source": [
181 "# Useful builtin functions\n",
182 "\n",
183 "\n",
184 "- `dir(obj)` - returns a list of valid attributes for that object\n",
185 "\n",
186 "- `help(object)` - invokes the built-in help system. This function is intended for interactive use.\n",
187 "\n",
188 "- `id(object)` - returns the \"identity\" (unique number) of the *object*\n",
189 "\n",
190 "- `isinstance(object, classinfo)` - returns `True` if the object argument is an instance of the classinfo argument, or a subclass thereof (**N.B** don't use `type(x) == type(y)`)\n",
191 "\n",
192 "- `issubclass(class, classinfo)` - returns `True` if class is a subclass of classinfo. A class is considered a subclass of itself.\n",
193 "\n",
194 "- `super([type[, object-or-type]])` - returns a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class. **More on that later**.\n",
195 "\n",
196 "- `type(object)` - returns the type of an object. Useful for debugging / analysis."
153 ] 197 ]
154 }, 198 },
155 { 199 {
@@ -164,7 +208,7 @@
164 }, 208 },
165 { 209 {
166 "cell_type": "code", 210 "cell_type": "code",
167 "execution_count": 154, 211 "execution_count": 287,
168 "id": "391e816e-c53b-454b-9e9f-3587234f1fea", 212 "id": "391e816e-c53b-454b-9e9f-3587234f1fea",
169 "metadata": {}, 213 "metadata": {},
170 "outputs": [ 214 "outputs": [
@@ -172,7 +216,9 @@
172 "name": "stdout", 216 "name": "stdout",
173 "output_type": "stream", 217 "output_type": "stream",
174 "text": [ 218 "text": [
175 "my_first_point = <Point(x=2, y=8)>\n", 219 "2:8\n",
220 "<Point(x=2, y=8)>\n",
221 "my_first_point = 2:8\n",
176 "my_first_point = <Point(x=2, y=8)>\n", 222 "my_first_point = <Point(x=2, y=8)>\n",
177 "2\n", 223 "2\n",
178 "2\n", 224 "2\n",
@@ -242,8 +288,10 @@
242 "\n", 288 "\n",
243 "my_first_point = Point(2, 8)\n", 289 "my_first_point = Point(2, 8)\n",
244 "\n", 290 "\n",
245 "print(f\"{my_first_point = }\")\n", 291 "print(f\"{my_first_point}\") # !s is implicit\n",
246 "print(f\"{my_first_point = !r}\")\n", 292 "print(f\"{my_first_point!r}\")\n",
293 "print(f\"{my_first_point = !s}\")\n",
294 "print(f\"{my_first_point = !r}\") # !r is implicit when using =\n",
247 "print(my_first_point.x)\n", 295 "print(my_first_point.x)\n",
248 "\n", 296 "\n",
249 "# we \"should not\" be accessing private and protected attributes directly\n", 297 "# we \"should not\" be accessing private and protected attributes directly\n",
@@ -258,7 +306,7 @@
258 }, 306 },
259 { 307 {
260 "cell_type": "code", 308 "cell_type": "code",
261 "execution_count": 155, 309 "execution_count": 288,
262 "id": "e82feefb-025d-4cbb-a8ac-ddc3cc01269c", 310 "id": "e82feefb-025d-4cbb-a8ac-ddc3cc01269c",
263 "metadata": {}, 311 "metadata": {},
264 "outputs": [ 312 "outputs": [
@@ -290,6 +338,7 @@
290 " return f\"<Vector (start={self._start!r}, end={self._end!r}>\"\n", 338 " return f\"<Vector (start={self._start!r}, end={self._end!r}>\"\n",
291 "\n", 339 "\n",
292 " def __str__(self) -> str:\n", 340 " def __str__(self) -> str:\n",
341 " print('str-called')\n",
293 " return f\"{self._start} -> {self._end}\"\n", 342 " return f\"{self._start} -> {self._end}\"\n",
294 "\n", 343 "\n",
295 " @property\n", 344 " @property\n",
@@ -323,11 +372,11 @@
323 "\n", 372 "\n",
324 "\n", 373 "\n",
325 "my_first_vector = Vector(Point(0, 0), Point(9, 12))\n", 374 "my_first_vector = Vector(Point(0, 0), Point(9, 12))\n",
326 "print(f\"{my_first_vector = !r}\")\n", 375 "print(f\"{my_first_vector = }\")\n",
327 "my_first_vector.end = Point(12, 12)\n", 376 "my_first_vector.end = Point(12, 12)\n",
328 "print(f\"{my_first_vector = !r}\")\n", 377 "print(f\"{my_first_vector = }\")\n",
329 "other_vector = Vector(Point(0, 0), Point(12, 12))\n", 378 "other_vector = Vector(Point(0, 0), Point(12, 12))\n",
330 "print(f\"{other_vector = !r}\")\n", 379 "print(f\"{other_vector = }\")\n",
331 "print(f\"{other_vector.length = }\")\n", 380 "print(f\"{other_vector.length = }\")\n",
332 "\n", 381 "\n",
333 "# the objects do not evaluate as \"alike\" because:\n", 382 "# the objects do not evaluate as \"alike\" because:\n",
@@ -340,7 +389,7 @@
340 }, 389 },
341 { 390 {
342 "cell_type": "code", 391 "cell_type": "code",
343 "execution_count": 156, 392 "execution_count": 289,
344 "id": "71a90ec6-3cc0-441e-a866-c2c2b9984559", 393 "id": "71a90ec6-3cc0-441e-a866-c2c2b9984559",
345 "metadata": {}, 394 "metadata": {},
346 "outputs": [ 395 "outputs": [
@@ -413,11 +462,11 @@
413 "\n", 462 "\n",
414 "\n", 463 "\n",
415 "my_first_vector = Vector(Point(0, 0), Point(9, 12))\n", 464 "my_first_vector = Vector(Point(0, 0), Point(9, 12))\n",
416 "print(f\"{my_first_vector = !r}\")\n", 465 "print(f\"{my_first_vector = }\")\n",
417 "my_first_vector.end = Point(12, 12)\n", 466 "my_first_vector.end = Point(12, 12)\n",
418 "print(f\"{my_first_vector = !r}\")\n", 467 "print(f\"{my_first_vector = }\")\n",
419 "other_vector = Vector(Point(0, 0), Point(12, 12))\n", 468 "other_vector = Vector(Point(0, 0), Point(12, 12))\n",
420 "print(f\"{other_vector = !r}\")\n", 469 "print(f\"{other_vector = }\")\n",
421 "print(f\"{other_vector.length = }\")\n", 470 "print(f\"{other_vector.length = }\")\n",
422 "\n", 471 "\n",
423 "print(Point(1, 2) == Point(1, 2))\n", 472 "print(Point(1, 2) == Point(1, 2))\n",
@@ -454,7 +503,7 @@
454 }, 503 },
455 { 504 {
456 "cell_type": "code", 505 "cell_type": "code",
457 "execution_count": 157, 506 "execution_count": 290,
458 "id": "a4d2735d-d167-4f43-ad55-cb3d13ece2dc", 507 "id": "a4d2735d-d167-4f43-ad55-cb3d13ece2dc",
459 "metadata": {}, 508 "metadata": {},
460 "outputs": [ 509 "outputs": [
@@ -497,7 +546,7 @@
497 }, 546 },
498 { 547 {
499 "cell_type": "code", 548 "cell_type": "code",
500 "execution_count": 158, 549 "execution_count": 291,
501 "id": "a6ead5e6-e987-4bda-bf48-30f91877278d", 550 "id": "a6ead5e6-e987-4bda-bf48-30f91877278d",
502 "metadata": {}, 551 "metadata": {},
503 "outputs": [ 552 "outputs": [
@@ -556,7 +605,7 @@
556 }, 605 },
557 { 606 {
558 "cell_type": "code", 607 "cell_type": "code",
559 "execution_count": 159, 608 "execution_count": 292,
560 "id": "11f6a3fb-64d7-40a9-a130-27548ec4b802", 609 "id": "11f6a3fb-64d7-40a9-a130-27548ec4b802",
561 "metadata": {}, 610 "metadata": {},
562 "outputs": [ 611 "outputs": [
@@ -634,19 +683,426 @@
634 }, 683 },
635 { 684 {
636 "cell_type": "code", 685 "cell_type": "code",
637 "execution_count": null, 686 "execution_count": 293,
638 "id": "c345a69f-02da-40e7-bb37-c0511b6af096", 687 "id": "c345a69f-02da-40e7-bb37-c0511b6af096",
639 "metadata": {}, 688 "metadata": {},
640 "outputs": [], 689 "outputs": [
690 {
691 "name": "stdout",
692 "output_type": "stream",
693 "text": [
694 "9\n",
695 "Vector.from_str('1:1:5:6') = <Vector (start=<Point(x=1, y=1)>, end=<Point(x=5, y=6)>>\n"
696 ]
697 }
698 ],
641 "source": [ 699 "source": [
642 "# Class methods and static methods\n", 700 "# Class methods and static methods\n",
643 "\n", 701 "\n",
702 "class Point(Point):\n",
703 "\n",
704 " @staticmethod\n",
705 " def get_manhattan_distance(point1: Point, point2: Point) -> int:\n",
706 " \"\"\"Returns the Manhattan distance of two points\"\"\"\n",
707 " return abs(point1.x - point2.x) + abs(point1.y - point2.y)\n",
708 "\n",
709 "\n",
644 "class Vector(Vector):\n", 710 "class Vector(Vector):\n",
645 " \n", 711 "\n",
646 " @classmethod\n", 712 " @classmethod\n",
647 " def from_str(cls, obj_str: str) -> cls:\n", 713 " def from_str(cls, obj_str: str):\n",
714 " \"\"\"\n",
715 " Creates an object from a string with the following format:\n",
716 " <startx>:<starty>:<endx>:<endy>\n",
648 " \"\"\"\n", 717 " \"\"\"\n",
649 " " 718 " coordinates = [int(coordinate_str) for coordinate_str in obj_str.split(\":\")]\n",
719 " return cls(\n",
720 " Point(coordinates[0], coordinates[1]),\n",
721 " Point(coordinates[2], coordinates[3]),\n",
722 " )\n",
723 "\n",
724 "print(Point.get_manhattan_distance(Point(1, 1), Point(5, 6)))\n",
725 "print(f\"{Vector.from_str('1:1:5:6') = }\")"
726 ]
727 },
728 {
729 "cell_type": "markdown",
730 "id": "61c1e910-9398-455d-b0c2-cda6c6870224",
731 "metadata": {},
732 "source": [
733 "# Inheritance in Python\n",
734 "\n",
735 "In object-oriented programming, inheritance is the mechanism of basing an object or class upon another object (prototype-based inheritance) or class (class-based inheritance), retaining similar implementation. Also defined as deriving new classes (sub classes) from existing ones such as super class or base class and then forming them into a hierarchy of classes.\n",
736 "\n",
737 "In most class-based object-oriented languages, an object created through inheritance, a \"child object\", acquires all the properties and behaviors of the \"parent object\" , with the exception of: constructors, destructor, overloaded operators and friend functions of the base class. \n",
738 "Inheritance allows programmers to create classes that are built upon existing classes, to specify a new implementation while maintaining the same behaviors (realizing an interface), to reuse code and to independently extend original software via public classes and interfaces.\n",
739 "\n",
740 "One can simply view inheritance as a tool for code reuse.\n",
741 "\n",
742 "Inheritance is not the only mechanism for extending classes and functionality in general."
743 ]
744 },
745 {
746 "cell_type": "markdown",
747 "id": "eac035eb-7757-485a-be6c-c450bf9ee748",
748 "metadata": {},
749 "source": [
750 "# The `super([type[, object-or-type])` function\n",
751 "\n",
752 "A common misconception is that `super()` returns an object of the parent class.\n",
753 "\n",
754 "There are two typical use cases for `super()`:\n",
755 "\n",
756 "In a class hierarchy with **single inheritance**, `super()` can be used to refer to parent classes without naming them explicitly, thus making the code more maintainable.\n",
757 "This use closely parallels the use of super in other programming languages.\n",
758 "\n",
759 "The second use case is to support cooperative multiple inheritance in a dynamic execution environment. \n",
760 "This use case is unique to Python and is not found in statically compiled languages or languages that only support single inheritance. \n",
761 "This makes it possible to implement \"diamond diagrams\" where multiple base classes implement the same method. \n",
762 "Good design dictates that such implementations have the same calling signature in every case (because the order of calls is determined at runtime, because that order adapts to changes in the class hierarchy, and because that order can include sibling classes that are unknown prior to runtime)."
763 ]
764 },
765 {
766 "cell_type": "markdown",
767 "id": "d1d17f26-de6e-4510-b0f0-3ae02761ae24",
768 "metadata": {},
769 "source": [
770 "# Example 2\n",
771 "\n",
772 "We will create a small and incomplete Animal class hierarchy"
773 ]
774 },
775 {
776 "cell_type": "code",
777 "execution_count": 294,
778 "id": "44d47e1c-1a06-4577-8db6-6ec78ce5cef8",
779 "metadata": {},
780 "outputs": [
781 {
782 "name": "stdout",
783 "output_type": "stream",
784 "text": [
785 "Moo\n",
786 "Help on class TigerShark in module __main__:\n",
787 "\n",
788 "class TigerShark(Fish)\n",
789 " | TigerShark(weight: int, alive: bool = True, **kwargs)\n",
790 " | \n",
791 " | Base class for all tiger sharks\n",
792 " | \n",
793 " | Method resolution order:\n",
794 " | TigerShark\n",
795 " | Fish\n",
796 " | Animal\n",
797 " | builtins.object\n",
798 " | \n",
799 " | Methods defined here:\n",
800 " | \n",
801 " | __init__(self, weight: int, alive: bool = True, **kwargs)\n",
802 " | Initialize self. See help(type(self)) for accurate signature.\n",
803 " | \n",
804 " | ----------------------------------------------------------------------\n",
805 " | Data descriptors inherited from Animal:\n",
806 " | \n",
807 " | __dict__\n",
808 " | dictionary for instance variables (if defined)\n",
809 " | \n",
810 " | __weakref__\n",
811 " | list of weak references to the object (if defined)\n",
812 " | \n",
813 " | alive\n",
814 " | getter property alive\n",
815 " | \n",
816 " | weight\n",
817 " | getter property weight\n",
818 "\n"
819 ]
820 }
821 ],
822 "source": [
823 "# This example demonstrates a simple and incomplete animal class hierarchy\n",
824 "\n",
825 "# N.B. The use of super() in this example is not optimal\n",
826 "\n",
827 "\n",
828 "class Animal:\n",
829 " \"\"\"Base class for all animals, inheriting only from 'builtins.object'\"\"\"\n",
830 "\n",
831 " def __init__(self, weight: int, alive: bool = True, **kwargs):\n",
832 " self._weight = weight\n",
833 " self._alive = alive\n",
834 "\n",
835 " @property\n",
836 " def weight(self) -> int:\n",
837 " \"\"\"getter property weight\"\"\"\n",
838 " return self._weight\n",
839 "\n",
840 " @weight.setter\n",
841 " def weight(self, value: int):\n",
842 " \"\"\"setter property weight\"\"\"\n",
843 " self._weight = value\n",
844 "\n",
845 " @property\n",
846 " def alive(self) -> bool:\n",
847 " \"\"\"getter property alive\"\"\"\n",
848 " return self._alive\n",
849 "\n",
850 " @alive.setter\n",
851 " def alive(self, value: bool):\n",
852 " \"\"\"setter property alive\"\"\"\n",
853 " self._alive = value\n",
854 "\n",
855 "\n",
856 "class Mammal(Animal):\n",
857 " \"\"\"Base class for all mammals\"\"\"\n",
858 "\n",
859 " def __init__(self, weight: int, tooth_replacement: bool, alive: bool = True, **kwargs):\n",
860 " super().__init__(weight, alive, **kwargs)\n",
861 " self._tooth_replacement = tooth_replacement\n",
862 "\n",
863 "\n",
864 "class Fish(Animal):\n",
865 " \"\"\"Base class for all fish\"\"\"\n",
866 "\n",
867 " def __init__(self, weight: int, gill_openings: int, alive: bool = True, **kwargs):\n",
868 " super().__init__(weight, alive, **kwargs)\n",
869 " self._gill_openings = gill_openings\n",
870 "\n",
871 "\n",
872 "class Tiger(Mammal):\n",
873 " \"\"\"Base class for all tigers\"\"\"\n",
874 "\n",
875 " def __init__(self, weight: int, conservation_status: str, alive: bool = True, **kwargs):\n",
876 " super().__init__(\n",
877 " weight=weight, tooth_replacement=True, alive=alive, **kwargs\n",
878 " )\n",
879 " self_conservation_status = conservation_status\n",
880 "\n",
881 "\n",
882 "class Cow(Mammal):\n",
883 " \"\"\"Base class for all cows\"\"\"\n",
884 "\n",
885 " def __init__(self, weight: int, alive: bool = True, **kwargs):\n",
886 " super().__init__(\n",
887 " weight=weight, tooth_replacement=True, alive=alive, **kwargs\n",
888 " )\n",
889 "\n",
890 " def make_a_sound(self):\n",
891 " if self._alive:\n",
892 " print(\"Moo\" if self._weight > 60 else \"Mee\")\n",
893 "\n",
894 "\n",
895 "class TigerShark(Fish):\n",
896 " \"\"\"Base class for all tiger sharks\"\"\"\n",
897 "\n",
898 " def __init__(self, weight: int, alive: bool = True, **kwargs):\n",
899 " super().__init__(weight=weight, gill_openings=3, alive=alive, **kwargs)\n",
900 "\n",
901 "tiger1 = Tiger(200, \"Endangered\")\n",
902 "tiger2 = Tiger(183, \"Critically endangered\")\n",
903 "cow1 = Cow(350)\n",
904 "cow1.make_a_sound()\n",
905 "tiger_shark1 = TigerShark(80)\n",
906 "help(TigerShark)"
907 ]
908 },
909 {
910 "cell_type": "markdown",
911 "id": "cc950a82-40a0-4bdb-89a0-ea6e2ebe5861",
912 "metadata": {},
913 "source": [
914 "# Challenges\n",
915 "\n",
916 "At least two obvious problems arise:\n",
917 "\n",
918 "- how do we handle the growing amount of params we have to send to `super()`?\n",
919 "\n",
920 "- how do insert functionality that does not necessarily follow the structure of our hierarchy. F.i predator functionality?\n",
921 "\n",
922 "\n",
923 "## Possible solutions\n",
924 "\n",
925 "- use `**kwargs` to collect and transmit even 'unwanted' params\n",
926 "\n",
927 "- use multiple inheritance and mixins\n",
928 "\n",
929 "\n",
930 "## Mixins\n",
931 "\n",
932 "A mixin is a class that provides method implementations for reuse by multiple related child classes. However, the inheritance is not implying an is-a relationship.\n",
933 "\n",
934 "A mixin doesn't define a new type. Therefore, it is not intended for direction instantiation.\n",
935 "\n",
936 "A mixin bundles a set of methods for reuse. Each mixin should have a single specific behavior, implementing closely related methods.\n",
937 "\n",
938 "Typically, a child class uses multiple inheritance to combine the mixin classes with a parent class.\n",
939 "\n",
940 "Since Python doesn’t define a formal way to define mixin classes, it’s a good practice to name mixin classes with the suffix *Mixin*.\n",
941 "\n",
942 "Usually, a mixin class should not have any attributes that overlap with the established hierarchy.\n",
943 "\n",
944 "A mixin class is like an interface in Java, hence the phrase 'implements X functionality' should fit in the mixin description.\n"
945 ]
946 },
947 {
948 "cell_type": "markdown",
949 "id": "35b3a8eb-e7b0-4181-822f-923a74a7ab27",
950 "metadata": {},
951 "source": [
952 "# Example 3\n",
953 "\n",
954 "Demonstrates the use of mixins."
955 ]
956 },
957 {
958 "cell_type": "code",
959 "execution_count": 295,
960 "id": "e6076925-75f0-456c-aed5-7db0a24a67a1",
961 "metadata": {},
962 "outputs": [
963 {
964 "name": "stdout",
965 "output_type": "stream",
966 "text": [
967 "{'_weight': 80, '_alive': True, '_gill_openings': 3}\n",
968 "{\"_weight\": 80, \"_alive\": true, \"_gill_openings\": 3}\n",
969 "{\"_weight\": 80, \"_alive\": true, \"_gill_openings\": 3}\n"
970 ]
971 }
972 ],
973 "source": [
974 "# Multiple inheritance and mixins\n",
975 "import json\n",
976 "\n",
977 "class DictMixin:\n",
978 " \"\"\"Implements a dictionary conversion functionality\"\"\"\n",
979 "\n",
980 " def to_dict(self):\n",
981 " return self._traverse_dict(self.__dict__)\n",
982 "\n",
983 " def _traverse_dict(self, attributes):\n",
984 " result = {}\n",
985 " for key, value in attributes.items():\n",
986 " result[key] = self._traverse(key, value)\n",
987 " return result\n",
988 "\n",
989 " def _traverse(self, key, value):\n",
990 " if isinstance(value, DictMixin):\n",
991 " return value.to_dict()\n",
992 " elif isinstance(value, dict):\n",
993 " return self._traverse_dict(value)\n",
994 " elif isinstance(value, list):\n",
995 " return [self._traverse(key, v) for v in value]\n",
996 " elif hasattr(value, '__dict__'):\n",
997 " return self._traverse_dict(value.__dict__)\n",
998 " else:\n",
999 " return value\n",
1000 "\n",
1001 "\n",
1002 "class JSONMixin:\n",
1003 " \"\"\"Implements a JSON conversion functionality\"\"\"\n",
1004 "\n",
1005 " def to_json(self):\n",
1006 " return json.dumps(self._traverse_dict(self.__dict__))\n",
1007 "\n",
1008 "\n",
1009 "class AnotherJSONMixin(DictMixin, JSONMixin):\n",
1010 " \"\"\"Another JSON mixin that implements JSON conversion\"\"\"\n",
1011 "\n",
1012 "\n",
1013 "class DictableTigerShark(TigerShark, DictMixin):\n",
1014 " \"\"\"\n",
1015 " A tiger shark class that provides a dictionary conversion functionality\n",
1016 " \"\"\"\n",
1017 "\n",
1018 "\n",
1019 "class JSONConvertableTigerShark(TigerShark, DictMixin, JSONMixin):\n",
1020 " \"\"\"A tiger shark class that provides JSON conversion functionality\"\"\"\n",
1021 "\n",
1022 "\n",
1023 "class AnotherJSONConvertableTigerShark(TigerShark, AnotherJSONMixin):\n",
1024 " \"\"\"\n",
1025 " Another tiger shark class that provides JSON conversion functionality\n",
1026 " \"\"\"\n",
1027 "\n",
1028 "\n",
1029 "dict_tiger_shark = DictableTigerShark(80)\n",
1030 "json_tiger_shark = JSONConvertableTigerShark(80)\n",
1031 "another_json_tiger_shark = AnotherJSONConvertableTigerShark(80)\n",
1032 "print(f\"{dict_tiger_shark.to_dict()}\")\n",
1033 "print(f\"{json_tiger_shark.to_json()}\")\n",
1034 "print(f\"{another_json_tiger_shark.to_json()}\")"
1035 ]
1036 },
1037 {
1038 "cell_type": "markdown",
1039 "id": "7624fb8e-f1dd-4ad6-a1c2-f01cca6b1f2a",
1040 "metadata": {},
1041 "source": [
1042 "# Multiple inheritance and `super`\n",
1043 "\n",
1044 "A common misconception is that `super()` returns an object of the parent class. (repeated)\n",
1045 "\n",
1046 "Whenever you use `super()` you **can not know in advance** where / what it is going to call.\n",
1047 "\n",
1048 "In other words, you don't get to choose who your `super()` is going to call, your children get to choose. \n",
1049 "Single inheritance is just a special case and simplification of the general rule.\n",
1050 "\n",
1051 "Python>=2.3 uses the [C3 Method Resolution Order (MRO)](https://www.python.org/download/releases/2.3/mro/), replacing the old *depth first and then left to right* in Python<=2.2."
1052 ]
1053 },
1054 {
1055 "cell_type": "code",
1056 "execution_count": 296,
1057 "id": "cf79617a-95d2-48f2-be66-fa1fa34e4e04",
1058 "metadata": {},
1059 "outputs": [
1060 {
1061 "name": "stdout",
1062 "output_type": "stream",
1063 "text": [
1064 "Simeon's MRO: (<class '__main__.Simeon'>, <class '__main__.Mother'>, <class '__main__.Grandmother1'>, <class '__main__.Grandfather1'>, <class '__main__.Father'>, <class '__main__.Grandmother2'>, <class '__main__.Grandfather2'>, <class '__main__.Adam'>, <class '__main__.Eve'>, <class 'object'>)\n"
1065 ]
1066 }
1067 ],
1068 "source": [
1069 "class Adam:\n",
1070 " pass\n",
1071 "\n",
1072 "\n",
1073 "class Eve:\n",
1074 " pass\n",
1075 "\n",
1076 "\n",
1077 "class Grandmother1(Adam, Eve):\n",
1078 " pass\n",
1079 "\n",
1080 "\n",
1081 "class Grandmother2(Adam, Eve):\n",
1082 " pass\n",
1083 "\n",
1084 "\n",
1085 "class Grandfather1(Adam, Eve):\n",
1086 " pass\n",
1087 "\n",
1088 "\n",
1089 "class Grandfather2(Adam, Eve):\n",
1090 " pass\n",
1091 "\n",
1092 "\n",
1093 "class Mother(Grandmother1, Grandfather1):\n",
1094 " pass\n",
1095 "\n",
1096 "\n",
1097 "class Father(Grandmother2, Grandfather2):\n",
1098 " pass\n",
1099 "\n",
1100 "\n",
1101 "class Simeon(Mother, Father):\n",
1102 " pass\n",
1103 "\n",
1104 "\n",
1105 "print(f\"Simeon's MRO: {Simeon.__mro__}\")"
650 ] 1106 ]
651 }, 1107 },
652 { 1108 {
@@ -656,18 +1112,19 @@
656 "source": [ 1112 "source": [
657 "# Additional reading\n", 1113 "# Additional reading\n",
658 "\n", 1114 "\n",
659 "- [Descriptors](https://docs.python.org/3/reference/datamodel.html#implementing-descriptors)\n", 1115 "- Preferably everything in [Data model - https://docs.python.org/3/reference/datamodel.html](https://docs.python.org/3/reference/datamodel.html)\n",
660 "\n",
661 "- [Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)\n",
662 "\n", 1116 "\n",
663 "- `__call__`\n", 1117 " - [Descriptors](https://docs.python.org/3/reference/datamodel.html#implementing-descriptors)\n",
664 "\n", 1118 "\n",
665 "- `__getattr__`\n", 1119 " - [Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)\n",
666 "\n", 1120 "\n",
667 "- `__hash__`\n", 1121 " - `__call__`\n",
668 "\n", 1122 "\n",
1123 " - `__getattr__`\n",
669 "\n", 1124 "\n",
670 "... preferably everything in [Data model - https://docs.python.org/3/reference/datamodel.html](https://docs.python.org/3/reference/datamodel.html)" 1125 " - `__hash__`\n",
1126 " \n",
1127 " - ...\n"
671 ] 1128 ]
672 } 1129 }
673 ], 1130 ],