From 97b8bfb4a427f4f889fdeb09678c88691d89c7b0 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Mon, 22 Dec 2025 02:06:13 +0000 Subject: [PATCH 01/15] Predict what double(22) will do --- prep_exercise/1.why_we_use_types.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 prep_exercise/1.why_we_use_types.py diff --git a/prep_exercise/1.why_we_use_types.py b/prep_exercise/1.why_we_use_types.py new file mode 100644 index 00000000..2426b40d --- /dev/null +++ b/prep_exercise/1.why_we_use_types.py @@ -0,0 +1,22 @@ +# exercise +# 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")) + +# My prediction was that double("22") would print 44. +# After running the code, I realized it prints "2222" because +# multiplying a string by an integer in Python repeats the string. +# +# What I have learned is that in Python, the * operator behaves +# differently depending on the data type. For example: +# - with integers, it performs mathematical multiplication +# - with strings, it performs string repetition From 35d41f09a850b81e77b3ae17cc16729b1f083ef9 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Mon, 22 Dec 2025 02:22:30 +0000 Subject: [PATCH 02/15] Explain bug in double() function --- ...why_we_use_types.py => 1a_why_we_use_types.py} | 2 +- prep_exercise/1b_why_we_use_types.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) rename prep_exercise/{1.why_we_use_types.py => 1a_why_we_use_types.py} (92%) create mode 100644 prep_exercise/1b_why_we_use_types.py diff --git a/prep_exercise/1.why_we_use_types.py b/prep_exercise/1a_why_we_use_types.py similarity index 92% rename from prep_exercise/1.why_we_use_types.py rename to prep_exercise/1a_why_we_use_types.py index 2426b40d..eb73f692 100644 --- a/prep_exercise/1.why_we_use_types.py +++ b/prep_exercise/1a_why_we_use_types.py @@ -1,4 +1,4 @@ -# exercise +# ========== exercise 1 ================================== # 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): diff --git a/prep_exercise/1b_why_we_use_types.py b/prep_exercise/1b_why_we_use_types.py new file mode 100644 index 00000000..f4a6379f --- /dev/null +++ b/prep_exercise/1b_why_we_use_types.py @@ -0,0 +1,15 @@ +# ================= exercise =============================== +# Read the code and write down what the bug is. How would you fix it? + +def double(number): + return number * 3 + +print(double(10)) + +# The bug in the code is that the function is named double(), but it multiplies +# the number by 3 instead of 2. This makes the function behavior inconsistent +# with its name. + +# To fix this, either: +# - we need to change the function to return number * 2, or +# - rename the function to triple() if multiplying by 3 is the intended behavior. From 365c6c08556718a9f761bb2b3f796a10774cfd24 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Sat, 27 Dec 2025 00:52:01 +0000 Subject: [PATCH 03/15] Implement type checking with mypy and Fix bugs --- prep_exercise/2_type_checking_with_mypy.py | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 prep_exercise/2_type_checking_with_mypy.py diff --git a/prep_exercise/2_type_checking_with_mypy.py b/prep_exercise/2_type_checking_with_mypy.py new file mode 100644 index 00000000..4b31de68 --- /dev/null +++ b/prep_exercise/2_type_checking_with_mypy.py @@ -0,0 +1,31 @@ + +def open_account(balances, name, amount): + balances[name] = amount + +def sum_balances(accounts): + total = 0 + for name, pence in accounts.items(): + print(f"{name} had balance {pence}") + total += int(pence) + return total + +def format_pence_as_string(total_pence): + 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 df16e92bbf9eb2687e1d734146d33e231022ea96 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Sat, 27 Dec 2025 18:52:04 +0000 Subject: [PATCH 04/15] Classes and objects exercise. Run exercise through mypy and check that it does report and error --- prep_exercise/3_classes_and_objects.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 prep_exercise/3_classes_and_objects.py diff --git a/prep_exercise/3_classes_and_objects.py b/prep_exercise/3_classes_and_objects.py new file mode 100644 index 00000000..0d1aca15 --- /dev/null +++ b/prep_exercise/3_classes_and_objects.py @@ -0,0 +1,26 @@ +class Person: + def __init__(self, name: str, age: int, preferred_operating_system: str, address: str): + self.name = name + self.age = age + self.preferred_operating_system = preferred_operating_system + self.address = address + +imran = Person("Imran", 22, "Ubuntu", "London street") +print(imran.name) +print(imran.address) + +eliza = Person("Eliza", 34, "Arch Linux", "Hatfield town") +print(eliza.name) +print(eliza.address) + +# ====function to check if imran is an adult=========== +def is_adult(person: Person) -> bool: + return person.age >= 18 + +print(is_adult(imran)) + + +def address(person: Person) -> str: + return person.city #city property does not exist + +print(address(imran)) From de541ce19d9c1cd2ec57d58b9ca9e91c734b2c92 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Sat, 27 Dec 2025 19:27:51 +0000 Subject: [PATCH 05/15] Replace age with date of birth and update adult check logic --- prep_exercise/4_methods.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 prep_exercise/4_methods.py diff --git a/prep_exercise/4_methods.py b/prep_exercise/4_methods.py new file mode 100644 index 00000000..9d2a483c --- /dev/null +++ b/prep_exercise/4_methods.py @@ -0,0 +1,24 @@ +# exercise +# Change the Person class to take a date of birth (using the standard library’s datetime.date class) and store it in a field instead of age. +# Update the is_adult method to act the same as before. + + +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 = age - 1 + + return age >= 18 + +imran = Person("Imran", date(2005, 5, 12), "Ubuntu") +print(imran.is_adult()) From a65cd79908c7b1f34618aac93430e4dcf8f87b3d Mon Sep 17 00:00:00 2001 From: ike-agu Date: Sat, 27 Dec 2025 20:08:59 +0000 Subject: [PATCH 06/15] Write a Person class using @datatype which uses a datetime.date for date of birth --- prep_exercise/5_Dataclasses.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 prep_exercise/5_Dataclasses.py diff --git a/prep_exercise/5_Dataclasses.py b/prep_exercise/5_Dataclasses.py new file mode 100644 index 00000000..9a8b8eff --- /dev/null +++ b/prep_exercise/5_Dataclasses.py @@ -0,0 +1,28 @@ +# ✍️exercise +# Write a Person class using @datatype which uses a datetime.date for date of birth, rather than an int for age. +# Re-add the is_adult method to it. + +from dataclasses import dataclass +from datetime import date + +@dataclass(frozen=True) +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 + + if (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day): + age = age - 1 + + return age >= 18 + +imran = Person("Imran", date(2000,5,12), "Ubuntu") +print(imran) + +imran2 = Person("Imran",date(2000,5,12), "Ubuntu") +print(imran == imran2) # Prints True From ddd8affd128c3e28a3e83faeb3021f7c98d025ce Mon Sep 17 00:00:00 2001 From: ike-agu Date: Sat, 27 Dec 2025 20:17:57 +0000 Subject: [PATCH 07/15] Fix bug so that code prints children's age --- prep_exercise/6_generics.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 prep_exercise/6_generics.py diff --git a/prep_exercise/6_generics.py b/prep_exercise/6_generics.py new file mode 100644 index 00000000..56c59d5e --- /dev/null +++ b/prep_exercise/6_generics.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Person: + name: str + age: int + children: List["Person"] + +fatma = Person(name="Fatma", age= 22, children=[]) +aisha = Person(name="Aisha", age =21, children=[]) + +imran = Person(name="Imran", age = 33, 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) From 87966be4188567c4711f9de219cf399c09f2d9fb Mon Sep 17 00:00:00 2001 From: ike-agu Date: Sun, 28 Dec 2025 00:55:25 +0000 Subject: [PATCH 08/15] Fix bugs and refactor code --- prep_exercise/7_type_guided_refactoring.py | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 prep_exercise/7_type_guided_refactoring.py diff --git a/prep_exercise/7_type_guided_refactoring.py b/prep_exercise/7_type_guided_refactoring.py new file mode 100644 index 00000000..315e89be --- /dev/null +++ b/prep_exercise/7_type_guided_refactoring.py @@ -0,0 +1,42 @@ +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: List[Laptop] = [] + 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}") From 2e3050b9f8e98225651ce179d92923f39a010fcc Mon Sep 17 00:00:00 2001 From: ike-agu Date: Sun, 28 Dec 2025 01:09:33 +0000 Subject: [PATCH 09/15] Validate user input and convert to constrained types --- prep_exercise/8_enums.py | 104 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 prep_exercise/8_enums.py diff --git a/prep_exercise/8_enums.py b/prep_exercise/8_enums.py new file mode 100644 index 00000000..ecccc20f --- /dev/null +++ b/prep_exercise/8_enums.py @@ -0,0 +1,104 @@ +from dataclasses import dataclass +from enum import Enum +from typing import List, Dict +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 + +# =====define parse age====== +def parse_age(raw: str) -> int: + try: + age= int(raw) + except ValueError: + print("Error: age must be asn integer.", file=sys.stderr) + sys.exit(1) + + if age <= 0: + print("Error: age must be a positive integer.", file=sys.stderr) + sys.exit(1) + + return age + +# =====define parse operating system====== +def parse_operating_system(raw: str) -> OperatingSystem: + normalized = raw.strip().lower() + + aliases = { + "macos": OperatingSystem.MACOS, + "mac": OperatingSystem.MACOS, + "arch": OperatingSystem.ARCH, + "arch linux": OperatingSystem.ARCH, + "ubuntu": OperatingSystem.UBUNTU, + } + + if normalized not in aliases: + valid = ", ".join(sorted(aliases.keys())) + print(f"Error: invalid operating system. Try one of: {valid}", file=sys.stderr) + sys.exit(1) + + return aliases[normalized] + + +# =======count laptops by os============ +def count_laptops_by_os(laptops: List[Laptop]) -> Dict[OperatingSystem, int]: + counts: Dict[OperatingSystem, int] = {os: 0 for os in OperatingSystem} + for laptop in laptops: + counts[laptop.operating_system] +=1 + return counts + + +# ======define main ================ +def main() -> None: + laptops = [ + Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13.0, operating_system=OperatingSystem.ARCH), + Laptop(id=2, manufacturer="Dell", model="XPS", screen_size_in_inches=15.0, operating_system=OperatingSystem.UBUNTU), + Laptop(id=3, manufacturer="Dell", model="XPS", screen_size_in_inches=15.0, operating_system=OperatingSystem.UBUNTU), + Laptop(id=4, manufacturer="Apple", model="macBook", screen_size_in_inches=13.0, operating_system=OperatingSystem.MACOS), +] + + name = input("Enter your name: ").strip() + if not name: + print("Error: name cannot be empty.", file=sys.stderr) + sys.exit(1) + + age = parse_age(input("Enter your age: ")) + preferred_os = parse_operating_system(input("preferred OS(Ubuntu / Arch / macOS): ")) + + person = Person(name=name, age=age, preferred_operating_system=preferred_os) + + counts = count_laptops_by_os(laptops) + preferred_count = counts[person.preferred_operating_system] + + print(f"\nHi {person.name} (age {person.age})") + print(f"Laptops available with {person.preferred_operating_system.value}: {preferred_count}") + + # Find the OS with maximum availability + best_os = max(counts, key=lambda os:counts[os]) + best_count = counts[best_os] + + if best_os != person.preferred_operating_system and best_count > preferred_count: + print( + f"If you are willing to accept {best_os.value} instead," + f"You're more likely to get a laptop. {best_count} available)." + ) + +if __name__ == "__main__": + main() From e524c391fa9b24ef44d0d66b1e038dca44bc499d Mon Sep 17 00:00:00 2001 From: ike-agu Date: Sun, 28 Dec 2025 02:10:45 +0000 Subject: [PATCH 10/15] Complete exercises on type checking, validation, and inheritance --- prep_exercise/9_inheritance.py | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 prep_exercise/9_inheritance.py diff --git a/prep_exercise/9_inheritance.py b/prep_exercise/9_inheritance.py new file mode 100644 index 00000000..3f038230 --- /dev/null +++ b/prep_exercise/9_inheritance.py @@ -0,0 +1,52 @@ +# play computer game with the code below and predict what you expect each line will do. Then run the code and check your prediction (If any lines cause errors, you may need to comment them out to check later lines). + +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) -> 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" (née {self.previous_last_names[0]})" + return f"{self.first_name} {self.last_name}{suffix}" + +person1 = Child("Elizaveta", "Alekseeva") +print(person1.get_name()) #works because get_name is inherited from Parent. +print(person1.get_full_name()) # get_full_name() works, exist in child. +person1.change_last_name("Tyurina") #change_last_name("Tyurina") works, exist in child.. +print(person1.get_name()) # works because it comes from Parent.get_name. +print(person1.get_full_name())# works because it comes from Parent.get_name. + + +person2 = Parent("Elizaveta", "Alekseeva") +print(person2.get_name()) # works because it is the Parent class. +print(person1.get_full_name()) +# print(person2.get_full_name()) #get_full_name is defined only in child +# person2.change_last_name("Tyurina") #change_last_name is defined only in child not in parent +print(person2.get_name()) +# print(person2.get_full_name()) #get_full_name is defined only in child not in parent + + +# ===============What I learned from playing computer game with above code. + +# 1. Objects can only call methods they actually have inside them. +# - Creating a parent object does not automatically give the child its behavior + +# ===================================================================== +#2. Inheritance is "is-a", not "might-be" i.e +# A child is a parent(here the child can inherit freely from the father.) +# A parent is NOT a child(i.e a parent does not inherit from the child) From 611c259e811e51e3d8a29283615743daa8d2e1e9 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Sun, 28 Dec 2025 02:16:13 +0000 Subject: [PATCH 11/15] Add missing type annotation for previous_last_names --- prep_exercise/9_inheritance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prep_exercise/9_inheritance.py b/prep_exercise/9_inheritance.py index 3f038230..2bb6742a 100644 --- a/prep_exercise/9_inheritance.py +++ b/prep_exercise/9_inheritance.py @@ -12,7 +12,7 @@ def get_name(self) -> str: class Child(Parent): def __init__(self, first_name: str, last_name: str): super().__init__(first_name, last_name) - self.previous_last_names = [] + self.previous_last_names: list[str] = [] def change_last_name(self, last_name) -> None: self.previous_last_names.append(self.last_name) From 664b191e95afe2b3d7167d357eda599709e54170 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Sun, 28 Dec 2025 02:18:06 +0000 Subject: [PATCH 12/15] Fix bug --- prep_exercise/9_inheritance.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prep_exercise/9_inheritance.py b/prep_exercise/9_inheritance.py index 2bb6742a..5804118f 100644 --- a/prep_exercise/9_inheritance.py +++ b/prep_exercise/9_inheritance.py @@ -33,10 +33,10 @@ def get_full_name(self) -> str: person2 = Parent("Elizaveta", "Alekseeva") -print(person2.get_name()) # works because it is the Parent class. +print(person2.get_name()) # works because it is in the Parent class. print(person1.get_full_name()) -# print(person2.get_full_name()) #get_full_name is defined only in child -# person2.change_last_name("Tyurina") #change_last_name is defined only in child not in parent +# print(person2.get_full_name()) # crashes the program as get_full_name is defined only in child +# person2.change_last_name("Tyurina") # crashes the program as change_last_name is defined only in child not in parent print(person2.get_name()) # print(person2.get_full_name()) #get_full_name is defined only in child not in parent From 83804ee7b3aa8515efd1bac57501737cedfa2118 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Tue, 20 Jan 2026 01:53:57 +0000 Subject: [PATCH 13/15] Update my code and Fix bugs to pass mypy type checking --- prep_exercise/2_type_checking_with_mypy.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/prep_exercise/2_type_checking_with_mypy.py b/prep_exercise/2_type_checking_with_mypy.py index 4b31de68..d6afc4f2 100644 --- a/prep_exercise/2_type_checking_with_mypy.py +++ b/prep_exercise/2_type_checking_with_mypy.py @@ -1,29 +1,29 @@ -def open_account(balances, name, amount): +def open_account(balances: dict[str, int], name: str, amount: int) -> None: balances[name] = amount -def sum_balances(accounts): +def sum_balances(accounts: dict[str, int]) -> int: total = 0 for name, pence in accounts.items(): print(f"{name} had balance {pence}") - total += int(pence) + total += pence return total -def format_pence_as_string(total_pence): +def format_pence_as_string(total_pence: int) -> str: if total_pence < 100: return f"{total_pence}p" - pounds = int(total_pence / 100) + pounds = total_pence // 100 pence = total_pence % 100 return f"£{pounds}.{pence:02d}" balances = { "Sima": 700, "Linn": 545, - "Georg": 831, + "George": 831, } open_account(balances, "Tobi", 913) -open_account(balances, "Olya", "713") +open_account(balances, "Olya", 713) total_pence = sum_balances(balances) total_string = format_pence_as_string(total_pence) From f4cb50707353e965574aabae4fb32aa2f42f32f6 Mon Sep 17 00:00:00 2001 From: ike-agu Date: Tue, 20 Jan 2026 03:06:01 +0000 Subject: [PATCH 14/15] Update print_family_tree to print descendants recursively --- prep_exercise/6_generics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prep_exercise/6_generics.py b/prep_exercise/6_generics.py index 56c59d5e..2bc98f08 100644 --- a/prep_exercise/6_generics.py +++ b/prep_exercise/6_generics.py @@ -13,8 +13,8 @@ class Person: imran = Person(name="Imran", age = 33, children=[fatma, aisha]) def print_family_tree(person: Person) -> None: - print(person.name) + print(f"- {person.name} ({person.age})") for child in person.children: - print(f"- {child.name} ({child.age})") + print_family_tree(child) print_family_tree(imran) From 7003f68f6b1013ce0e98b017fa4ec7b9c90a444d Mon Sep 17 00:00:00 2001 From: ike-agu Date: Tue, 20 Jan 2026 03:17:07 +0000 Subject: [PATCH 15/15] Fix typo in laptop availability message by removing extra parenthesis --- prep_exercise/8_enums.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prep_exercise/8_enums.py b/prep_exercise/8_enums.py index ecccc20f..e6592162 100644 --- a/prep_exercise/8_enums.py +++ b/prep_exercise/8_enums.py @@ -97,7 +97,7 @@ def main() -> None: if best_os != person.preferred_operating_system and best_count > preferred_count: print( f"If you are willing to accept {best_os.value} instead," - f"You're more likely to get a laptop. {best_count} available)." + f"You're more likely to get a laptop. {best_count} available." ) if __name__ == "__main__":