diff --git a/13_object_oriented_programming_advanced.ipynb b/13_object_oriented_programming_advanced.ipynb index 91842885..a498eb09 100644 --- a/13_object_oriented_programming_advanced.ipynb +++ b/13_object_oriented_programming_advanced.ipynb @@ -23,6 +23,7 @@ " - [`super()`](#super())\n", " - [Quiz on Inheritance](#Quiz-on-Inheritance)\n", " - [Exercise: Triathlete](#Exercise:-Triathlete)\n", + " - [The `__new__` method](#The-__new__-method)\n", " - [Abstract Classes](#Abstract-Classes)\n", " - [Quiz on Abstraction](#Quiz-on-Abstraction)\n", " - [Exercise: Banking System](#Exercise:-Banking-System)\n", @@ -554,6 +555,66 @@ "cell_type": "markdown", "id": "39", "metadata": {}, + "source": [ + "## The `__new__` method\n", + "\n", + "Let's also take a look at the `__new__` method, as explained [here](https://www.pythontutorial.net/python-oop/python-__new__/).\n", + "A more technical explanation is given in the [official documentation](https://docs.python.org/3/reference/datamodel.html#object.__new__).\n", + "\n", + "`__new__` is a static method of a class.\n", + "When you create a new instance of a class, Python actually calls `__new__` first, to create the object, and then calls `__init__` to initialize the object's attributes.\n", + "Override the `__new__` method if you want to tweak the object at creation time.\n", + "\n", + "The signature of `__new__` is:\n", + "```python\n", + "def __new__(cls, *args, **kwargs):\n", + " instance = super().__new__(cls)\n", + " return instance\n", + "```\n", + "\n", + "- `cls`: The class being instantiated (analogous to `self` in `__init__`, but refers to the class itself, not yet an instance). This is passed automatically by Python.\n", + "- `*args` / `**kwargs`: Any positional or keyword arguments that were passed to the class constructor. The same ones that will later be forwarded to `__init__`.\n", + "- `super().__new__(cls)`: Delegates the actual object allocation to the parent class (ultimately `object`, the base class of anything in Python). This creates and returns the new instance in memory.\n", + "- The method should the new instance. If it returns an instance of `cls`, Python will then call `__init__` on it.\n", + "\n", + "A common real-world use case is implementing the [**Singleton pattern**](https://www.geeksforgeeks.org/python/singleton-pattern-in-python-a-complete-guide/), ensuring that only one instance of a class ever exists.\n", + "By controlling object creation inside `__new__` you can return the same instance every time the class is called." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "class Singleton:\n", + " _instance = None # class-level variable to hold the single instance\n", + "\n", + " def __new__(cls, *args, **kwargs):\n", + " if cls._instance is None:\n", + " print(\"Creating new instance...\")\n", + " cls._instance = super().__new__(cls)\n", + " else:\n", + " print(\"Returning existing instance.\")\n", + " return cls._instance\n", + "\n", + " def __init__(self, value):\n", + " self.value = value\n", + "\n", + "\n", + "a = Singleton(42)\n", + "b = Singleton(99)\n", + "\n", + "print(f\"a.value = {a.value}\")\n", + "print(f\"b.value = {b.value}\")\n", + "print(f\"a is b: {a is b}\") # True because both variables point to the same object" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, "source": [ "## Abstract Classes\n", "\n", @@ -570,7 +631,7 @@ { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "42", "metadata": {}, "outputs": [], "source": [ @@ -589,7 +650,7 @@ }, { "cell_type": "markdown", - "id": "41", + "id": "43", "metadata": {}, "source": [ "We mark methods of an abstract class as **virtual** – meaning that they must be overridden by each subclass that inherits from the abstract parent – using the `abstractmethod()` decorator.\n", @@ -601,7 +662,7 @@ { "cell_type": "code", "execution_count": null, - "id": "42", + "id": "44", "metadata": {}, "outputs": [], "source": [ @@ -610,7 +671,7 @@ }, { "cell_type": "markdown", - "id": "43", + "id": "45", "metadata": {}, "source": [ "Let's create two concrete subclasses of `Shape`:" @@ -619,7 +680,7 @@ { "cell_type": "code", "execution_count": null, - "id": "44", + "id": "46", "metadata": {}, "outputs": [], "source": [ @@ -648,7 +709,7 @@ }, { "cell_type": "markdown", - "id": "45", + "id": "47", "metadata": {}, "source": [ "Now we are allowed to create instances of the subclasses and also call their methods:" @@ -657,7 +718,7 @@ { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "48", "metadata": {}, "outputs": [], "source": [ @@ -672,7 +733,7 @@ }, { "cell_type": "markdown", - "id": "47", + "id": "49", "metadata": {}, "source": [ "### Quiz on Abstraction" @@ -681,7 +742,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "50", "metadata": {}, "outputs": [], "source": [ @@ -692,7 +753,7 @@ }, { "cell_type": "markdown", - "id": "49", + "id": "51", "metadata": {}, "source": [ "### Exercise: Banking System\n", @@ -743,7 +804,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "52", "metadata": {}, "outputs": [], "source": [ @@ -753,7 +814,7 @@ { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -779,7 +840,7 @@ }, { "cell_type": "markdown", - "id": "52", + "id": "54", "metadata": {}, "source": [ "## Decorators\n", @@ -792,7 +853,7 @@ }, { "cell_type": "markdown", - "id": "53", + "id": "55", "metadata": {}, "source": [ "### @classmethod\n", @@ -814,7 +875,7 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "56", "metadata": {}, "outputs": [], "source": [ @@ -856,7 +917,7 @@ }, { "cell_type": "markdown", - "id": "55", + "id": "57", "metadata": {}, "source": [ "### @staticmethod\n", @@ -871,7 +932,7 @@ }, { "cell_type": "markdown", - "id": "56", + "id": "58", "metadata": { "lines_to_next_cell": 2 }, @@ -893,7 +954,7 @@ { "cell_type": "code", "execution_count": null, - "id": "57", + "id": "59", "metadata": {}, "outputs": [], "source": [ @@ -913,7 +974,7 @@ }, { "cell_type": "markdown", - "id": "58", + "id": "60", "metadata": {}, "source": [ "### @property\n", @@ -930,7 +991,7 @@ { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "61", "metadata": {}, "outputs": [], "source": [ @@ -953,7 +1014,7 @@ }, { "cell_type": "markdown", - "id": "60", + "id": "62", "metadata": {}, "source": [ "We can access the `area` property, just like any other class attribute.\n", @@ -963,7 +1024,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61", + "id": "63", "metadata": {}, "outputs": [], "source": [ @@ -972,7 +1033,7 @@ }, { "cell_type": "markdown", - "id": "62", + "id": "64", "metadata": {}, "source": [ "### Setters & Getters\n", @@ -990,7 +1051,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "65", "metadata": {}, "outputs": [], "source": [ @@ -1016,7 +1077,7 @@ }, { "cell_type": "markdown", - "id": "64", + "id": "66", "metadata": {}, "source": [ "Create an instance and use the getter:" @@ -1025,7 +1086,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "67", "metadata": {}, "outputs": [], "source": [ @@ -1035,7 +1096,7 @@ }, { "cell_type": "markdown", - "id": "66", + "id": "68", "metadata": {}, "source": [ "Update the radius using the setter:" @@ -1044,7 +1105,7 @@ { "cell_type": "code", "execution_count": null, - "id": "67", + "id": "69", "metadata": {}, "outputs": [], "source": [ @@ -1055,7 +1116,7 @@ }, { "cell_type": "markdown", - "id": "68", + "id": "70", "metadata": {}, "source": [ "What happens when we enter an invalid value?" @@ -1064,7 +1125,7 @@ { "cell_type": "code", "execution_count": null, - "id": "69", + "id": "71", "metadata": {}, "outputs": [], "source": [ @@ -1073,7 +1134,7 @@ }, { "cell_type": "markdown", - "id": "70", + "id": "72", "metadata": {}, "source": [ "Finally let's use the deleter:" @@ -1082,7 +1143,7 @@ { "cell_type": "code", "execution_count": null, - "id": "71", + "id": "73", "metadata": {}, "outputs": [], "source": [ @@ -1091,7 +1152,7 @@ }, { "cell_type": "markdown", - "id": "72", + "id": "74", "metadata": {}, "source": [ "We are no longer able to access the deleted attribute:" @@ -1100,7 +1161,7 @@ { "cell_type": "code", "execution_count": null, - "id": "73", + "id": "75", "metadata": {}, "outputs": [], "source": [ @@ -1109,7 +1170,7 @@ }, { "cell_type": "markdown", - "id": "74", + "id": "76", "metadata": {}, "source": [ "### Quiz on Decorators" @@ -1118,7 +1179,7 @@ { "cell_type": "code", "execution_count": null, - "id": "75", + "id": "77", "metadata": {}, "outputs": [], "source": [ @@ -1129,7 +1190,7 @@ }, { "cell_type": "markdown", - "id": "76", + "id": "78", "metadata": {}, "source": [ "## Encapsulation\n", @@ -1150,7 +1211,7 @@ }, { "cell_type": "markdown", - "id": "77", + "id": "79", "metadata": {}, "source": [ "### Public\n", @@ -1159,7 +1220,7 @@ }, { "cell_type": "markdown", - "id": "78", + "id": "80", "metadata": {}, "source": [ "### Private\n", @@ -1171,7 +1232,7 @@ { "cell_type": "code", "execution_count": null, - "id": "79", + "id": "81", "metadata": {}, "outputs": [], "source": [ @@ -1190,7 +1251,7 @@ }, { "cell_type": "markdown", - "id": "80", + "id": "82", "metadata": {}, "source": [ "### Protected\n", @@ -1203,7 +1264,7 @@ { "cell_type": "code", "execution_count": null, - "id": "81", + "id": "83", "metadata": {}, "outputs": [], "source": [ @@ -1222,7 +1283,7 @@ }, { "cell_type": "markdown", - "id": "82", + "id": "84", "metadata": {}, "source": [ "### Quiz on Encapsulation" @@ -1231,7 +1292,7 @@ { "cell_type": "code", "execution_count": null, - "id": "83", + "id": "85", "metadata": {}, "outputs": [], "source": [ @@ -1242,7 +1303,7 @@ }, { "cell_type": "markdown", - "id": "84", + "id": "86", "metadata": {}, "source": [ "## How to write better classes\n", @@ -1252,7 +1313,7 @@ }, { "cell_type": "markdown", - "id": "85", + "id": "87", "metadata": {}, "source": [ "### Using `dataclasses`\n", @@ -1263,7 +1324,7 @@ { "cell_type": "code", "execution_count": null, - "id": "86", + "id": "88", "metadata": {}, "outputs": [], "source": [ @@ -1276,7 +1337,7 @@ }, { "cell_type": "markdown", - "id": "87", + "id": "89", "metadata": {}, "source": [ "A simpler way, however, would be to import `dataclass` from the `dataclasses` module.\n", @@ -1287,7 +1348,7 @@ { "cell_type": "code", "execution_count": null, - "id": "88", + "id": "90", "metadata": {}, "outputs": [], "source": [ @@ -1303,7 +1364,7 @@ }, { "cell_type": "markdown", - "id": "89", + "id": "91", "metadata": {}, "source": [ "Now, with the use of these auto generated methods, we can create an instance of the class and print a representation of the object, without any additional code.\n", @@ -1313,7 +1374,7 @@ { "cell_type": "code", "execution_count": null, - "id": "90", + "id": "92", "metadata": {}, "outputs": [], "source": [ @@ -1327,7 +1388,7 @@ }, { "cell_type": "markdown", - "id": "91", + "id": "93", "metadata": {}, "source": [ "### Using `attrs`\n", @@ -1349,7 +1410,7 @@ }, { "cell_type": "markdown", - "id": "92", + "id": "94", "metadata": {}, "source": [ "Let's rewrite the previous example, this time using `attrs`.\n", @@ -1359,7 +1420,7 @@ { "cell_type": "code", "execution_count": null, - "id": "93", + "id": "95", "metadata": {}, "outputs": [], "source": [ @@ -1382,7 +1443,7 @@ }, { "cell_type": "markdown", - "id": "94", + "id": "96", "metadata": {}, "source": [ "However, `attrs` also provides **validators**.\n", @@ -1394,7 +1455,7 @@ { "cell_type": "code", "execution_count": null, - "id": "95", + "id": "97", "metadata": {}, "outputs": [], "source": [ @@ -1418,7 +1479,7 @@ }, { "cell_type": "markdown", - "id": "96", + "id": "98", "metadata": {}, "source": [ "### Quiz on `attrs` and `dataclasses`" @@ -1427,7 +1488,7 @@ { "cell_type": "code", "execution_count": null, - "id": "97", + "id": "99", "metadata": {}, "outputs": [], "source": [ @@ -1438,7 +1499,7 @@ }, { "cell_type": "markdown", - "id": "98", + "id": "100", "metadata": {}, "source": [ "## Exercises" @@ -1447,7 +1508,7 @@ { "cell_type": "code", "execution_count": null, - "id": "99", + "id": "101", "metadata": {}, "outputs": [], "source": [ @@ -1456,7 +1517,7 @@ }, { "cell_type": "markdown", - "id": "100", + "id": "102", "metadata": {}, "source": [ "### Store Inventory\n", @@ -1496,7 +1557,7 @@ { "cell_type": "code", "execution_count": null, - "id": "101", + "id": "103", "metadata": {}, "outputs": [], "source": [ @@ -1532,7 +1593,7 @@ }, { "cell_type": "markdown", - "id": "102", + "id": "104", "metadata": {}, "source": [ "### Music Streaming Service\n", @@ -1565,7 +1626,7 @@ { "cell_type": "code", "execution_count": null, - "id": "103", + "id": "105", "metadata": {}, "outputs": [], "source": [ @@ -1615,13 +1676,13 @@ }, { "cell_type": "markdown", - "id": "104", + "id": "106", "metadata": {}, "source": [] }, { "cell_type": "markdown", - "id": "105", + "id": "107", "metadata": {}, "source": [ "### The N-body problem\n", @@ -1648,7 +1709,7 @@ }, { "cell_type": "markdown", - "id": "106", + "id": "108", "metadata": {}, "source": [ "