diff --git a/13_object_oriented_programming_advanced.ipynb b/13_object_oriented_programming_advanced.ipynb index cd0c03c5..91842885 100644 --- a/13_object_oriented_programming_advanced.ipynb +++ b/13_object_oriented_programming_advanced.ipynb @@ -22,7 +22,7 @@ " - [Composition](#Composition)\n", " - [`super()`](#super())\n", " - [Quiz on Inheritance](#Quiz-on-Inheritance)\n", - " - [Exercise: Child Eye Color](#Exercise:-Child-Eye-Color)\n", + " - [Exercise: Triathlete](#Exercise:-Triathlete)\n", " - [Abstract Classes](#Abstract-Classes)\n", " - [Quiz on Abstraction](#Quiz-on-Abstraction)\n", " - [Exercise: Banking System](#Exercise:-Banking-System)\n", @@ -494,22 +494,21 @@ "id": "36", "metadata": {}, "source": [ - "### Exercise: Child Eye Color\n", + "### Exercise: Triathlete\n", "\n", - "In this exercise, we will implement the following simplified theory on how to predict a child's eye color, based on the eye color of its parents.\n", + "A triathlon is a race consisting of three disciplines: **swimming**, **cycling**, and **running**, completed back-to-back.\n", "\n", - "We assume that the only existing eye colors are blue and brown. We also assume the following rules:\n", - "- If both parents have brown eyes, their child will also have brown eyes.\n", - "- If both parents have blue eyes, their child will also have blue eyes.\n", - "- If one parent has brown eyes and the other one has blue eyes, the dominant color will be brown.\n", + "We model this using the following class hierarchy:\n", + "- **Swimmer** with attribute `swim_time`.\n", + "- **Cyclist** with attribute `bike_time`.\n", + "- **Runner** with attribute `run_time`.\n", + "- **Triathlete** with attributes `name` and `total_time`, which is the sum of all three split times inherited from `Swimmer`, `Cyclist`, and `Runner`.\n", "\n", "
eye_color.Triathlete using the arguments passed to the solution function.__init__ method is called when an object is created and is used to initialize the object.",
+ "__repr__": "The __repr__ method is used to provide an unambiguous string representation of an object.",
+ "__eq__": "The __eq__ method is used to define equality comparison between objects.",
},
correct_answer="__init__",
hint="This method is automatically called when an object is instantiated.",
@@ -27,11 +27,11 @@ def __init__(self, title=""):
)
q3 = Question(
- question="What is the purpose of the `super()` function in Python?",
+ question="What is the purpose of the super() function in Python?",
options={
- "To call a method from the parent class": "Correct! `super()` is used to call a method from the parent class.",
- "To create a derived class": "Incorrect. `super()` is not used for creating derived classes.",
- "To initialize an object": "Incorrect. Object initialization is done using the `__init__` method.",
+ "To call a method from the parent class": "Correct! super() is used to call a method from the parent class.",
+ "To create a derived class": "Incorrect. super() is not used for creating derived classes.",
+ "To initialize an object": "Incorrect. Object initialization is done using the __init__ method.",
},
correct_answer="To call a method from the parent class",
hint="This function is used to access inherited methods.",
@@ -58,9 +58,9 @@ def __init__(self, title=""):
q1 = Question(
question="Which module in Python is used to create abstract classes?",
options={
- "abc": "Correct! The `abc` module provides the infrastructure for defining abstract base classes.",
- "abstract": "There is no module named `abstract` in Python.",
- "abstractmodule": "There is no module named `abstractmodule` in Python.",
+ "abc": "Correct! The abc module provides the infrastructure for defining abstract base classes.",
+ "abstract": "There is no module named abstract in Python.",
+ "abstractmodule": "There is no module named abstractmodule in Python.",
},
correct_answer="abc",
hint="This module's name is an abbreviation for 'Abstract Base Classes'.",
@@ -100,22 +100,22 @@ def __init__(self, title=""):
question="Which decorator is used to define a method that belongs to the class rather than an instance?",
options={
"@staticmethod": "Incorrect. A static method does not belong to the class or instance.",
- "@classmethod": "Correct! A class method belongs to the class and takes `cls` as its first parameter.",
- "@property": "Incorrect. The `@property` decorator is used to define getter methods.",
- "@abstractmethod": "Incorrect. The `@abstractmethod` decorator is used in abstract classes.",
+ "@classmethod": "Correct! A class method belongs to the class and takes cls as its first parameter.",
+ "@property": "Incorrect. The @property decorator is used to define getter methods.",
+ "@abstractmethod": "Incorrect. The @abstractmethod decorator is used in abstract classes.",
},
correct_answer="@classmethod",
- hint="This method takes `cls` as its first parameter.",
+ hint="This method takes cls as its first parameter.",
shuffle=True,
)
q2 = Question(
- question="What is the purpose of the `@property` decorator?",
+ question="What is the purpose of the @property decorator?",
options={
- "To define a computed attribute": "Correct! The `@property` decorator is used to define computed attributes.",
- "To define a static method": "Incorrect. Static methods are defined using the `@staticmethod` decorator.",
- "To define a class method": "Incorrect. Class methods are defined using the `@classmethod` decorator.",
- "To define an abstract method": "Incorrect. Abstract methods are defined using the `@abstractmethod` decorator.",
+ "To define a computed attribute": "Correct! The @property decorator is used to define computed attributes.",
+ "To define a static method": "Incorrect. Static methods are defined using the @staticmethod decorator.",
+ "To define a class method": "Incorrect. Class methods are defined using the @classmethod decorator.",
+ "To define an abstract method": "Incorrect. Abstract methods are defined using the @abstractmethod decorator.",
},
correct_answer="To define a computed attribute",
hint="This decorator allows you to define methods that can be accessed like attributes.",
@@ -126,9 +126,9 @@ def __init__(self, title=""):
question="Which decorator is used to define a method that does not access the class or instance?",
options={
"@staticmethod": "Correct! A static method does not access the class or instance.",
- "@classmethod": "Incorrect. A class method accesses the class using `cls`.",
- "@property": "Incorrect. The `@property` decorator is used to define getter methods.",
- "@abstractmethod": "Incorrect. The `@abstractmethod` decorator is used in abstract classes.",
+ "@classmethod": "Incorrect. A class method accesses the class using cls.",
+ "@property": "Incorrect. The @property decorator is used to define getter methods.",
+ "@abstractmethod": "Incorrect. The @abstractmethod decorator is used in abstract classes.",
},
correct_answer="@staticmethod",
hint="This method is often used for utility functions.",
@@ -136,10 +136,10 @@ def __init__(self, title=""):
)
q4 = Question(
- question="A method with which decorator takes `cls` as its first parameter?",
+ question="A method with which decorator takes cls as its first parameter?",
options={
- "@classmethod": "Correct! A class method is bound to a class rather than its instances and the parameter `cls` represents the class itself.",
- "@staticmethod": "A static method does not have access to `cls` or `self` and cannot modify the class state.",
+ "@classmethod": "Correct! A class method is bound to a class rather than its instances and the parameter cls represents the class itself.",
+ "@staticmethod": "A static method does not have access to cls or self and cannot modify the class state.",
"@abstractmethod": "This decorator defines a method in an abstract class that **must** be implemented by all its concrete subclasses.",
},
correct_answer="@classmethod",
@@ -148,27 +148,27 @@ def __init__(self, title=""):
)
q5 = Question(
- question="What is the purpose of the `@classmethod` decorator?",
+ question="What is the purpose of the @classmethod decorator?",
options={
- "To define a method that belongs to the class rather than an instance": "Correct! A class method belongs to the class and takes `cls` as its first parameter.",
+ "To define a method that belongs to the class rather than an instance": "Correct! A class method belongs to the class and takes cls as its first parameter.",
"To define a method that does not access the class or instance": "Incorrect. This describes a static method.",
- "To define a computed attribute": "Incorrect. Computed attributes are defined using the `@property` decorator.",
- "To define an abstract method": "Incorrect. Abstract methods are defined using the `@abstractmethod` decorator.",
+ "To define a computed attribute": "Incorrect. Computed attributes are defined using the @property decorator.",
+ "To define an abstract method": "Incorrect. Abstract methods are defined using the @abstractmethod decorator.",
},
correct_answer="To define a method that belongs to the class rather than an instance",
- hint="This method takes `cls` as its first parameter.",
+ hint="This method takes cls as its first parameter.",
shuffle=True,
)
q6 = Question(
- question="What is the difference between `@staticmethod` and `@classmethod`?",
+ question="What is the difference between @staticmethod and @classmethod?",
options={
- "`@staticmethod` does not access the class or instance, while `@classmethod` takes `cls` as its first parameter": "Correct! This is the key difference between the two decorators.",
- "`@staticmethod` is used for utility functions, while `@classmethod` is used for abstract methods": "Incorrect. Abstract methods are unrelated to these decorators.",
- "`@staticmethod` is faster than `@classmethod`": "Incorrect. Performance is not the defining difference.",
- "`@staticmethod` is used for computed attributes, while `@classmethod` is used for class-level attributes": "Incorrect. Computed attributes are defined using `@property`.",
+ "@staticmethod does not access the class or instance, while @classmethod takes cls as its first parameter": "Correct! This is the key difference between the two decorators.",
+ "@staticmethod is used for utility functions, while @classmethod is used for abstract methods": "Incorrect. Abstract methods are unrelated to these decorators.",
+ "@staticmethod is faster than @classmethod": "Incorrect. Performance is not the defining difference.",
+ "@staticmethod is used for computed attributes, while @classmethod is used for class-level attributes": "Incorrect. Computed attributes are defined using @property.",
},
- correct_answer="`@staticmethod` does not access the class or instance, while `@classmethod` takes `cls` as its first parameter",
+ correct_answer="@staticmethod does not access the class or instance, while @classmethod takes cls as its first parameter",
hint="Think about the parameters each decorator uses.",
shuffle=True,
)
@@ -207,9 +207,9 @@ def __init__(self, title=""):
question="What is the purpose of encapsulation in OOP?",
options={
"To bundle data and methods into a single unit": "Correct! Encapsulation bundles data and methods into a single unit.",
- "To define abstract methods": "Incorrect. Abstract methods are defined using the `abc` module.",
+ "To define abstract methods": "Incorrect. Abstract methods are defined using the abc module.",
"To create a class that cannot be inherited": "Incorrect. Encapsulation does not restrict inheritance.",
- "To define static methods": "Incorrect. Static methods are defined using the `@staticmethod` decorator.",
+ "To define static methods": "Incorrect. Static methods are defined using the @staticmethod decorator.",
},
correct_answer="To bundle data and methods into a single unit",
hint="Encapsulation is one of the fundamental principles of OOP.",
@@ -233,11 +233,11 @@ def __init__(self, title=""):
class OopAdvancedAttrsDataclasses(Quiz):
def __init__(self, title=""):
q1 = Question(
- question="What is something that `attrs` provides but `dataclasses` doesn't?",
+ question="What is something that attrs provides but dataclasses doesn't?",
options={
- "__init__()": "Both packages automatically generate `__init__()`: `dataclasses` uses the `@dataclass` decorator, while `attrs` uses `@define`.",
- "__repr__()": "Both packages automatically generate `__repr__()` to help you easily print a class instance.",
- "validators": "Correct! You need to define the attribute as a `field()` and then use the validator decorator.",
+ "__init__()": "Both packages automatically generate __init__(): dataclasses uses the @dataclass decorator, while attrs uses @define.",
+ "__repr__()": "Both packages automatically generate __repr__() to help you easily print a class instance.",
+ "validators": "Correct! You need to define the attribute as a field() and then use the validator decorator.",
},
correct_answer="validators",
hint="",
diff --git a/tutorial/tests/test_13_object_oriented_programming_advanced.py b/tutorial/tests/test_13_object_oriented_programming_advanced.py
index 8dee209b..bd3f2ce3 100644
--- a/tutorial/tests/test_13_object_oriented_programming_advanced.py
+++ b/tutorial/tests/test_13_object_oriented_programming_advanced.py
@@ -11,83 +11,91 @@ def __init__(self):
#
-# Exercise 1: Child Eye Color
+# Exercise 1: Triathlete
#
-def reference_child_eye_color(mother_eye_color: str, father_eye_color: str):
- class Mother:
- def __init__(self, eye_color: str):
- self.eye_color_mother = eye_color
-
- class Father:
- def __init__(self, eye_color: str):
- self.eye_color_father = eye_color
-
- class Child(Mother, Father):
- def __init__(self, eye_color_mother: str, eye_color_father: str):
- Mother.__init__(self, eye_color_mother)
- Father.__init__(self, eye_color_father)
- self.eye_color = self.set_eye_color()
-
- def set_eye_color(self):
- if self.eye_color_mother == self.eye_color_father:
- return self.eye_color_mother
- return "brown"
+def reference_triathlete(
+ name: str, swim_time: float, bike_time: float, run_time: float
+):
+ class Swimmer:
+ def __init__(self, swim_time: float):
+ self.swim_time = swim_time
+
+ class Cyclist:
+ def __init__(self, bike_time: float):
+ self.bike_time = bike_time
+
+ class Runner:
+ def __init__(self, run_time: float):
+ self.run_time = run_time
+
+ class Triathlete(Swimmer, Cyclist, Runner):
+ def __init__(
+ self, name: str, swim_time: float, bike_time: float, run_time: float
+ ):
+ Swimmer.__init__(self, swim_time)
+ Cyclist.__init__(self, bike_time)
+ Runner.__init__(self, run_time)
+ self.total_time = self.swim_time + self.bike_time + self.run_time
+ self.name = name
- return Child(mother_eye_color, father_eye_color)
+ return Triathlete(name, swim_time, bike_time, run_time)
-def validate_child_eye_color(solution_result):
+def validate_triathlete(solution_result):
assert not isinstance(
solution_result, str | int | float | bool | list | dict | tuple | set
), "Solution must return a class instance, not a datatype."
assert type(solution_result).__module__ != "builtins", (
"Solution must return an instance of a custom class, not a built-in type."
)
- assert type(solution_result).__name__ == "Child", (
- "The class should be named 'Child'."
+ assert type(solution_result).__name__ == "Triathlete", (
+ "The class should be named 'Triathlete'."
)
- # Check inheritance by base class names
+ # Check direct base class names
base_class_names = [base.__name__ for base in type(solution_result).__bases__]
- assert "Mother" in base_class_names, (
- "The 'Child' class must inherit from a class named 'Mother'."
+ assert "Swimmer" in base_class_names, (
+ "The 'Triathlete' class must inherit from a class named 'Swimmer'."
+ )
+ assert "Cyclist" in base_class_names, (
+ "The 'Triathlete' class must inherit from a class named 'Cyclist'."
)
- assert "Father" in base_class_names, (
- "The 'Child' class must inherit from a class named 'Father'."
+ assert "Runner" in base_class_names, (
+ "The 'Triathlete' class must inherit from a class named 'Runner'."
)
- # Check the class attributes
+ # Check the instance attributes
try:
attrs = list(vars(solution_result))
except TypeError:
raise SubAssertionError from None
- assert len(attrs) == 3, "The class should have 3 attributes."
- assert "eye_color" in attrs, (
- "The class should have an attribute called 'eye_color'."
+ assert len(attrs) == 5, "Triathlete should have 5 attributes."
+ assert "name" in attrs, "The class should have an attribute called 'name'."
+ assert "swim_time" in attrs, (
+ "The class should have an attribute called 'swim_time'."
)
- assert "eye_color_mother" in attrs, (
- "The class should have an attribute called 'eye_color_mother'."
+ assert "bike_time" in attrs, (
+ "The class should have an attribute called 'bike_time'."
)
- assert "eye_color_father" in attrs, (
- "The class should have an attribute called 'eye_color_father'."
+ assert "run_time" in attrs, "The class should have an attribute called 'run_time'."
+ assert "total_time" in attrs, (
+ "The class should have an attribute called 'total_time'."
)
@pytest.mark.parametrize(
- "mother_eye_color, father_eye_color",
+ "name, swim_time, bike_time, run_time",
[
- ("blue", "blue"),
- ("brown", "brown"),
- ("blue", "brown"),
- ("brown", "blue"),
+ ("Alice", 1.5, 3.0, 1.0),
+ ("Bob", 2.0, 4.5, 1.5),
],
)
-def test_child_eye_color(mother_eye_color, father_eye_color, function_to_test):
- solution_result = function_to_test(mother_eye_color, father_eye_color)
- reference_result = reference_child_eye_color(mother_eye_color, father_eye_color)
+def test_triathlete(name, swim_time, bike_time, run_time, function_to_test):
+ solution_result = function_to_test(name, swim_time, bike_time, run_time)
+ reference_result = reference_triathlete(name, swim_time, bike_time, run_time)
- validate_child_eye_color(solution_result)
- assert solution_result.eye_color == reference_result.eye_color
+ validate_triathlete(solution_result)
+ assert solution_result.total_time == reference_result.total_time
#