From 12660671f12b35dff8104d202708dd4fb8cbab97 Mon Sep 17 00:00:00 2001 From: Auke Klazema <klazema@astron.nl> Date: Thu, 19 May 2022 11:33:19 +0200 Subject: [PATCH] Extend multiple inheritance section --- PythonProgrammingBeyondTheBasics.ipynb | 277 +++++++++++++++++++++---- 1 file changed, 236 insertions(+), 41 deletions(-) diff --git a/PythonProgrammingBeyondTheBasics.ipynb b/PythonProgrammingBeyondTheBasics.ipynb index 69847a7..c958339 100644 --- a/PythonProgrammingBeyondTheBasics.ipynb +++ b/PythonProgrammingBeyondTheBasics.ipynb @@ -7,6 +7,12 @@ "source": [ "# Python programming beyond the basics\n", "\n", + "Author: Auke Klazema (klazema@astron.nl) \n", + "Length: 2.5 days \n", + "License: Creative Commons - BY (CC-BY)\n", + "\n", + "## Introduction\n", + "\n", "In \"Intro Programming in Python\" the minimal basics of programming were introduced. This course goes beyond those basics and will introduce more abstractions that can be useful in describing your computations in the most clear way to the reader of your code. The programming language and the abstractions it provides are not designed for computers, but for humans. The only thing the computer wants are numbers that represent instructions that it knows how to perform. Its the reader of the code that finds the numbers hard to reason about. It needs the abstractions to be able to deal with the complexity of the problems solved with computations." ] }, @@ -2119,7 +2125,7 @@ "source": [ "### Multiple inheritance\n", "\n", - "Python allows us to inherent from more than one class at once. This can lead to some nice abstraction options but also to potential issues you need to be aware of." + "Python allows us to inherent from more than one class at once. This can lead to some nice abstraction options, but also to potential issues you need to be aware of." ] }, { @@ -2216,7 +2222,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 7, "id": "7d6728b4-bdac-4f11-85b6-9aa28256767f", "metadata": {}, "outputs": [ @@ -2263,6 +2269,146 @@ "print(issubclass(D, A))" ] }, + { + "cell_type": "markdown", + "id": "6052ec69-ebed-464f-8d47-86914358f0ed", + "metadata": {}, + "source": [ + "#### Multiple inheritance with classes that need parameters.\n", + "\n", + "* https://stackoverflow.com/questions/34884567/python-multiple-inheritance-passing-arguments-to-constructors-using-super\n", + "* https://rhettinger.wordpress.com/2011/05/26/super-considered-super/\n", + "\n", + "With multiple inheritance where we need to initialize some or all of our objects we run in to some issues. We could call the super with the needed Class and self. But there is also another solution using keyword arguments but all Classes used in the multiple inheritance need to be designed like this. So multiple inheritance is difficult to do correctly. If all the initialize methods take no arguments we are lucky. But we are likely not always lucky. There are many languages that disallow multiple inheritance because its hard to get it right." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7d92ccf4-d510-46d7-9ede-ba3bc432de27", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Init A\n", + "Init B\n", + "Init A\n", + "Init C\n", + "Init D\n", + "A\n", + "B\n", + "C\n" + ] + } + ], + "source": [ + "# This setup works but in a so called diamond shape hierarchy you would see that class on top gets initialized twice.\n", + "# This might be a problem or it might not but that is hard to determine so this setup is discuraged.\n", + "class A:\n", + " def __init__(self, a, b):\n", + " self.a = a\n", + " self.b = b\n", + " print(\"Init A\")\n", + "\n", + "class B(A):\n", + " def __init__(self, a, b):\n", + " A.__init__(self, a, b)\n", + " self.a = a\n", + " self.b = b\n", + " print(\"Init B\")\n", + "\n", + "class C(A):\n", + " def __init__(self, a, b, c):\n", + " A.__init__(self, a, b)\n", + " self.a = a\n", + " self.b = b\n", + " self.c = c\n", + " print(\"Init C\")\n", + "\n", + "class D(B, C):\n", + " def __init__(self, a, b, c):\n", + " B.__init__(self, a, b)\n", + " C.__init__(self, a, b, c)\n", + " print(\"Init D\")\n", + "\n", + "dee = D(\"A\", \"B\", \"C\")\n", + "print(dee.a)\n", + "print(dee.b)\n", + "print(dee.c)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "feb7f063-9cc7-4eb3-a745-7f80e1c16355", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Init A\n", + "Init C\n", + "{'a': 'A'}\n", + "Init B\n", + "{'a': 'A', 'c': 'C'}\n", + "Init D\n", + "{'a': 'A', 'b': 'B', 'c': 'C'}\n", + "A\n", + "B\n", + "C\n" + ] + } + ], + "source": [ + "# We now have a diamond setup but Init A is called only once by using super.\n", + "# In order to pull this off we need to have full control over the chain.\n", + "# We also need to make sure we don't reuse argument names.\n", + "# It can also be solved with positional arguments but then you need to\n", + "# in full control of the order of every thing and not have a diamond setup.\n", + "class A:\n", + " def __init__(self, a, **kwargs):\n", + " super().__init__(**kwargs)\n", + " self.a = a\n", + " print(\"Init A\")\n", + "\n", + "class B(A):\n", + " def __init__(self, b, **kwargs):\n", + " super().__init__(**kwargs)\n", + " self.b = b\n", + " print(\"Init B\")\n", + " print(kwargs)\n", + "\n", + "class C(A):\n", + " def __init__(self, c, **kwargs):\n", + " super().__init__(**kwargs)\n", + " self.c = c\n", + " print(\"Init C\")\n", + " print(kwargs)\n", + "\n", + "class D(B, C):\n", + " def __init__(self, d, **kwargs):\n", + " super().__init__(**kwargs)\n", + " self.d = d\n", + " print(\"Init D\")\n", + " print(kwargs)\n", + "\n", + "dee = D(a=\"A\", b=\"B\", c=\"C\", d=\"D\")\n", + "print(dee.a)\n", + "print(dee.b)\n", + "print(dee.c)" + ] + }, + { + "cell_type": "markdown", + "id": "8f275c0d-3623-4d83-a6ec-e584c9eecacd", + "metadata": {}, + "source": [ + "In the article \"super considered super\" linked above there is also an example how to incorporate non-cooperative classes if its needed. It uses an adapter pattern. Please study it if you have a need for multiple inheritance, but verify you have implemented it correctly, because its easy to do it wrong. " + ] + }, { "cell_type": "markdown", "id": "d847f22e-8ec7-4bee-becb-16b2868dfa12", @@ -2275,8 +2421,8 @@ }, { "cell_type": "code", - "execution_count": 78, - "id": "8db10f77-b3fb-4e1e-9dda-c6863a91ab01", + "execution_count": 1, + "id": "1076c754-dfd3-489c-9df0-ae77d6b5eb73", "metadata": {}, "outputs": [ { @@ -2295,7 +2441,7 @@ "['Bla bla']" ] }, - "execution_count": 78, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } @@ -2329,38 +2475,7 @@ "\n", "cat.speak()\n", "cat.tell_time()\n", - "dog.speak()\n", - "\n", - "class Notebook(object):\n", - " def __init__(self):\n", - " self.notes = []\n", - " \n", - "class NotebookMixin(object):\n", - " def __init__(self):\n", - " self._notebook = Notebook()\n", - " \n", - " def take_note(self, note):\n", - " self._notebook.notes.append(note)\n", - " \n", - " def notes(self):\n", - " return self._notebook.notes\n", - " \n", - "class Person(object):\n", - " def __init__(self, name, age):\n", - " self.name = name\n", - " self.age = age\n", - " \n", - "class Journalist(NotebookMixin, Person):\n", - " def __init__(self, name, age):\n", - " NotebookMixin.__init__(self)\n", - " Person.__init__(self, name, age)\n", - " \n", - "journalist = Journalist(\"Henk\", 30)\n", - "\n", - "journalist.notes()\n", - "print(journalist.age)\n", - "journalist.take_note(\"Bla bla\")\n", - "journalist.notes()" + "dog.speak()" ] }, { @@ -2860,10 +2975,33 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "4a03469a-d281-491b-9fb9-0bc898fe0c88", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "test_anwser_start_with_zero (__main__.TestCalculator) ... ok\n", + "\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.001s\n", + "\n", + "OK\n" + ] + }, + { + "data": { + "text/plain": [ + "<unittest.main.TestProgram at 0x7fbd3063a040>" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import unittest\n", "\n", @@ -2928,7 +3066,7 @@ " self.assertEqual(0, self.calc.answer)\n", "\n", "# To run unittests inside a Jupyter notebook we call main differently.\n", - "unittest.main(argv=[''], verbosity=3, exit=False)\n" + "unittest.main(argv=[''], verbosity=3, exit=False)" ] }, { @@ -2981,7 +3119,7 @@ "\n", "bug.calc(4)\n", "bug.calc(10)\n", - "#bug.calc(20)\n", + "# bug.calc(20)\n", "\n", "pdb.runcall(bug.calc, 20)\n", "\n", @@ -3082,6 +3220,63 @@ "func_2(1, 7, 8)\n", "func_2(1, 7, arg3=10)" ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9e2e5752-a34a-4abf-ab0c-85fe61702d6f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "30\n" + ] + }, + { + "data": { + "text/plain": [ + "['Bla bla']" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "class Notebook(object):\n", + " def __init__(self):\n", + " self.notes = []\n", + " \n", + "class NotebookMixin(object):\n", + " def __init__(self, *args, **kwargs):\n", + " self._notebook = Notebook()\n", + " super().__init__(*args, **kwargs)\n", + " \n", + " def take_note(self, note):\n", + " self._notebook.notes.append(note)\n", + " \n", + " def notes(self):\n", + " return self._notebook.notes\n", + " \n", + "class Person(object):\n", + " def __init__(self, name, age):\n", + " self.name = name\n", + " self.age = age\n", + " \n", + "class Journalist(NotebookMixin, Person):\n", + " def __init__(self, name, age):\n", + " super().__init__(name, age)\n", + " \n", + "journalist = Journalist(\"Henk\", 30)\n", + "\n", + "journalist.notes()\n", + "print(journalist.age)\n", + "journalist.take_note(\"Bla bla\")\n", + "journalist.notes()" + ] } ], "metadata": { -- GitLab