From 5eb54da2af012e0b9660f5f5fd887e1aaf5e0051 Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Fri, 7 Nov 2025 17:31:01 +0100 Subject: [PATCH 01/14] fixes in oop notebook --- 05_object_oriented_programming.ipynb | 149 +++++++++++++----- .../test_05_object_oriented_programming.py | 2 +- 2 files changed, 108 insertions(+), 43 deletions(-) diff --git a/05_object_oriented_programming.ipynb b/05_object_oriented_programming.ipynb index afb772b5..d56eb360 100644 --- a/05_object_oriented_programming.ipynb +++ b/05_object_oriented_programming.ipynb @@ -32,7 +32,7 @@ " - [Exercise 1: Ice cream scoop](#Exercise-1:-Ice-cream-scoop)\n", " - [Exercise 2: Ice cream bowl](#Exercise-2:-Ice-cream-bowl)\n", " - [Exercise 3: Ice cream shop](#Exercise-3:-Ice-cream-shop)\n", - " - [Exercise 4: Intcode computer 🌶️](#Exercise-4:-Intcode-computer-🌶️)" + " - [Exercise 4: Intcode computer](#Exercise-4:-Intcode-computer)" ] }, { @@ -68,11 +68,14 @@ "id": "5", "metadata": {}, "source": [ - "Object-oriented programming (OOP) is probably the most well-known approach to programming. Almost every programming language in some way or another supports this paradigm.\n", + "Object-oriented programming (OOP) is probably the most well-known approach to programming.\n", + "Almost every programming language in some way or another supports this paradigm.\n", "\n", - "The idea behind OOP is simple: instead of defining our functions in one part of the code, and the data on which those functions operate in a separate part of the code, we define them together.\n", + "The idea behind OOP is simple: instead of defining our functions in one part of the code, and the data, on which those functions operate, in a separate part of the code, we define them together.\n", "\n", - "The concept of **object** is the heart of OOP, and the fundamental building block is the **class**. We have already seen many types of objects, each belonging to different classes:" + "The concept of **class** is the heart of OOP.\n", + "It defines the expected structure and behaviour of an object.\n", + "We have already seen many types of objects, each belonging to different classes:" ] }, { @@ -95,7 +98,8 @@ "id": "7", "metadata": {}, "source": [ - "In Python, to create a custom class we use the `class` keyword, and we can initialize class attributes in the special method `__init__`.\n", + "In Python, we use the `class` keyword to create a custom class.\n", + "Then, we initialize class attributes in the special method `__init__`.\n", "\n", "For example, let's create a class that represents a rectangle as a geometrical entity:" ] @@ -120,16 +124,26 @@ "id": "9", "metadata": {}, "source": [ - "The first argument (`self`) is automatically filled in by Python and contains **the object being created**. It's the way a class can modify itself, in some sense.\n", + "The first argument (`self`) should always be there.\n", + "In Python, `self` is a fundamental concept when working with classes.\n", + "It represents the object that is currently being used.\n", + "\n", + "An object of type `class` is called **instance**.\n", + "Whenever we create an instance of a class, `self` refers to that specific instance.\n", + "\n", + "`self` is essential for accessing attributes and methods within the class.\n", + "It allows methods within the class to access and modify the object's attributes, making each instance independent of others.\n", + "Without `self`, Python would not know which instance’s attributes or methods to refer to.\n", "\n", "
\n", "

Note

\n", - " Using the name self is just a convention (although a good one, and you should use it to make your code more understandable by others), you could really call it whatever (valid) name you want\n", + " Using the name self is just a convention (although a good one, and you should use it to make your code more understandable by others), you could really call it whatever (valid) name you want.\n", "
\n", "\n", - "We create **instances** of the `Rectangle` class by calling it with arguments that are passed to the `__init__` method as the second and third arguments.\n", + "You can create **instances** of `Rectangle` by calling the class and passing the required arguments (except `self`).\n", + "For `Rectangle`, those arguments are passed to the `__init__` method as the second and third arguments.\n", "\n", - "You **don't** have to pass `self` when creating a new instance of a class." + "Remember that you **don't** have to pass `self` when creating a new instance of a class." ] }, { @@ -169,6 +183,14 @@ "r2.height # this will return the `self.height` value" ] }, + { + "cell_type": "markdown", + "id": "170c2585", + "metadata": {}, + "source": [ + "In this example we can see that `r1` and `r2` are two completely independent **instances** of the same class `Rectangle`." + ] + }, { "cell_type": "markdown", "id": "13", @@ -238,9 +260,10 @@ "id": "18", "metadata": {}, "source": [ - "`width` and `height` are attributes of the `Rectangle` class. But since they are just values (they are **not** functions), we call them **properties**.\n", + "`width` and `height` are attributes of the `Rectangle` class.\n", + "But since they are just values (they are **not** functions), we call them **properties**.\n", "\n", - "Attributes that are callables (that is, functions) are called **methods**.\n", + "Attributes that are callables (so, functions) are called **methods**.\n", "\n", "Properties and methods together are called **attributes** of a class." ] @@ -250,7 +273,19 @@ "id": "19", "metadata": {}, "source": [ - "You'll note that we were able to retrieve the `width` and `height` attributes (properties) using a dot notation, where we specify the object we are interested in, then a dot, then the attribute we are interested in." + "You saw note that we were able to retrieve the `width` and `height` attributes (properties) using a dot notation, where we specify the object we are interested in, then a dot, then the attribute we are interested in.\n", + "\n", + "So, for example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c632a6fc", + "metadata": {}, + "outputs": [], + "source": [ + "r1.height" ] }, { @@ -375,9 +410,12 @@ "id": "28", "metadata": {}, "source": [ - "Special methods are methods that Python defines automatically. If you define a custom class, then you are responsible of defining the **expected behavior** of these methods. Otherwise, Python will fallback to the default, built-in definition, or it will raise an error if it doesn't know what to do.\n", + "Special methods are methods that Python defines automatically.\n", + "If you define a custom class, then you are responsible of defining the **expected behavior** of these methods.\n", + "Otherwise, Python will fallback to the default, built-in definition, or it will raise an error if it doesn't know what to do.\n", "\n", - "These are also called **dunder methods**, that is, \"double-underscore methods\", because their names look like `__method__`. There are special **attributes** as well." + "These are also called **dunder methods**, that is, \"double-underscore methods\", because their names look like `__method__`.\n", + "There are special **attributes** as well." ] }, { @@ -427,7 +465,8 @@ "id": "33", "metadata": {}, "source": [ - "Not exactly what we might have expected. On the other hand, how is Python supposed to know how to display our rectangle as a string?\n", + "Not exactly what we might have expected.\n", + "On the other hand, how is Python supposed to know how to display our rectangle as a string?\n", "\n", "We could write a method in the class such as:" ] @@ -502,11 +541,12 @@ "id": "39", "metadata": {}, "source": [ - "This is where these special methods come in. When we call `str(r1)`, Python will first look to see if our class (`Rectangle`) has a special method called `__str__`.\n", + "This is where these special methods come in.\n", + "When we call `str(r1)`, Python will first look to see if our class (`Rectangle`) has a special method called `__str__`.\n", "\n", "If the `__str__` method is present, then Python will call it and return that value.\n", "\n", - "There's actually another one called `__repr__` which is related, but we'll just focus on `__str__` for now." + "There is actually another one called `__repr__` which is related, but we will just focus on `__str__` for now." ] }, { @@ -583,11 +623,15 @@ "id": "45", "metadata": {}, "source": [ - "As you can see we still get the default. That's because here Python is **not** converting `r1` to a string, but instead looking for a string *representation* of the object. It is looking for the [`__repr__` method](https://docs.python.org/3/reference/datamodel.html#object.__repr__), which is defined as\n", + "As you can see we still get the default.\n", + "That is because here Python is **not** converting `r1` to a string, but instead looking for a string *representation* of the object.\n", + "It is looking for the [`__repr__` method](https://docs.python.org/3/reference/datamodel.html#object.__repr__), which is defined as\n", "\n", "> the “official” string representation of an object\n", "\n", - "Ideally, the `__repr__` method should return a **valid Python expression** that can be used to recreate an instance of the object. If it's not possible, it should return a simple string. For this tutorial, we can define a `__repr__` method that behaves **identically** to the `__str__` method." + "Ideally, the `__repr__` method should return a **valid Python expression** that can be used to recreate an instance of the object.\n", + "If it's not possible, it should return a simple string.\n", + "For this tutorial, we can define a `__repr__` method that behaves **identically** to the `__str__` method." ] }, { @@ -702,7 +746,8 @@ "id": "53", "metadata": {}, "source": [ - "How about the comparison operator, such as `==`? How can we tell Python how it should compare two different rectangles?" + "How about the comparison operator, such as `==`?\n", + "How can we tell Python how it should compare two different rectangles?" ] }, { @@ -733,7 +778,8 @@ "id": "56", "metadata": {}, "source": [ - "As you can see, Python does not consider `r1` and `r2` as equal (using the `==` operator). Again, how is Python supposed to know that two rectangle objects with the same height and width should be considered equal?" + "As you can see, Python does not consider `r1` and `r2` as equal (using the `==` operator).\n", + "Again, how is Python supposed to know that two rectangle objects with the same height and width should be considered equal?" ] }, { @@ -741,7 +787,8 @@ "id": "57", "metadata": {}, "source": [ - "We just need to tell Python how to do it, using the special method `__eq__`. Let's see how:" + "We just need to tell Python how to do it, using the special method `__eq__`.\n", + "Let's see how:" ] }, { @@ -800,7 +847,8 @@ "id": "60", "metadata": {}, "source": [ - "We now have two **different** objects, two instances of our `Rectangle` class. In fact, we can check that the two objects are different with the `is` operator" + "We now have two **different** objects, two instances of our `Rectangle` class.\n", + "In fact, we can check that the two objects are different with the `is` operator." ] }, { @@ -820,7 +868,7 @@ "id": "62", "metadata": {}, "source": [ - "However, they are **equal** according to our `__eq__` function: rectangles are considered equal if their widths and heights are equal" + "However, they are **equal** according to our `__eq__` function: rectangles are considered equal if their widths and heights are equal." ] }, { @@ -892,7 +940,7 @@ "id": "69", "metadata": {}, "source": [ - "Here's our final class, without any `print` statement – remember, we should try to avoid side-effects when they are **not** necessary:" + "Here is our final class, without any `print` statement – remember, we should try to avoid side-effects when they are **not** necessary:" ] }, { @@ -1103,9 +1151,13 @@ "id": "83", "metadata": {}, "source": [ - "How did that work? We did not define a `__gt__` method.\n", + "How did that work?\n", + "We did not define a `__gt__` method.\n", "\n", - "Well, Python decided that, since `r1 > r2` was not implemented, it would give `r2 < r1` a try. And since, `__lt__` **is** defined, it worked! It just a matter of swapping the terms in the comparison. Clever, eh? 😎" + "Well, Python decided that, since `r1 > r2` was not implemented, it would give `r2 < r1` a try.\n", + "And since, `__lt__` **is** defined, it worked!\n", + "It just a matter of swapping the terms in the comparison.\n", + "Clever, right? 😎" ] }, { @@ -1141,9 +1193,11 @@ "id": "87", "metadata": {}, "source": [ - "A question you might have about our `Rectangle` class is the following: why should we **call** a function to return its area? Isn't area a **property** of a rectangle?\n", + "A question you might have about our `Rectangle` class is the following: why should we **call** a function to return its area?\n", + "Isn't area a **property** of a rectangle?\n", "\n", - "Unfortunately, Python doesn't think the same way. If we try to do" + "Unfortunately, Python does not think the same way.\n", + "If we try to do:" ] }, { @@ -1163,7 +1217,8 @@ "id": "89", "metadata": {}, "source": [ - "It would tell us that `r1.area` is in fact an object. It's actually a **function**" + "It would tell us that `r1.area` is in fact an object.\n", + "It's actually a **function**." ] }, { @@ -1201,7 +1256,8 @@ "id": "93", "metadata": {}, "source": [ - "But what if you want to define area as one of Rectangle's properties? The only difference with the other ones, it that this property requires some additional logic in the background, in order for its value to be calculated." + "But what if you want to define area as one of Rectangle's properties?\n", + "The only difference with the other ones, it that this property requires some additional logic in the background, in order for its value to be calculated." ] }, { @@ -1209,7 +1265,8 @@ "id": "94", "metadata": {}, "source": [ - "Python provides you the special keyword `@property`. We are not going into the details of what this keyword does, but here's how you can use it in your classes to make them more \"user-friendly\":" + "Python provides you the special keyword `@property`.\n", + "We are not going into the details of what this keyword does, but here is how you can use it in your classes to make them more \"user-friendly\":" ] }, { @@ -1246,7 +1303,8 @@ "id": "96", "metadata": {}, "source": [ - "You can simply add `@property` **just above** the line that defines your property. For example, the `def area()` or `def perimeter()` functions above." + "You can simply add `@property` **just above** the line that defines your property.\n", + "For example, the `def area()` or `def perimeter()` functions above." ] }, { @@ -1278,7 +1336,8 @@ "id": "99", "metadata": {}, "source": [ - "The only difference is that in this way we are unable to directly set the value of these properties. Their values are always internally calculated based on their defined logic, and we are only able to access them." + "The only difference is that in this way we are unable to directly set the value of these properties.\n", + "Their values are always internally calculated based on their defined logic, and we are only able to access them." ] }, { @@ -1304,7 +1363,9 @@ "id": "102", "metadata": {}, "source": [ - "If you want to learn more about the `@property` keyword, you can check out [this link](https://docs.python.org/3/library/functions.html#property). **Beware**, it's rather advanced stuff! If it's your first time with Python, just skip it for the time being." + "If you want to learn more about the `@property` keyword, you can check out [this link](https://docs.python.org/3/library/functions.html#property).\n", + "**Beware**, it's rather advanced stuff!\n", + "If it's your first time with Python, just skip it for the time being." ] }, { @@ -1399,7 +1460,11 @@ "source": [ "Define a class `Scoop` that represents a single scoop of ice cream. Each scoop should have a **single** attribute, `flavor`, a string that you can initialize when you create the instance of `Scoop`.\n", "\n", - "Define also a `__str__` method to return a string reprensentation of a scoop. The output should be `Ice cream scoop with flavor ''`, where `'`, where `` is the actual scoop's flavor.\n", + "**Pay attention to the single quotes!**\n", + "\n", + "Modify the `solution_ice_cream_scoop` function to return a list of Scoop instances, one for each flavor of the tuple of flavors that are provided to you as an argument to the function.\n", "\n", "
\n", "

Question

\n", @@ -1420,10 +1485,10 @@ " \"\"\"A function that contains the definition of a class Scoop, and returns a list of Scoop instances.\n", "\n", " Scoop is a class with one attribute called 'flavor'.\n", - " Scoop implements the __str__() method which should return the string 'Ice cream scoop with flavor '{flavor}'\n", + " Scoop implements the __str__() method which should return the string \"Ice cream scoop with flavor '{flavor}'\".\n", "\n", " Args:\n", - " flavors: all available ice cream flavors\n", + " flavors: a tuple with all available ice cream flavors\n", " Returns:\n", " - a list containing one Scoop instance per flavor\n", " \"\"\"\n", @@ -1441,9 +1506,9 @@ "\n", "Create a class `Bowl` that can hold many ice cream scoops, as many as you like. You *should use* the custom class `Scoop` that you created in the previous exercise.\n", "\n", - "The `Bowl` class should have a method called `add_scoops()` that accepts **variable number** of scoops.\n", + "The `Bowl` class should have a method called `add_scoops()` that accepts a **variable number** of scoops.\n", "\n", - "

Define also the __str__ method to return the reprensentation of a bowl, which should report the content of the bowl you just created. For example: Ice cream bowl with chocolate, vanilla, stracciatella scoops.

\n", + "

Define also the __str__ method to return the representation of a bowl, which should report the content of the bowl you just created. For example: Ice cream bowl with chocolate, vanilla, stracciatella scoops.

\n", "\n", "
\n", "

Hint

\n", @@ -1548,7 +1613,7 @@ "tags": [] }, "source": [ - "## Exercise 4: Intcode computer 🌶️\n", + "## Exercise 4: Intcode computer\n", "\n", "
\n", "

Note

\n", @@ -1700,7 +1765,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.19" } }, "nbformat": 4, diff --git a/tutorial/tests/test_05_object_oriented_programming.py b/tutorial/tests/test_05_object_oriented_programming.py index be2c15bc..b0984213 100644 --- a/tutorial/tests/test_05_object_oriented_programming.py +++ b/tutorial/tests/test_05_object_oriented_programming.py @@ -352,7 +352,7 @@ class Bowl: def __init__(self): self.scoops = [] - def add_scoops(self, *new_scoops: "Scoop") -> None: + def add_scoops(self, *new_scoops: list["Scoop"]) -> None: for one_scoop in new_scoops: self.scoops.append(one_scoop) From 7fe8961f133767fe85120794439b8c7b442c8738 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:35:30 +0000 Subject: [PATCH 02/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- 05_object_oriented_programming.ipynb | 226 +++++++++++++-------------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/05_object_oriented_programming.ipynb b/05_object_oriented_programming.ipynb index d56eb360..b1eb5268 100644 --- a/05_object_oriented_programming.ipynb +++ b/05_object_oriented_programming.ipynb @@ -185,7 +185,7 @@ }, { "cell_type": "markdown", - "id": "170c2585", + "id": "13", "metadata": {}, "source": [ "In this example we can see that `r1` and `r2` are two completely independent **instances** of the same class `Rectangle`." @@ -193,7 +193,7 @@ }, { "cell_type": "markdown", - "id": "13", + "id": "14", "metadata": {}, "source": [ "## Example 1" @@ -202,7 +202,7 @@ { "cell_type": "code", "execution_count": null, - "id": "14", + "id": "15", "metadata": {}, "outputs": [], "source": [ @@ -211,7 +211,7 @@ }, { "cell_type": "markdown", - "id": "15", + "id": "16", "metadata": {}, "source": [ "
\n", @@ -223,7 +223,7 @@ { "cell_type": "code", "execution_count": null, - "id": "16", + "id": "17", "metadata": {}, "outputs": [], "source": [ @@ -247,7 +247,7 @@ }, { "cell_type": "markdown", - "id": "17", + "id": "18", "metadata": { "tags": [] }, @@ -257,7 +257,7 @@ }, { "cell_type": "markdown", - "id": "18", + "id": "19", "metadata": {}, "source": [ "`width` and `height` are attributes of the `Rectangle` class.\n", @@ -270,7 +270,7 @@ }, { "cell_type": "markdown", - "id": "19", + "id": "20", "metadata": {}, "source": [ "You saw note that we were able to retrieve the `width` and `height` attributes (properties) using a dot notation, where we specify the object we are interested in, then a dot, then the attribute we are interested in.\n", @@ -281,7 +281,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c632a6fc", + "id": "21", "metadata": {}, "outputs": [], "source": [ @@ -290,7 +290,7 @@ }, { "cell_type": "markdown", - "id": "20", + "id": "22", "metadata": {}, "source": [ "We can add callable attributes to our class (methods), that will also be referenced using the dot notation.\n", @@ -301,7 +301,7 @@ { "cell_type": "code", "execution_count": null, - "id": "21", + "id": "23", "metadata": { "tags": [] }, @@ -322,7 +322,7 @@ { "cell_type": "code", "execution_count": null, - "id": "22", + "id": "24", "metadata": { "tags": [] }, @@ -334,7 +334,7 @@ { "cell_type": "code", "execution_count": null, - "id": "23", + "id": "25", "metadata": { "tags": [] }, @@ -345,7 +345,7 @@ }, { "cell_type": "markdown", - "id": "24", + "id": "26", "metadata": {}, "source": [ "When we ran the above line of code, our object was `r1`, so when `area` was called, Python called the method `area` in the `Rectangle` class automatically, passing `r1` as the argument to the `self` parameter." @@ -353,7 +353,7 @@ }, { "cell_type": "markdown", - "id": "25", + "id": "27", "metadata": {}, "source": [ "## Example 2\n", @@ -372,7 +372,7 @@ { "cell_type": "code", "execution_count": null, - "id": "26", + "id": "28", "metadata": {}, "outputs": [], "source": [ @@ -397,7 +397,7 @@ }, { "cell_type": "markdown", - "id": "27", + "id": "29", "metadata": { "tags": [] }, @@ -407,7 +407,7 @@ }, { "cell_type": "markdown", - "id": "28", + "id": "30", "metadata": {}, "source": [ "Special methods are methods that Python defines automatically.\n", @@ -420,7 +420,7 @@ }, { "cell_type": "markdown", - "id": "29", + "id": "31", "metadata": { "tags": [] }, @@ -433,7 +433,7 @@ { "cell_type": "code", "execution_count": null, - "id": "30", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -442,7 +442,7 @@ }, { "cell_type": "markdown", - "id": "31", + "id": "33", "metadata": {}, "source": [ "What happens if we try this with our `Rectangle` object?" @@ -451,7 +451,7 @@ { "cell_type": "code", "execution_count": null, - "id": "32", + "id": "34", "metadata": { "tags": [] }, @@ -462,7 +462,7 @@ }, { "cell_type": "markdown", - "id": "33", + "id": "35", "metadata": {}, "source": [ "Not exactly what we might have expected.\n", @@ -474,7 +474,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "36", "metadata": { "tags": [] }, @@ -497,7 +497,7 @@ }, { "cell_type": "markdown", - "id": "35", + "id": "37", "metadata": {}, "source": [ "So now we could get a string from our object as follows:" @@ -506,7 +506,7 @@ { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "38", "metadata": { "tags": [] }, @@ -518,7 +518,7 @@ }, { "cell_type": "markdown", - "id": "37", + "id": "39", "metadata": {}, "source": [ "However, using the built-in `str` function still does not work 🤔" @@ -527,7 +527,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "40", "metadata": { "tags": [] }, @@ -538,7 +538,7 @@ }, { "cell_type": "markdown", - "id": "39", + "id": "41", "metadata": {}, "source": [ "This is where these special methods come in.\n", @@ -552,7 +552,7 @@ { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "42", "metadata": { "tags": [] }, @@ -577,7 +577,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41", + "id": "43", "metadata": { "tags": [] }, @@ -589,7 +589,7 @@ { "cell_type": "code", "execution_count": null, - "id": "42", + "id": "44", "metadata": { "tags": [] }, @@ -600,7 +600,7 @@ }, { "cell_type": "markdown", - "id": "43", + "id": "45", "metadata": {}, "source": [ "However, in Jupyter, look what happens here:" @@ -609,7 +609,7 @@ { "cell_type": "code", "execution_count": null, - "id": "44", + "id": "46", "metadata": { "tags": [] }, @@ -620,7 +620,7 @@ }, { "cell_type": "markdown", - "id": "45", + "id": "47", "metadata": {}, "source": [ "As you can see we still get the default.\n", @@ -637,7 +637,7 @@ { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "48", "metadata": { "tags": [] }, @@ -663,7 +663,7 @@ }, { "cell_type": "markdown", - "id": "47", + "id": "49", "metadata": {}, "source": [ "So, let's try to define and print `r1` again, to see what happens." @@ -672,7 +672,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "50", "metadata": {}, "outputs": [], "source": [ @@ -682,7 +682,7 @@ }, { "cell_type": "markdown", - "id": "49", + "id": "51", "metadata": {}, "source": [ "### Example 3\n", @@ -696,7 +696,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "52", "metadata": {}, "outputs": [], "source": [ @@ -706,7 +706,7 @@ { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -733,7 +733,7 @@ }, { "cell_type": "markdown", - "id": "52", + "id": "54", "metadata": { "tags": [] }, @@ -743,7 +743,7 @@ }, { "cell_type": "markdown", - "id": "53", + "id": "55", "metadata": {}, "source": [ "How about the comparison operator, such as `==`?\n", @@ -753,7 +753,7 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "56", "metadata": { "tags": [] }, @@ -766,7 +766,7 @@ { "cell_type": "code", "execution_count": null, - "id": "55", + "id": "57", "metadata": {}, "outputs": [], "source": [ @@ -775,7 +775,7 @@ }, { "cell_type": "markdown", - "id": "56", + "id": "58", "metadata": {}, "source": [ "As you can see, Python does not consider `r1` and `r2` as equal (using the `==` operator).\n", @@ -784,7 +784,7 @@ }, { "cell_type": "markdown", - "id": "57", + "id": "59", "metadata": {}, "source": [ "We just need to tell Python how to do it, using the special method `__eq__`.\n", @@ -794,7 +794,7 @@ { "cell_type": "code", "execution_count": null, - "id": "58", + "id": "60", "metadata": { "tags": [] }, @@ -832,7 +832,7 @@ { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "61", "metadata": { "tags": [] }, @@ -844,7 +844,7 @@ }, { "cell_type": "markdown", - "id": "60", + "id": "62", "metadata": {}, "source": [ "We now have two **different** objects, two instances of our `Rectangle` class.\n", @@ -854,7 +854,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61", + "id": "63", "metadata": { "tags": [] }, @@ -865,7 +865,7 @@ }, { "cell_type": "markdown", - "id": "62", + "id": "64", "metadata": {}, "source": [ "However, they are **equal** according to our `__eq__` function: rectangles are considered equal if their widths and heights are equal." @@ -874,7 +874,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "65", "metadata": { "tags": [] }, @@ -886,7 +886,7 @@ { "cell_type": "code", "execution_count": null, - "id": "64", + "id": "66", "metadata": { "tags": [] }, @@ -898,7 +898,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "67", "metadata": { "tags": [] }, @@ -909,7 +909,7 @@ }, { "cell_type": "markdown", - "id": "66", + "id": "68", "metadata": {}, "source": [ "And if we try to compare our Rectangle to a different type:" @@ -918,7 +918,7 @@ { "cell_type": "code", "execution_count": null, - "id": "67", + "id": "69", "metadata": { "tags": [] }, @@ -929,7 +929,7 @@ }, { "cell_type": "markdown", - "id": "68", + "id": "70", "metadata": {}, "source": [ "That's because our `Rectangle` class automatically returns `False` if we try to compare for equality an instance of `Rectangle` and any other Python object." @@ -937,7 +937,7 @@ }, { "cell_type": "markdown", - "id": "69", + "id": "71", "metadata": {}, "source": [ "Here is our final class, without any `print` statement – remember, we should try to avoid side-effects when they are **not** necessary:" @@ -946,7 +946,7 @@ { "cell_type": "code", "execution_count": null, - "id": "70", + "id": "72", "metadata": {}, "outputs": [], "source": [ @@ -976,7 +976,7 @@ }, { "cell_type": "markdown", - "id": "71", + "id": "73", "metadata": {}, "source": [ "### Example 4\n", @@ -990,7 +990,7 @@ { "cell_type": "code", "execution_count": null, - "id": "72", + "id": "74", "metadata": {}, "outputs": [], "source": [ @@ -1000,7 +1000,7 @@ { "cell_type": "code", "execution_count": null, - "id": "73", + "id": "75", "metadata": {}, "outputs": [], "source": [ @@ -1027,7 +1027,7 @@ }, { "cell_type": "markdown", - "id": "74", + "id": "76", "metadata": {}, "source": [ "## More comparison methods" @@ -1035,7 +1035,7 @@ }, { "cell_type": "markdown", - "id": "75", + "id": "77", "metadata": {}, "source": [ "What about `<`, `>`, `<=`, etc.?\n", @@ -1047,7 +1047,7 @@ }, { "cell_type": "markdown", - "id": "76", + "id": "78", "metadata": {}, "source": [ "
\n", @@ -1059,7 +1059,7 @@ { "cell_type": "code", "execution_count": null, - "id": "77", + "id": "79", "metadata": { "tags": [] }, @@ -1098,7 +1098,7 @@ { "cell_type": "code", "execution_count": null, - "id": "78", + "id": "80", "metadata": { "tags": [] }, @@ -1111,7 +1111,7 @@ { "cell_type": "code", "execution_count": null, - "id": "79", + "id": "81", "metadata": {}, "outputs": [], "source": [ @@ -1121,7 +1121,7 @@ { "cell_type": "code", "execution_count": null, - "id": "80", + "id": "82", "metadata": {}, "outputs": [], "source": [ @@ -1130,7 +1130,7 @@ }, { "cell_type": "markdown", - "id": "81", + "id": "83", "metadata": {}, "source": [ "What about `>`?" @@ -1139,7 +1139,7 @@ { "cell_type": "code", "execution_count": null, - "id": "82", + "id": "84", "metadata": {}, "outputs": [], "source": [ @@ -1148,7 +1148,7 @@ }, { "cell_type": "markdown", - "id": "83", + "id": "85", "metadata": {}, "source": [ "How did that work?\n", @@ -1162,7 +1162,7 @@ }, { "cell_type": "markdown", - "id": "84", + "id": "86", "metadata": {}, "source": [ "Of course, `<=` is not going to magically work!" @@ -1171,7 +1171,7 @@ { "cell_type": "code", "execution_count": null, - "id": "85", + "id": "87", "metadata": {}, "outputs": [], "source": [ @@ -1180,7 +1180,7 @@ }, { "cell_type": "markdown", - "id": "86", + "id": "88", "metadata": { "tags": [] }, @@ -1190,7 +1190,7 @@ }, { "cell_type": "markdown", - "id": "87", + "id": "89", "metadata": {}, "source": [ "A question you might have about our `Rectangle` class is the following: why should we **call** a function to return its area?\n", @@ -1203,7 +1203,7 @@ { "cell_type": "code", "execution_count": null, - "id": "88", + "id": "90", "metadata": { "tags": [] }, @@ -1214,7 +1214,7 @@ }, { "cell_type": "markdown", - "id": "89", + "id": "91", "metadata": {}, "source": [ "It would tell us that `r1.area` is in fact an object.\n", @@ -1224,7 +1224,7 @@ { "cell_type": "code", "execution_count": null, - "id": "90", + "id": "92", "metadata": { "tags": [] }, @@ -1235,7 +1235,7 @@ }, { "cell_type": "markdown", - "id": "91", + "id": "93", "metadata": {}, "source": [ "Which means that if you want to return the Rectangle's area, you should do the following: " @@ -1244,7 +1244,7 @@ { "cell_type": "code", "execution_count": null, - "id": "92", + "id": "94", "metadata": {}, "outputs": [], "source": [ @@ -1253,7 +1253,7 @@ }, { "cell_type": "markdown", - "id": "93", + "id": "95", "metadata": {}, "source": [ "But what if you want to define area as one of Rectangle's properties?\n", @@ -1262,7 +1262,7 @@ }, { "cell_type": "markdown", - "id": "94", + "id": "96", "metadata": {}, "source": [ "Python provides you the special keyword `@property`.\n", @@ -1272,7 +1272,7 @@ { "cell_type": "code", "execution_count": null, - "id": "95", + "id": "97", "metadata": { "tags": [] }, @@ -1300,7 +1300,7 @@ }, { "cell_type": "markdown", - "id": "96", + "id": "98", "metadata": {}, "source": [ "You can simply add `@property` **just above** the line that defines your property.\n", @@ -1309,7 +1309,7 @@ }, { "cell_type": "markdown", - "id": "97", + "id": "99", "metadata": {}, "source": [ "By doing so, you can now access the values of area and perimeter in the same way that you would for any other property of the class:" @@ -1318,7 +1318,7 @@ { "cell_type": "code", "execution_count": null, - "id": "98", + "id": "100", "metadata": { "tags": [] }, @@ -1333,7 +1333,7 @@ }, { "cell_type": "markdown", - "id": "99", + "id": "101", "metadata": {}, "source": [ "The only difference is that in this way we are unable to directly set the value of these properties.\n", @@ -1342,7 +1342,7 @@ }, { "cell_type": "markdown", - "id": "100", + "id": "102", "metadata": {}, "source": [ "See what happens when you try to execute the following line:" @@ -1351,7 +1351,7 @@ { "cell_type": "code", "execution_count": null, - "id": "101", + "id": "103", "metadata": {}, "outputs": [], "source": [ @@ -1360,7 +1360,7 @@ }, { "cell_type": "markdown", - "id": "102", + "id": "104", "metadata": {}, "source": [ "If you want to learn more about the `@property` keyword, you can check out [this link](https://docs.python.org/3/library/functions.html#property).\n", @@ -1370,7 +1370,7 @@ }, { "cell_type": "markdown", - "id": "103", + "id": "105", "metadata": { "tags": [] }, @@ -1380,7 +1380,7 @@ }, { "cell_type": "markdown", - "id": "104", + "id": "106", "metadata": {}, "source": [ "\n", @@ -1397,7 +1397,7 @@ }, { "cell_type": "markdown", - "id": "105", + "id": "107", "metadata": { "tags": [] }, @@ -1407,7 +1407,7 @@ }, { "cell_type": "markdown", - "id": "106", + "id": "108", "metadata": {}, "source": [ "# Quiz\n", @@ -1418,7 +1418,7 @@ { "cell_type": "code", "execution_count": null, - "id": "107", + "id": "109", "metadata": {}, "outputs": [], "source": [ @@ -1429,7 +1429,7 @@ }, { "cell_type": "markdown", - "id": "108", + "id": "110", "metadata": {}, "source": [ "# Exercises" @@ -1438,7 +1438,7 @@ { "cell_type": "code", "execution_count": null, - "id": "109", + "id": "111", "metadata": {}, "outputs": [], "source": [ @@ -1447,7 +1447,7 @@ }, { "cell_type": "markdown", - "id": "110", + "id": "112", "metadata": {}, "source": [ "## Exercise 1: Ice cream scoop" @@ -1455,7 +1455,7 @@ }, { "cell_type": "markdown", - "id": "111", + "id": "113", "metadata": {}, "source": [ "Define a class `Scoop` that represents a single scoop of ice cream. Each scoop should have a **single** attribute, `flavor`, a string that you can initialize when you create the instance of `Scoop`.\n", @@ -1475,7 +1475,7 @@ { "cell_type": "code", "execution_count": null, - "id": "112", + "id": "114", "metadata": {}, "outputs": [], "source": [ @@ -1499,7 +1499,7 @@ }, { "cell_type": "markdown", - "id": "113", + "id": "115", "metadata": {}, "source": [ "## Exercise 2: Ice cream bowl\n", @@ -1524,7 +1524,7 @@ { "cell_type": "code", "execution_count": null, - "id": "114", + "id": "116", "metadata": {}, "outputs": [], "source": [ @@ -1550,7 +1550,7 @@ }, { "cell_type": "markdown", - "id": "115", + "id": "117", "metadata": {}, "source": [ "## Exercise 3: Ice cream shop" @@ -1558,7 +1558,7 @@ }, { "cell_type": "markdown", - "id": "116", + "id": "118", "metadata": {}, "source": [ "Create a class `Shop` that sells many ice cream flavours. \n", @@ -1583,7 +1583,7 @@ { "cell_type": "code", "execution_count": null, - "id": "117", + "id": "119", "metadata": {}, "outputs": [], "source": [ @@ -1608,7 +1608,7 @@ }, { "cell_type": "markdown", - "id": "118", + "id": "120", "metadata": { "tags": [] }, @@ -1624,7 +1624,7 @@ }, { "cell_type": "markdown", - "id": "119", + "id": "121", "metadata": {}, "source": [ "An **Intcode program** is a list of integers separated by commas (e.g. `1,0,0,3,99`). The first number is called \"position `0`\". Each number represents either an **opcode** or a **position**.\n", @@ -1675,7 +1675,7 @@ }, { "cell_type": "markdown", - "id": "120", + "id": "122", "metadata": {}, "source": [ "
\n", @@ -1686,7 +1686,7 @@ }, { "cell_type": "markdown", - "id": "121", + "id": "123", "metadata": {}, "source": [ "Here are the initial and final states of a few small programs:\n", @@ -1698,7 +1698,7 @@ }, { "cell_type": "markdown", - "id": "122", + "id": "124", "metadata": {}, "source": [ "
\n", @@ -1714,7 +1714,7 @@ { "cell_type": "code", "execution_count": null, - "id": "123", + "id": "125", "metadata": { "tags": [] }, From 26e357e169085e97169152648cb113c4cc0dc0f8 Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Mon, 10 Nov 2025 14:46:05 +0100 Subject: [PATCH 03/14] more text fixes --- 05_object_oriented_programming.ipynb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/05_object_oriented_programming.ipynb b/05_object_oriented_programming.ipynb index b1eb5268..a308e560 100644 --- a/05_object_oriented_programming.ipynb +++ b/05_object_oriented_programming.ipynb @@ -273,7 +273,8 @@ "id": "20", "metadata": {}, "source": [ - "You saw note that we were able to retrieve the `width` and `height` attributes (properties) using a dot notation, where we specify the object we are interested in, then a dot, then the attribute we are interested in.\n", + "You previously saw that we were able to retrieve the `width` and `height` attributes (properties) using a **dot notation**:\n", + "we specify the object we are interested in, then a dot, then the attribute we are interested in.\n", "\n", "So, for example:" ] @@ -295,7 +296,7 @@ "source": [ "We can add callable attributes to our class (methods), that will also be referenced using the dot notation.\n", "\n", - "Again, the methods will require the first argument to be the object being used when the method is called, that is, `self`." + "Again, the methods will require the first argument to be the object being used when the method is called (so, `self`)." ] }, { @@ -348,7 +349,8 @@ "id": "26", "metadata": {}, "source": [ - "When we ran the above line of code, our object was `r1`, so when `area` was called, Python called the method `area` in the `Rectangle` class automatically, passing `r1` as the argument to the `self` parameter." + "When we ran the above line of code, our object was `r1`.\n", + "When `area()` was called, Python called that method in the `Rectangle` class automatically, passing `r1` as the argument to the `self` parameter." ] }, { From ff6a9e970d3fa3355e0b4c8ec7893d8bdd1a522e Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Mon, 10 Nov 2025 16:29:50 +0100 Subject: [PATCH 04/14] final fixes --- 05_object_oriented_programming.ipynb | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/05_object_oriented_programming.ipynb b/05_object_oriented_programming.ipynb index a308e560..91b2486f 100644 --- a/05_object_oriented_programming.ipynb +++ b/05_object_oriented_programming.ipynb @@ -141,8 +141,6 @@ "
\n", "\n", "You can create **instances** of `Rectangle` by calling the class and passing the required arguments (except `self`).\n", - "For `Rectangle`, those arguments are passed to the `__init__` method as the second and third arguments.\n", - "\n", "Remember that you **don't** have to pass `self` when creating a new instance of a class." ] }, @@ -264,7 +262,6 @@ "But since they are just values (they are **not** functions), we call them **properties**.\n", "\n", "Attributes that are callables (so, functions) are called **methods**.\n", - "\n", "Properties and methods together are called **attributes** of a class." ] }, @@ -275,7 +272,6 @@ "source": [ "You previously saw that we were able to retrieve the `width` and `height` attributes (properties) using a **dot notation**:\n", "we specify the object we are interested in, then a dot, then the attribute we are interested in.\n", - "\n", "So, for example:" ] }, @@ -417,7 +413,9 @@ "Otherwise, Python will fallback to the default, built-in definition, or it will raise an error if it doesn't know what to do.\n", "\n", "These are also called **dunder methods**, that is, \"double-underscore methods\", because their names look like `__method__`.\n", - "There are special **attributes** as well." + "\n", + "There are special **attributes** as well.\n", + "We will not cover them during this course, but you can read about them [here](https://docs.python.org/3/reference/datamodel.html#object.__lt__) if you want to." ] }, { @@ -548,7 +546,7 @@ "\n", "If the `__str__` method is present, then Python will call it and return that value.\n", "\n", - "There is actually another one called `__repr__` which is related, but we will just focus on `__str__` for now." + "There is actually another one called `__repr__` which is related, but let's see an example on `__str__` first." ] }, { @@ -1044,7 +1042,7 @@ "\n", "Again, Python has special methods we can use to provide that functionality.\n", "\n", - "These are `__lt__`, `__gt__`, `__le__` methods. There are [many more](https://docs.python.org/3/reference/datamodel.html)!" + "These are `__lt__`, `__gt__`, `__le__` methods. There are [many more](https://docs.python.org/3/reference/datamodel.html#callable-types)!" ] }, { @@ -1054,7 +1052,7 @@ "source": [ "
\n", "

Note

\n", - " While we can define custom methods for the comparison operators, it doesn't mean it makes sense. In this example with the Rectangle class, the meaning of the greater/less than operations is not formally defined. We chose to compare the rectangles areas but that's completely arbitrary.\n", + " While we can define custom methods for the comparison operators, it does not mean it always makes sense. In this example with the Rectangle class, the meaning of the greater/less than operations is not formally defined. We chose to compare the rectangles areas but that's completely arbitrary.\n", "
" ] }, @@ -1259,7 +1257,7 @@ "metadata": {}, "source": [ "But what if you want to define area as one of Rectangle's properties?\n", - "The only difference with the other ones, it that this property requires some additional logic in the background, in order for its value to be calculated." + "The only difference with the other ones, is that this property requires some additional logic in the background, in order for its value to be calculated." ] }, { @@ -1466,8 +1464,6 @@ "The output should be `Ice cream scoop with flavor ''`, where `` is the actual scoop's flavor.\n", "**Pay attention to the single quotes!**\n", "\n", - "Modify the `solution_ice_cream_scoop` function to return a list of Scoop instances, one for each flavor of the tuple of flavors that are provided to you as an argument to the function.\n", - "\n", "
\n", "

Question

\n", " Complete the solution function such that it creates an instance of the Scoop class for every flavor contained in the function parameter flavors. This function should return a list that collects the Scoop instances of the ice cream flavors.\n", @@ -1572,7 +1568,7 @@ "
    \n", "
  • In the __init__ method of the Shop class, you should define an attribute called flavors, that acts as a container to hold the available flavours.
  • \n", "
  • You can use __eq__ and __lt__ to define __le__.
  • \n", - "
  • You should just compare the amount of flavors each shop has.
  • \n", + "
  • You should just compare the amount of flavors each shop has.
  • \n", "
\n", "
\n", "\n", From 45c95f70d799c774bb13b7e7028d9147812660bc Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:31:44 +0100 Subject: [PATCH 05/14] Update tutorial/tests/test_05_object_oriented_programming.py Co-authored-by: Edoardo Baldi --- tutorial/tests/test_05_object_oriented_programming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/tests/test_05_object_oriented_programming.py b/tutorial/tests/test_05_object_oriented_programming.py index b0984213..be2c15bc 100644 --- a/tutorial/tests/test_05_object_oriented_programming.py +++ b/tutorial/tests/test_05_object_oriented_programming.py @@ -352,7 +352,7 @@ class Bowl: def __init__(self): self.scoops = [] - def add_scoops(self, *new_scoops: list["Scoop"]) -> None: + def add_scoops(self, *new_scoops: "Scoop") -> None: for one_scoop in new_scoops: self.scoops.append(one_scoop) From 8419cd8406a391498930aa3bc2f9cfecab909a14 Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Mon, 10 Nov 2025 20:30:17 +0100 Subject: [PATCH 06/14] fix links --- 05_object_oriented_programming.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/05_object_oriented_programming.ipynb b/05_object_oriented_programming.ipynb index 91b2486f..6048e431 100644 --- a/05_object_oriented_programming.ipynb +++ b/05_object_oriented_programming.ipynb @@ -415,7 +415,7 @@ "These are also called **dunder methods**, that is, \"double-underscore methods\", because their names look like `__method__`.\n", "\n", "There are special **attributes** as well.\n", - "We will not cover them during this course, but you can read about them [here](https://docs.python.org/3/reference/datamodel.html#object.__lt__) if you want to." + "We will not cover them during this course, but you can read about them [here](https://docs.python.org/3/reference/datamodel.html#callable-types) if you want to." ] }, { @@ -1042,7 +1042,7 @@ "\n", "Again, Python has special methods we can use to provide that functionality.\n", "\n", - "These are `__lt__`, `__gt__`, `__le__` methods. There are [many more](https://docs.python.org/3/reference/datamodel.html#callable-types)!" + "These are `__lt__`, `__gt__`, `__le__` methods. There are [many more](https://docs.python.org/3/reference/datamodel.html#object.__lt__)!" ] }, { From 3cf99e429310753222af682884fb2de29c62a917 Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:20:42 +0100 Subject: [PATCH 07/14] Update 05_object_oriented_programming.ipynb Co-authored-by: Pascal Su --- 05_object_oriented_programming.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05_object_oriented_programming.ipynb b/05_object_oriented_programming.ipynb index 6048e431..0e60b479 100644 --- a/05_object_oriented_programming.ipynb +++ b/05_object_oriented_programming.ipynb @@ -1156,7 +1156,7 @@ "\n", "Well, Python decided that, since `r1 > r2` was not implemented, it would give `r2 < r1` a try.\n", "And since, `__lt__` **is** defined, it worked!\n", - "It just a matter of swapping the terms in the comparison.\n", + "It is just a matter of swapping the terms in the comparison.\n", "Clever, right? 😎" ] }, From 7e219ff91270dcdcd6582558f51b34ab30ba3ca6 Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Tue, 11 Nov 2025 11:38:53 +0100 Subject: [PATCH 08/14] fix person comparison exercise --- 05_object_oriented_programming.ipynb | 15 +++-- .../test_05_object_oriented_programming.py | 62 +++++++++---------- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/05_object_oriented_programming.ipynb b/05_object_oriented_programming.ipynb index 0e60b479..cb527afd 100644 --- a/05_object_oriented_programming.ipynb +++ b/05_object_oriented_programming.ipynb @@ -983,7 +983,7 @@ "\n", "
\n", "

Question

\n", - " Modify your code from Example 1 so that class Person will now have an additional attribute called age. Then, add a __eq__() comparison method that makes sure that two persons are the same when they have the same first name, last name and age. Create an instance of this class, representing a person, which is being initialized by using the arguments passed in the solution function. Lastly, return the instance.\n", + " Modify your code from Example 1 so that class Person will now have a __eq__() comparison method that makes sure that two persons are the same when they have the same first name and last name. Create two instances of this class, each one representing a person, which is being initialized by using the arguments passed in the solution function. The first and last name of each of the two persons are each in a tuple of strings. Lastly, return a list with the two instances.\n", "
" ] }, @@ -1006,19 +1006,18 @@ "source": [ "%%ipytest\n", "\n", - "def solution_oop_compare_persons(first_name: str, last_name: str, age: int):\n", + "def solution_oop_compare_persons(person_1: tuple[str], person_2: tuple[str]) -> list:\n", " \"\"\"A function that contains the definition of a class Person, and returns an instance of it.\n", "\n", - " Person is a class with three attributes called 'first_name', 'last_name' and 'age'.\n", + " Person is a class with two attributes called 'first_name' and 'last_name'.\n", " Person implements the __eq__() comparison method,\n", - " which should return True when two persons have the same first name, last name and age.\n", + " which should return True when two persons have the same first name and last name.\n", "\n", " Args:\n", - " first_name: a string used to initialize the Person instance\n", - " last_name: a string used to initialize the Person instance\n", - " age: an integer used to initialize the Person instance\n", + " person_1: a tuple containing two strings, the first name and the last name of the first person\n", + " person_2: a tuple containing two strings, the first name and the last name of the second person\n", " Returns:\n", - " - an instance of Person\n", + " - a list containing two instances of Person\n", " \"\"\"\n", "\n", " # Write your solution here\n", diff --git a/tutorial/tests/test_05_object_oriented_programming.py b/tutorial/tests/test_05_object_oriented_programming.py index be2c15bc..3f6fd60f 100644 --- a/tutorial/tests/test_05_object_oriented_programming.py +++ b/tutorial/tests/test_05_object_oriented_programming.py @@ -190,74 +190,72 @@ def test_oop_str_and_repr(first_name, last_name, function_to_test): # -def reference_oop_compare_persons(first_name: str, last_name: str, age: int): +def reference_oop_compare_persons(person_1: tuple[str], person_2: tuple[str]) -> list: class Person: - """A class representing a person with first name, last name and age""" + """A class representing a person with first name and last name""" - def __init__(self, first_name: str, last_name: str, age: int): + def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name - self.age = age def __eq__(self, other): if isinstance(other, Person): - return (self.first_name, self.last_name, self.age) == ( - other.first_name, - other.last_name, - other.age, - ) + return (self.first_name, self.last_name) == (other.first_name, other.last_name) else: return False - return Person(first_name, last_name, age) + return [Person(*person_1), Person(*person_2)] def validate_oop_compare_persons(solution_result): assert not isinstance( solution_result, str | int | float | bool | list | dict | tuple | set - ), "Solution must return a class instance, not a datatype." + ), "The returned list must contain class instances, not datatypes." assert type(solution_result).__module__ != "builtins", ( - "Solution must return an instance of a custom class, not a built-in type." + "The returned list must contain instances of a custom class, not a built-in type." ) assert type(solution_result).__name__ == "Person", ( "The class should be named 'Person'." ) - assert hasattr(solution_result.__eq__, "__closure__"), ( - "Make sure that the class is properly implementing the __eq__() method." - ) # Check the class attributes try: attrs = list(vars(solution_result)) except TypeError: raise SubAssertionError from None - assert len(attrs) == 3, "The class should have 3 attributes." - assert "first_name" in attrs and "last_name" in attrs and "age" in attrs, ( - "The class attributes should be 'first_name', 'last_name' and 'age'." + assert len(attrs) == 2, "The class should have 2 attributes." + assert "first_name" in attrs and "last_name" in attrs, ( + "The class attributes should be 'first_name' and 'last_name'." + ) + assert hasattr(solution_result.__eq__, "__closure__"), ( + "Make sure that the class is properly implementing the __eq__() method." ) @pytest.mark.parametrize( - "first_name_a, last_name_a, age_a, first_name_b, last_name_b, age_b", + "person_1, person_2", [ - ("Jane", "Doe", 30, "John", "Doe", 25), - ("John", "Smith", 25, "John", "Doe", 25), - ("John", "Doe", 20, "John", "Doe", 25), - ("John", "Doe", 25, "John", "Doe", 25), + (("Jane", "Doe"), ("John", "Doe")), + (("John", "Smith"), ("John", "Doe")), + (("John", "Doe"), ("John", "Doe")), ], ) def test_oop_compare_persons( - first_name_a, last_name_a, age_a, first_name_b, last_name_b, age_b, function_to_test + person_1, person_2, function_to_test ): - solution_result_a = function_to_test(first_name_a, last_name_a, age_a) - reference_result_a = reference_oop_compare_persons(first_name_a, last_name_a, age_a) + solution_result = function_to_test(person_1, person_2) + reference_result = reference_oop_compare_persons(person_1, person_2) - solution_result_b = function_to_test(first_name_b, last_name_b, age_b) - reference_result_b = reference_oop_compare_persons(first_name_b, last_name_b, age_b) + assert isinstance(solution_result, list), "Solution must return a list." + assert len(solution_result) == 2, ( + "The returned list must contain two persons." + ) - validate_oop_compare_persons(solution_result_a) - assert (solution_result_a == solution_result_b) == ( - reference_result_a == reference_result_b - ), "Comparison failed." + for res in solution_result: + validate_oop_compare_persons(res) + + solution_comparison = (solution_result[0] == solution_result[1]) + reference_comparison = (reference_result[0] == reference_result[1]) + assert solution_comparison == reference_comparison, "Person comparison failed." # From 98ab6ea053ecda442a4625327f4bcef0b19f506e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:39:05 +0000 Subject: [PATCH 09/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../test_05_object_oriented_programming.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tutorial/tests/test_05_object_oriented_programming.py b/tutorial/tests/test_05_object_oriented_programming.py index 3f6fd60f..618bd1b5 100644 --- a/tutorial/tests/test_05_object_oriented_programming.py +++ b/tutorial/tests/test_05_object_oriented_programming.py @@ -200,7 +200,10 @@ def __init__(self, first_name, last_name): def __eq__(self, other): if isinstance(other, Person): - return (self.first_name, self.last_name) == (other.first_name, other.last_name) + return (self.first_name, self.last_name) == ( + other.first_name, + other.last_name, + ) else: return False @@ -239,22 +242,18 @@ def validate_oop_compare_persons(solution_result): (("John", "Doe"), ("John", "Doe")), ], ) -def test_oop_compare_persons( - person_1, person_2, function_to_test -): +def test_oop_compare_persons(person_1, person_2, function_to_test): solution_result = function_to_test(person_1, person_2) reference_result = reference_oop_compare_persons(person_1, person_2) assert isinstance(solution_result, list), "Solution must return a list." - assert len(solution_result) == 2, ( - "The returned list must contain two persons." - ) + assert len(solution_result) == 2, "The returned list must contain two persons." for res in solution_result: validate_oop_compare_persons(res) - solution_comparison = (solution_result[0] == solution_result[1]) - reference_comparison = (reference_result[0] == reference_result[1]) + solution_comparison = solution_result[0] == solution_result[1] + reference_comparison = reference_result[0] == reference_result[1] assert solution_comparison == reference_comparison, "Person comparison failed." From 9cf0be7411798a5421db31aa3128fd65db149ff1 Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Tue, 11 Nov 2025 11:58:09 +0100 Subject: [PATCH 10/14] fix ice cream shop exercise --- 05_object_oriented_programming.ipynb | 11 ++++---- .../test_05_object_oriented_programming.py | 26 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/05_object_oriented_programming.ipynb b/05_object_oriented_programming.ipynb index cb527afd..c307d04d 100644 --- a/05_object_oriented_programming.ipynb +++ b/05_object_oriented_programming.ipynb @@ -1573,7 +1573,7 @@ "\n", "
\n", "

Question

\n", - " Complete the solution function so that it creates an ice cream shop, which should sell the flavors provided by the function parameter flavors. The output of this function should be the instance of the shop you just created.\n", + " Complete the solution function so that it creates two ice cream shops, each of which should sell the flavors provided by the respective function parameter, so flavors_1 or flavors_2. The output of this function should be a list containing the two Shop instances.\n", "
" ] }, @@ -1586,17 +1586,18 @@ "source": [ "%%ipytest\n", "\n", - "def solution_ice_cream_shop(flavors: list[str]):\n", - " \"\"\"A function that contains the definition of a class Shop and returns an instance of it.\n", + "def solution_ice_cream_shop(flavors_1: list[str], flavors_2: list[str]) -> list:\n", + " \"\"\"A function that contains the definition of a class Shop and returns a list with two Shop instances.\n", "\n", " Shop is a class with one attribute called 'flavors', which acts as a container for the available ice cream flavors.\n", " Shop also implements the __eq__() and __lt__() methods to compare the flavor containers.\n", " It also defines the __le__() method by using the two other comparison methods.\n", "\n", " Args:\n", - " flavors: all available ice cream flavors of the Shop\n", + " flavors_1: all available ice cream flavors of the first Shop\n", + " flavors_2: all available ice cream flavors of the second Shop\n", " Returns:\n", - " - a Shop instance\n", + " - a list with two Shop instances\n", " \"\"\"\n", "\n", " # Write your solution here\n", diff --git a/tutorial/tests/test_05_object_oriented_programming.py b/tutorial/tests/test_05_object_oriented_programming.py index 618bd1b5..2d9efeae 100644 --- a/tutorial/tests/test_05_object_oriented_programming.py +++ b/tutorial/tests/test_05_object_oriented_programming.py @@ -432,7 +432,7 @@ def test_ice_cream_bowl(flavors, function_to_test) -> None: # -def reference_ice_cream_shop(flavors: list[str]): +def reference_ice_cream_shop(flavors_1: list[str], flavors_2: list[str]) -> list: class Shop: """A class representing an ice cream shop""" @@ -459,7 +459,7 @@ def __le__(self, other): return self < other or self == other return False - return Shop(flavors) + return [Shop(flavors_1), Shop(flavors_2)] def validate_ice_cream_shop(solution_result): @@ -493,24 +493,26 @@ def validate_ice_cream_shop(solution_result): @pytest.mark.parametrize( - "flavors_a, flavors_b", + "flavors_1, flavors_2", [ (["chocolate", "vanilla", "stracciatella"], ["caramel", "strawberry", "mango"]), (["vanilla", "stracciatella"], ["chocolate", "vanilla", "mango"]), (["vanilla", "mango"], ["chocolate"]), ], ) -def test_ice_cream_shop(flavors_a, flavors_b, function_to_test) -> None: - solution_result_a = function_to_test(flavors_a) - reference_result_a = reference_ice_cream_shop(flavors_a) +def test_ice_cream_shop(flavors_1, flavors_2, function_to_test) -> None: + solution_result = function_to_test(flavors_1, flavors_2) + reference_result = reference_ice_cream_shop(flavors_1, flavors_2) - solution_result_b = function_to_test(flavors_b) - reference_result_b = reference_ice_cream_shop(flavors_b) + assert isinstance(solution_result, list), "Solution must return a list." + assert len(solution_result) == 2, "The returned list must contain two ice cream shops." + + for res in solution_result: + validate_ice_cream_shop(res) - validate_ice_cream_shop(solution_result_a) - assert (solution_result_a <= solution_result_b) == ( - reference_result_a <= reference_result_b - ), "Comparison failed." + solution_comparison = solution_result[0] <= solution_result[1] + reference_comparison = reference_result[0] <= reference_result[1] + assert solution_comparison == reference_comparison, "Ice cream shop comparison failed." # From bb3f6764f7557f788a1208d66bff66ef4897b3c5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:58:22 +0000 Subject: [PATCH 11/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tutorial/tests/test_05_object_oriented_programming.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tutorial/tests/test_05_object_oriented_programming.py b/tutorial/tests/test_05_object_oriented_programming.py index 2d9efeae..50093780 100644 --- a/tutorial/tests/test_05_object_oriented_programming.py +++ b/tutorial/tests/test_05_object_oriented_programming.py @@ -505,14 +505,18 @@ def test_ice_cream_shop(flavors_1, flavors_2, function_to_test) -> None: reference_result = reference_ice_cream_shop(flavors_1, flavors_2) assert isinstance(solution_result, list), "Solution must return a list." - assert len(solution_result) == 2, "The returned list must contain two ice cream shops." + assert len(solution_result) == 2, ( + "The returned list must contain two ice cream shops." + ) for res in solution_result: validate_ice_cream_shop(res) solution_comparison = solution_result[0] <= solution_result[1] reference_comparison = reference_result[0] <= reference_result[1] - assert solution_comparison == reference_comparison, "Ice cream shop comparison failed." + assert solution_comparison == reference_comparison, ( + "Ice cream shop comparison failed." + ) # From a70bef9e115841d85b70c55b06d39b2477e3639b Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:22:12 +0100 Subject: [PATCH 12/14] Update 05_object_oriented_programming.ipynb Co-authored-by: Pascal Su --- 05_object_oriented_programming.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05_object_oriented_programming.ipynb b/05_object_oriented_programming.ipynb index c307d04d..8c6805f5 100644 --- a/05_object_oriented_programming.ipynb +++ b/05_object_oriented_programming.ipynb @@ -1007,7 +1007,7 @@ "%%ipytest\n", "\n", "def solution_oop_compare_persons(person_1: tuple[str], person_2: tuple[str]) -> list:\n", - " \"\"\"A function that contains the definition of a class Person, and returns an instance of it.\n", + " \"\"\"A function that contains the definition of a class Person, and returns two instances of it.\n", "\n", " Person is a class with two attributes called 'first_name' and 'last_name'.\n", " Person implements the __eq__() comparison method,\n", From 0bfa36089590193158cb935aebda326dc5c70dbf Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Tue, 11 Nov 2025 14:46:22 +0100 Subject: [PATCH 13/14] fixes after review --- .../test_05_object_oriented_programming.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/tutorial/tests/test_05_object_oriented_programming.py b/tutorial/tests/test_05_object_oriented_programming.py index 50093780..a923a7f0 100644 --- a/tutorial/tests/test_05_object_oriented_programming.py +++ b/tutorial/tests/test_05_object_oriented_programming.py @@ -190,7 +190,7 @@ def test_oop_str_and_repr(first_name, last_name, function_to_test): # -def reference_oop_compare_persons(person_1: tuple[str], person_2: tuple[str]) -> list: +def reference_oop_compare_persons(person_1: tuple[str, ...], person_2: tuple[str, ...]) -> list: class Person: """A class representing a person with first name and last name""" @@ -262,7 +262,7 @@ def test_oop_compare_persons(person_1, person_2, function_to_test): # -def reference_ice_cream_scoop(flavors: tuple[str]) -> list: +def reference_ice_cream_scoop(flavors: tuple[str, ...]) -> list: class Scoop: """A class representing a single scoop of ice cream""" @@ -333,7 +333,7 @@ def test_ice_cream_scoop(flavors, function_to_test) -> None: # -def reference_ice_cream_bowl(flavors: tuple[str]): +def reference_ice_cream_bowl(flavors: tuple[str, ...]): class Scoop: """A class representing a single scoop of ice cream""" @@ -512,10 +512,20 @@ def test_ice_cream_shop(flavors_1, flavors_2, function_to_test) -> None: for res in solution_result: validate_ice_cream_shop(res) - solution_comparison = solution_result[0] <= solution_result[1] - reference_comparison = reference_result[0] <= reference_result[1] - assert solution_comparison == reference_comparison, ( - "Ice cream shop comparison failed." + assert ( + (solution_result[0] < solution_result[1]) == (reference_result[0] < reference_result[1]) + ), ( + "Ice cream shop __lt__ comparison failed." + ) + assert ( + (solution_result[0] == solution_result[1]) == (reference_result[0] == reference_result[1]) + ), ( + "Ice cream shop __eq__ comparison failed." + ) + assert ( + (solution_result[0] <= solution_result[1]) == (reference_result[0] <= reference_result[1]) + ), ( + "Ice cream shop __le__ comparison failed." ) From 41f514a06700001f2e16512dba3b29c81fbe4838 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:46:35 +0000 Subject: [PATCH 14/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../test_05_object_oriented_programming.py | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tutorial/tests/test_05_object_oriented_programming.py b/tutorial/tests/test_05_object_oriented_programming.py index a923a7f0..92c7686f 100644 --- a/tutorial/tests/test_05_object_oriented_programming.py +++ b/tutorial/tests/test_05_object_oriented_programming.py @@ -190,7 +190,9 @@ def test_oop_str_and_repr(first_name, last_name, function_to_test): # -def reference_oop_compare_persons(person_1: tuple[str, ...], person_2: tuple[str, ...]) -> list: +def reference_oop_compare_persons( + person_1: tuple[str, ...], person_2: tuple[str, ...] +) -> list: class Person: """A class representing a person with first name and last name""" @@ -512,21 +514,15 @@ def test_ice_cream_shop(flavors_1, flavors_2, function_to_test) -> None: for res in solution_result: validate_ice_cream_shop(res) - assert ( - (solution_result[0] < solution_result[1]) == (reference_result[0] < reference_result[1]) - ), ( - "Ice cream shop __lt__ comparison failed." - ) - assert ( - (solution_result[0] == solution_result[1]) == (reference_result[0] == reference_result[1]) - ), ( - "Ice cream shop __eq__ comparison failed." - ) - assert ( - (solution_result[0] <= solution_result[1]) == (reference_result[0] <= reference_result[1]) - ), ( - "Ice cream shop __le__ comparison failed." - ) + assert (solution_result[0] < solution_result[1]) == ( + reference_result[0] < reference_result[1] + ), "Ice cream shop __lt__ comparison failed." + assert (solution_result[0] == solution_result[1]) == ( + reference_result[0] == reference_result[1] + ), "Ice cream shop __eq__ comparison failed." + assert (solution_result[0] <= solution_result[1]) == ( + reference_result[0] <= reference_result[1] + ), "Ice cream shop __le__ comparison failed." #