From 6dcd1727eb9d5dbfcb8f6e9599ada10d33063bac Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Wed, 18 Jan 2023 21:12:31 +0100 Subject: Remove some old presentations --- notebooks/python/python_intro.ipynb | 2 +- notebooks/python/python_oo.ipynb | 107 ++++++++++++++++++++++++++---------- 2 files changed, 79 insertions(+), 30 deletions(-) (limited to 'notebooks') 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 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.8" + "version": "3.11.1" } }, "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 @@ "\"The Zen of Python\" (import this) is still relevant and should be followed :)\n", "\n", "\"Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.\" - PEP484\n", - "\n" + "\n", + "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!" ] }, { @@ -225,17 +226,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "car1.get_obj_info_str() = \"I am <__main__.Car object at 0x7f5630542810> with id 140008154736656 from with id 94904583728688\"\n", - "car2.get_obj_info_str() = \"I am <__main__.Car object at 0x7f5633ec8190> with id 140008215052688 from with id 94904583728688\"\n", - "car1.model = 'BMW', car1.reg_nr = 'EC76183', car1.extras = ['GPSnav', 'Sound system'], id(car1.cls_extras) = 140008154734272, id(car1.__init__) = 140008154735680\n", - "car2.model = 'Scoda', car2.reg_nr = 'BD77655', car2.extras = ['GPSnav'], id(car2.cls_extras) = 140008154734272, id(car2.__init__) = 140008154736768\n", - "id(Car.cls_extras) = 140008154734272, id(Car.__init__) = 140008154484864\n", - "car1.cls_extras = ['GPSnav', 'Sound system'], id(car1.cls_extras) = 140008154734272\n", - "car2.cls_extras = ['GPSnav', 'Sound system'], id(car2.cls_extras) = 140008154734272\n", + "car1.get_obj_info_str() = \"I am <__main__.Car object at 0x7fc7add96f10> with id 140495591927568 from with id 94628241379728\"\n", + "car2.get_obj_info_str() = \"I am <__main__.Car object at 0x7fc7aef459d0> with id 140495610468816 from with id 94628241379728\"\n", + "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", + "car2.model = 'Scoda', car2.reg_nr = 'BD77655', car2.extras = ['GPSnav'], id(car2.cls_extras) = 140495591909184, id(car2.get_obj_info_str) = 140495591927936\n", + "id(Car.cls_extras) = 140495591909184, id(Car.get_obj_info_str) = 140495591682944\n", + "car1.cls_extras = ['GPSnav', 'Sound system'], id(car1.cls_extras) = 140495591909184\n", + "car2.cls_extras = ['GPSnav', 'Sound system'], id(car2.cls_extras) = 140495591909184\n", "True\n", "hasattr(car1, 'import_tax_paid') = True\n", "hasattr(car2, 'import_tax_paid') = False\n", - "id(car1.__class__) = 94904583728688, id(car2.__class__) = 94904583728688, id(Car) = 94904583728688\n" + "id(car1.__class__) = 94628241379728, id(car2.__class__) = 94628241379728, id(Car) = 94628241379728\n" ] } ], @@ -244,6 +245,7 @@ "\n", " cls_extras = [] # class attribute - shared by all instances\n", " # cls_extras: list = [] is also possible\n", + " car_count = 0\n", "\n", " def __init__(self, model: str, reg_nr: str, extras: list):\n", " \"\"\"\n", @@ -262,24 +264,40 @@ "\n", " # self.cls_extras = extras # will create a *NEW instance attribute* called 'cls_extras'\n", " \n", + " Car.car_count += 1 # increase Car.car_count by 1 every time a car is created \n", + " \n", + " def __del__(self):\n", + " \"\"\"\n", + " Called when the instance is about to be destroyed.\n", + "\n", + " This is also called a finalizer or (improperly) a destructor.\n", + " \"\"\"\n", + " Car.car_count -= 1 # decrease by 1 every time a Car object is destroyed\n", + "\n", " def get_obj_info_str(self): # regular method that becomes instance attribute\n", " return f\"I am {self} with id {id(self)} from {self.__class__} with id {id(self.__class__)}\"\n", "\n", "car1 = Car(\"BMW\", \"EC76183\", [\"GPSnav\", \"Sound system\"])\n", "car2 = Car(\"Scoda\", \"BD77655\", [\"GPSnav\"])\n", "\n", - "print(f\"{car1.get_obj_info_str() = }\")\n", + "print(f\"{car1.get_obj_info_str() = }\") # same as calling Car.get_object_info_str(car1)\n", "print(f\"{car2.get_obj_info_str() = }\")\n", "\n", "# default behavior for `object.attr`:\n", "# getter:\n", "# - checks if 'attr' is an instance attribute\n", - "# - checks if 'attr' is a class attribute\n", + "# - checks if 'attr' is a class attribute (through the method resolution order - MRO)\n", "# - raises AttributeError\n", - "# setter: (re)defines an instance attribute\n", - "print(f\"{car1.model = }, {car1.reg_nr = }, {car1.extras = }, {id(car1.cls_extras) = }, {id(car1.__init__) = }\")\n", - "print(f\"{car2.model = }, {car2.reg_nr = }, {car2.extras = }, {id(car2.cls_extras) = }, {id(car2.__init__) = }\")\n", - "print(f\"{id(Car.cls_extras) = }, {id(Car.__init__) = }\")\n", + "# setter:\n", + "# - (re)defines an instance attribute\n", + "\n", + "# Details not covered in this course:\n", + "# Car.x is translated to Car.__dict__[\"x\"] (...through the MRO)\n", + "# car1.x is translated to car1.__dict__[\"x\"] (if not found ... Car.x (see above), if not found car1.__getattr__(\"x\") is called)\n", + "\n", + "print(f\"{car1.model = }, {car1.reg_nr = }, {car1.extras = }, {id(car1.cls_extras) = }, {id(car1.get_obj_info_str) = }\")\n", + "print(f\"{car2.model = }, {car2.reg_nr = }, {car2.extras = }, {id(car2.cls_extras) = }, {id(car2.get_obj_info_str) = }\")\n", + "print(f\"{id(Car.cls_extras) = }, {id(Car.get_obj_info_str) = }\")\n", "car1.cls_extras.extend(car1.extras)\n", "# car1.cls_extras = car1.extras # N.B. This will create a *NEW instance attribute* called 'cls_extras'\n", "print(f\"{car1.cls_extras = }, {id(car1.cls_extras) = }\")\n", @@ -289,7 +307,7 @@ "car1.import_tax_paid = True # 'import_tax_paid' will only be present in car1\n", "print(car1.import_tax_paid)\n", "print(f\"{hasattr(car1, 'import_tax_paid') = }\") # Out: True\n", - "print(f\"{hasattr(car2, 'import_tax_paid') = }\") # Out: False\n", + "print(f\"{hasattr(car2, 'import_tax_paid') = }\") # Out: False - no 'import_tax_paid' attribute in object car2\n", "\n", "# .__class__ instance attribute pointing to its class (type) will be created\n", "print(f\"{id(car1.__class__) = }, {id(car2.__class__) = }, {id(Car) = }\")" @@ -705,6 +723,7 @@ "name": "stdout", "output_type": "stream", "text": [ + "2:2\n", "__mul__ called\n", "vector * 3 = Vector(start=Point(x=2, y=2), end=Point(x=12, y=18))\n", "__rmul__ called\n", @@ -771,6 +790,7 @@ " # __rmul__ = __mul__\n", "\n", "vector = Vector(Point(2, 2), Point(4, 6))\n", + "print(f\"{vector['start']}\")\n", "print(f\"{vector * 3 = }\")\n", "print(f\"{3 * vector = }\")" ] @@ -863,7 +883,7 @@ "id": "d1d17f26-de6e-4510-b0f0-3ae02761ae24", "metadata": {}, "source": [ - "# Example 2\n", + "# Example 3\n", "\n", "We will create a small and incomplete Animal class hierarchy" ] @@ -973,7 +993,7 @@ " super().__init__(\n", " weight=weight, tooth_replacement=True, alive=alive, **kwargs\n", " )\n", - " self_conservation_status = conservation_status\n", + " self._conservation_status = conservation_status\n", "\n", "\n", "class Cow(Mammal):\n", @@ -1104,6 +1124,7 @@ " \"\"\"\n", "\n", "\n", + "# not good! The user of JSONMixin should not have to explicitly know about / use DictMixin\n", "class JSONConvertableTigerShark(TigerShark, DictMixin, JSONMixin):\n", " \"\"\"A tiger shark class that provides JSON conversion functionality\"\"\"\n", "\n", @@ -1143,14 +1164,7 @@ "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", "\n", "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", - "`TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y` is raised then.\n", - "\n", - "\n", - "## The Liskov substitution principle (LSP):\n", - "\n", - "Functions that use references to base class objects must be able to use objects of derived classes without knowing it.\n", - "\n", - "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" + "`TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y` is raised then." ] }, { @@ -1232,6 +1246,14 @@ "help(simeon)" ] }, + { + "cell_type": "markdown", + "id": "875e7ffd-4feb-4ef0-b912-0a939c6c2dd1", + "metadata": {}, + "source": [ + "... **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. :)" + ] + }, { "cell_type": "code", "execution_count": 12, @@ -1274,7 +1296,6 @@ " \"\"\"Produces JSON content for production plans\"\"\"\n", " \n", " def to_json(self, **kwargs):\n", - " # super().get_data(**kwargs)\n", " self.get_data(**kwargs)\n", " prod_plans = self.extract_production_plans(**kwargs)\n", " return json.dumps(self.__do_some_magic(prod_plans), indent=kwargs.get(\"indent\", 2))\n", @@ -1295,7 +1316,10 @@ "\n", "\n", "cool_ts_proprocessor = CoolTSPreprocessor()\n", - "print(cool_ts_proprocessor.to_json(indent=4))" + "print(cool_ts_proprocessor.to_json(indent=4))\n", + "\n", + "# but we want to test CoolTSPreprocessor without the annoying ConnectionManager.get_data method being used\n", + "# simply (temporary or canditionally) replacing ConnectionManager with MockConnectionManager will go against the O in SOLID" ] }, { @@ -1311,6 +1335,7 @@ "Using mock data\n", "Extracting production plans from data\n", "some magic is happening here...\n", + "{}\n", "(, , , , )\n" ] } @@ -1320,10 +1345,34 @@ " pass\n", "\n", "mocked_but_still_cool_ts_preprocessor = MockedButStillCoolTSPreprocessor()\n", - "mocked_but_still_cool_ts_preprocessor.to_json(indent=4)\n", + "print(mocked_but_still_cool_ts_preprocessor.to_json(indent=4))\n", "print(MockedButStillCoolTSPreprocessor.__mro__)" ] }, + { + "cell_type": "markdown", + "id": "738b9084-27b9-4837-a927-e2087267d483", + "metadata": {}, + "source": [ + "# Quick tips and final words\n", + "\n", + "- 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", + "\n", + "- 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", + "\n", + "- Learn to think in terms of objects, like in real world objects (f.i. Car, Animal, TimeSeries, ProductionPlan, DataSource...)!\n", + "\n", + "- 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", + "\n", + "- 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", + "\n", + "- 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", + "\n", + "- Consider composition (\"has-a\" relationship) before considering inhiritance (\"is-a\" relationship)!\n", + "\n", + "- Read and learn from free software projects written in Python (f.i. on github.com)" + ] + }, { "cell_type": "markdown", "id": "540de317-ba77-4f5c-97ba-f1c0bec071fb", -- cgit v1.3