From 9937825a44961295ad95ad10aa5fc4a73c27af6f Mon Sep 17 00:00:00 2001 From: Fithi Teklom Date: Tue, 1 Sep 2026 00:56:46 +0100 Subject: [PATCH 1/7] complete type checking and classes exercises. --- sprint5-prep-exercises/add_is_adult.py | 24 ++++++++++++ sprint5-prep-exercises/advantagesOfMethods.py | 9 +++++ sprint5-prep-exercises/classesAndoObjects.py | 16 ++++++++ .../limits_of_type_checking.py | 9 +++++ sprint5-prep-exercises/predict_double.py | 14 +++++++ sprint5-prep-exercises/type_annotation.py | 38 +++++++++++++++++++ 6 files changed, 110 insertions(+) create mode 100644 sprint5-prep-exercises/add_is_adult.py create mode 100644 sprint5-prep-exercises/advantagesOfMethods.py create mode 100644 sprint5-prep-exercises/classesAndoObjects.py create mode 100644 sprint5-prep-exercises/limits_of_type_checking.py create mode 100644 sprint5-prep-exercises/predict_double.py create mode 100644 sprint5-prep-exercises/type_annotation.py diff --git a/sprint5-prep-exercises/add_is_adult.py b/sprint5-prep-exercises/add_is_adult.py new file mode 100644 index 000000000..006774d5d --- /dev/null +++ b/sprint5-prep-exercises/add_is_adult.py @@ -0,0 +1,24 @@ +class Person: + def __init__( + self, + name: str, + age: int, + preferred_operating_system: str + ): + self.name = name + self.age = age + self.preferred_operating_system = preferred_operating_system + + +def is_adult(person: Person) -> bool: + return person.age >= 18 + + +def get_address(person: Person) -> str: + return person.address + + +imran = Person("Imran", 22, "Ubuntu") + +print(imran.name) +print(is_adult(imran)) \ No newline at end of file diff --git a/sprint5-prep-exercises/advantagesOfMethods.py b/sprint5-prep-exercises/advantagesOfMethods.py new file mode 100644 index 000000000..826492bc8 --- /dev/null +++ b/sprint5-prep-exercises/advantagesOfMethods.py @@ -0,0 +1,9 @@ +# Better organisation — behaviour related to a class stays together with that class. + +# Easier to discover — if I have a Person, I can look at the Person class to see what it can do. + +# More readable — person.is_adult() clearly shows that is_adult is behaviour associated with a Person. + +# Encapsulation — other code doesn't need to know the internal details of how Person calculates something. + +# Easier maintenance/refactoring — if the internal representation changes, such as changing age to date_of_birth, we can update the method while callers can continue using person.is_adult(). \ No newline at end of file diff --git a/sprint5-prep-exercises/classesAndoObjects.py b/sprint5-prep-exercises/classesAndoObjects.py new file mode 100644 index 000000000..a5fc2a61c --- /dev/null +++ b/sprint5-prep-exercises/classesAndoObjects.py @@ -0,0 +1,16 @@ +class Person: + def __init__(self, name: str, age: int, preferred_operating_system: str): + self.name = name + self.age = age + self.preferred_operating_system = preferred_operating_system + +imran = Person("Imran", 22, "Ubuntu") +print(imran.name) +print(imran.address) + +eliza = Person("Eliza", 34, "Arch Linux") +print(eliza.name) +print(eliza.address) + +#The error is that the code tries to access an attribute called address, but the Person class does not define an address attribute. +#Mypy knows that imran and eliza are Person objects, so it can detect that .address does not exist before we run the program. \ No newline at end of file diff --git a/sprint5-prep-exercises/limits_of_type_checking.py b/sprint5-prep-exercises/limits_of_type_checking.py new file mode 100644 index 000000000..f5e3e35e8 --- /dev/null +++ b/sprint5-prep-exercises/limits_of_type_checking.py @@ -0,0 +1,9 @@ +# Read the above code and write down what the bug is. How would you fix it? + +def double(number): + # return number * 3 + return number * 2 # I would fix it like this + +print(double(10)) + +#Because double function is expected to double or multiply a number by two but in contrast it is multiplying by 3. \ No newline at end of file diff --git a/sprint5-prep-exercises/predict_double.py b/sprint5-prep-exercises/predict_double.py new file mode 100644 index 000000000..2b9796da4 --- /dev/null +++ b/sprint5-prep-exercises/predict_double.py @@ -0,0 +1,14 @@ +# Predict what double("22") will do. Then run the code and check. Did it do what you expected? Why did it return the value it did? +def half(value): + return value / 2 + +def double(value): + return value * 2 + +def second(value): + return value[1] + +print(double(22)) + +#So I predicted the string would be doubled(repeated) --> "2222" +#As in Python, multiplying a string by an integer means repeat the string \ No newline at end of file diff --git a/sprint5-prep-exercises/type_annotation.py b/sprint5-prep-exercises/type_annotation.py new file mode 100644 index 000000000..b76ea59bd --- /dev/null +++ b/sprint5-prep-exercises/type_annotation.py @@ -0,0 +1,38 @@ +def open_account( + balances: dict[str, int], + name: str, + amount: int, +) -> None: + balances[name] = amount + +def sum_balances(accounts: dict[str, int]) -> int: + total = 0 + for name, pence in accounts.items(): + print(f"{name} had balance {pence}") + total += pence + return total + +def format_pence_as_string(total_pence: int) -> str: + if total_pence < 100: + return f"{total_pence}p" + + pounds = int(total_pence / 100) + pence = total_pence % 100 + + return f"£{pounds}.{pence:02d}" + +balances = { + "Sima": 700, + "Linn": 545, + "Georg": 831, +} + +open_account(balances, "Tobi", 913) +open_account(balances, "Olya", 713) + +total_pence = sum_balances(balances) +total_string = format_pence_as_string(total_pence) + +print(f"The bank accounts total {total_string}") + + From 17533e84906d3678f4c7e1518ca05c2383070ed6 Mon Sep 17 00:00:00 2001 From: Fithi Teklom Date: Tue, 1 Sep 2026 01:03:04 +0100 Subject: [PATCH 2/7] complete method exercise --- sprint5-prep-exercises/methods.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 sprint5-prep-exercises/methods.py diff --git a/sprint5-prep-exercises/methods.py b/sprint5-prep-exercises/methods.py new file mode 100644 index 000000000..b4f1eb7d0 --- /dev/null +++ b/sprint5-prep-exercises/methods.py @@ -0,0 +1,24 @@ +from datetime import date + + +class Person: + def __init__(self, name: str, date_of_birth: date, preferred_operating_system: str): + self.name = name + self.date_of_birth = date_of_birth + self.preferred_operating_system = preferred_operating_system + + def is_adult(self): + today = date.today() + age = today.year - self.date_of_birth.year + + if (today.month, today.day) < ( + self.date_of_birth.month, + self.date_of_birth.day, + ): + age -= 1 + + return age >= 18 + + +imran = Person("Imran", date(2000, 2, 10), "Ubuntu") +print(imran.is_adult()) \ No newline at end of file From 76425eb2a716cd2dc6931965b464d9fa15fcc9da Mon Sep 17 00:00:00 2001 From: Fithi Teklom Date: Tue, 1 Sep 2026 01:08:22 +0100 Subject: [PATCH 3/7] complete data class exercise --- sprint5-prep-exercises/data_class.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 sprint5-prep-exercises/data_class.py diff --git a/sprint5-prep-exercises/data_class.py b/sprint5-prep-exercises/data_class.py new file mode 100644 index 000000000..617021a94 --- /dev/null +++ b/sprint5-prep-exercises/data_class.py @@ -0,0 +1,19 @@ +from datetime import date +from dataclasses import dataclass + + +@dataclass +class Person: + name: str + date_of_birth: date + preferred_operating_system: str + + def is_adult(self): + today = date.today() + age = today.year - self.date_of_birth.year + + return age >= 18 + + +imran = Person("Imran", date(2000, 10, 10), "Ubuntu") +print(imran.is_adult()) \ No newline at end of file From 6bb9d87065d05fb9bc4f8fae4990ebd502bbbafd Mon Sep 17 00:00:00 2001 From: Fithi Teklom Date: Tue, 1 Sep 2026 01:10:52 +0100 Subject: [PATCH 4/7] Complete generic exercise --- sprint5-prep-exercises/generic.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 sprint5-prep-exercises/generic.py diff --git a/sprint5-prep-exercises/generic.py b/sprint5-prep-exercises/generic.py new file mode 100644 index 000000000..e95c1169e --- /dev/null +++ b/sprint5-prep-exercises/generic.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class Person: + name: str + age: int + children: list + + +fatma = Person(name="Fatma", age= 26, children=[]) +aisha = Person(name="Aisha", age = 20, children=[]) + +imran = Person(name="Imran", age = 45, children=[fatma, aisha]) + + +def print_family_tree(person: Person) -> None: + print(person.name) + for child in person.children: + print(f"- {child.name} ({child.age})") + + +print_family_tree(imran) \ No newline at end of file From f52f136a0c48025ea4b0684174e3ea0897f7d770 Mon Sep 17 00:00:00 2001 From: Fithi Teklom Date: Tue, 1 Sep 2026 01:18:10 +0100 Subject: [PATCH 5/7] complete type refactoring exercise --- .../type_guided_refactoring.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 sprint5-prep-exercises/type_guided_refactoring.py diff --git a/sprint5-prep-exercises/type_guided_refactoring.py b/sprint5-prep-exercises/type_guided_refactoring.py new file mode 100644 index 000000000..f78d8024b --- /dev/null +++ b/sprint5-prep-exercises/type_guided_refactoring.py @@ -0,0 +1,80 @@ +from dataclasses import dataclass +from typing import List + + +@dataclass(frozen=True) +class Person: + name: str + age: int + preferred_operating_systems: List[str] + + +@dataclass(frozen=True) +class Laptop: + id: int + manufacturer: str + model: str + screen_size_in_inches: float + operating_system: str + + +def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop]: + possible_laptops = [] + for laptop in laptops: + if laptop.operating_system in person.preferred_operating_systems: + possible_laptops.append(laptop) + return possible_laptops + + +people = [ + Person(name="Imran", age=22, preferred_operating_systems=["Ubuntu"]), + Person(name="Eliza", age=34, preferred_operating_systems=["Arch Linux"]), +] + +laptops = [ + Laptop( + id=1, + manufacturer="Dell", + model="XPS", + screen_size_in_inches=13, + operating_system="Arch Linux", + ), + Laptop( + id=2, + manufacturer="Dell", + model="XPS", + screen_size_in_inches=15, + operating_system="Ubuntu", + ), + Laptop( + id=3, + manufacturer="Dell", + model="XPS", + screen_size_in_inches=15, + operating_system="ubuntu", + ), + Laptop( + id=4, + manufacturer="Apple", + model="macBook", + screen_size_in_inches=13, + operating_system="macOS", + ), +] + +for person in people: + possible_laptops = find_possible_laptops(laptops, person) + print(f"Possible laptops for {person.name}: {possible_laptops}") + +# After running the code with mypy, I found two type errors. +# Error 1: +# On line 30, mypy says that `preferred_operating_systems` is given as a `str`, +# but the Person class expects a `list[str]`. +# To fix this, the operating system should be passed inside a list instead of +# passing it as a single string. + +# Error 2: +# The same problem happens on line 31. A `str` is passed when mypy expects +# a `list[str]`. +# This can also be fixed by changing the single operating system string into +# a list of strings. From 7427120ee8df1b3cc9c63562b47e82e2d743835e Mon Sep 17 00:00:00 2001 From: Fithi Teklom Date: Tue, 1 Sep 2026 01:47:03 +0100 Subject: [PATCH 6/7] Complete enums exercise --- sprint5-prep-exercises/enums.py | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 sprint5-prep-exercises/enums.py diff --git a/sprint5-prep-exercises/enums.py b/sprint5-prep-exercises/enums.py new file mode 100644 index 000000000..3b95450b4 --- /dev/null +++ b/sprint5-prep-exercises/enums.py @@ -0,0 +1,109 @@ +from dataclasses import dataclass +from enum import Enum +import sys + + +class OperatingSystem(Enum): + MACOS = "macOS" + ARCH = "Arch Linux" + UBUNTU = "Ubuntu" + + +@dataclass(frozen=True) +class Person: + name: str + age: int + preferred_operating_system: OperatingSystem + + +@dataclass(frozen=True) +class Laptop: + id: int + manufacturer: str + model: str + screen_size_in_inches: float + operating_system: OperatingSystem + + +# 1. The library already has some laptops +laptops = [ + Laptop(1, "Dell", "XPS", 13, OperatingSystem.ARCH), + Laptop(2, "Dell", "XPS", 15, OperatingSystem.UBUNTU), + Laptop(3, "Dell", "XPS", 15, OperatingSystem.UBUNTU), + Laptop(4, "Apple", "MacBook", 13, OperatingSystem.MACOS), +] + + +# 2. Get the person's name +name = input("Enter your name: ") + + +# Get age as text, then convert it to int immediately +age_input = input("Enter your age: ") + +try: + age = int(age_input) +except ValueError: + print("Error: age must be a whole number.", file=sys.stderr) + sys.exit(1) + + +# Get operating system as text, then convert it to an enum immediately +os_input = input( + "Enter your preferred operating system " + "(Ubuntu, Arch Linux, macOS): " +) + +try: + preferred_os = OperatingSystem(os_input) +except ValueError: + print( + "Error: operating system must be Ubuntu, Arch Linux, or macOS.", + file=sys.stderr, + ) + sys.exit(1) + + +# Now we have correctly typed data, so create the Person +person = Person( + name=name, + age=age, + preferred_operating_system=preferred_os, +) + + +# 3. Count laptops with the person's preferred operating system +preferred_count = 0 + +for laptop in laptops: + if laptop.operating_system == person.preferred_operating_system: + preferred_count += 1 + + +print( + f"The library has {preferred_count} laptop(s) " + f"with {person.preferred_operating_system.value}." +) + + +# 4. Check whether another operating system has more laptops +for operating_system in OperatingSystem: + + # Don't compare their preferred OS with itself + if operating_system == person.preferred_operating_system: + continue + + count = 0 + + for laptop in laptops: + if laptop.operating_system == operating_system: + count += 1 + + if count > preferred_count: + print( + f"If you are willing to use {operating_system.value}, " + f"you are more likely to get a laptop because " + f"the library has {count} available." + ) + + From 294c6b75c6536f57ba6716388968df3354b778fc Mon Sep 17 00:00:00 2001 From: Fithi Teklom Date: Tue, 1 Sep 2026 01:57:04 +0100 Subject: [PATCH 7/7] work on inheritance exercise --- sprint5-prep-exercises/inheritance.py | 67 +++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 sprint5-prep-exercises/inheritance.py diff --git a/sprint5-prep-exercises/inheritance.py b/sprint5-prep-exercises/inheritance.py new file mode 100644 index 000000000..a4cdc2fdf --- /dev/null +++ b/sprint5-prep-exercises/inheritance.py @@ -0,0 +1,67 @@ +class Parent: + def __init__(self, first_name: str, last_name: str): + self.first_name = first_name + self.last_name = last_name + + def get_name(self) -> str: + return f"{self.first_name} {self.last_name}" + + +class Child(Parent): + def __init__(self, first_name: str, last_name: str): + super().__init__(first_name, last_name) + self.previous_last_names = [] + + def change_last_name(self, last_name: str) -> None: + self.previous_last_names.append(self.last_name) + self.last_name = last_name + + def get_full_name(self) -> str: + suffix = "" + + if len(self.previous_last_names) > 0: + suffix = f" (previously {self.previous_last_names[0]})" + + return f"{self.first_name} {self.last_name}{suffix}" + + +person1 = Child("Sara", "Ali") + +print(person1.get_name()) +print(person1.get_full_name()) + +person1.change_last_name("Ahmed") + +print(person1.get_name()) +print(person1.get_full_name()) + + +person2 = Parent("Sara", "Ali") + +print(person2.get_name()) + +# These lines would cause errors because these methods +# only exist in Child, not Parent: + +# print(person2.get_full_name()) +# person2.change_last_name("Ahmed") + +print(person2.get_name()) + +# This would also cause an error: +# print(person2.get_full_name()) + + +# The important inheritance relationship is: + +# Parent +# │ +# ├── get_name() +# │ +# ▼ +# Child +# ├── inherits get_name() +# ├── adds change_last_name() +# └── adds get_full_name() + +# So a Child can use the inherited Parent method, but a Parent cannot use methods that only exist in Child. \ No newline at end of file