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", "
\n", "

Question

\n", " \n", "
" ] @@ -533,19 +532,21 @@ "source": [ "%%ipytest\n", "\n", - "def solution_child_eye_color(mother_eye_color: str, father_eye_color: str) -> list:\n", + "def solution_triathlete(name: str, swim_time: float, bike_time: float, run_time: float):\n", " \"\"\"\n", - " Given the eye colors of the mother and father, defines the eye color of the child.\n", - " The possible eye colors are: brown or blue, with brown being the dominant one.\n", - " This function defines a class Mother and a class Father, which are used to create an instance of the class Child.\n", - " It returns an instance of the class Child with the eye color defined by the parents.\n", + " Defines three parent classes: Swimmer (swim_time), Cyclist (bike_time), Runner (run_time).\n", + " Triathlete inherits from all three.\n", + " Triathlete has attributes name and total_time, which is the sum of all three split times.\n", "\n", " Args:\n", - " mother_eye_color (str): Eye color of the mother.\n", - " father_eye_color (str): Eye color of the father.\n", + " name (str): Name of the triathlete.\n", + " swim_time (float): Swim split time in hours.\n", + " bike_time (float): Bike split time in hours.\n", + " run_time (float): Run split time in hours.\n", " Returns:\n", - " - an instance of class Child\n", + " - an instance of class Triathlete\n", " \"\"\"\n", + "\n", " return" ] }, diff --git a/tutorial/quiz/object_oriented_programming_advanced.py b/tutorial/quiz/object_oriented_programming_advanced.py index e9847453..7334c779 100644 --- a/tutorial/quiz/object_oriented_programming_advanced.py +++ b/tutorial/quiz/object_oriented_programming_advanced.py @@ -6,9 +6,9 @@ def __init__(self, title=""): q1 = Question( question="Which special method is used for object initialization in Python?", options={ - "__init__": "Correct! The `__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.", + "__init__": "Correct! The __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 #