diff --git a/.gitignore b/.gitignore index c4234ab74..63db17d88 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ node_modules testoutput.txt +.venv +__pycache__ +.mypy_cache \ No newline at end of file diff --git a/sprint-5-prep/1-use-types.py b/sprint-5-prep/1-use-types.py new file mode 100644 index 000000000..28d3014f8 --- /dev/null +++ b/sprint-5-prep/1-use-types.py @@ -0,0 +1,50 @@ +# 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 double(value): + return value * 2 + +#Answer +#I guess it'll return 2222. That's because any value in quotes is a string, +# and when strings are multiplied, they repeat the characters. + +def double(value): + return value * 2 + +print("double is:", double("22")) #double is: 2222 + +# Yes, It returned 2222 as I predicted. +# Python's operator "*" when used with a string and a number, repeats this string as "number" times, +# and it didn't convert the string "22" into number 22. + + +#Exercise-2 + +def double(number): + return number * 3 + +print(double(10)) + + +#Read the above code and write down what the bug is. How would you fix it? + +# Answer +# There are two way to fix it: +# 1. Change *3 to *2. + +def double(number): + return number * 2 + +print(double(10)) + +# or + +# 2. Rename the function to "triple" if we want multiply by 3 - it keeps the logic correct. + +def triple(number): + return number * 3 + +print(triple(10)) diff --git a/sprint-5-prep/2-type-checking-mypy.py b/sprint-5-prep/2-type-checking-mypy.py new file mode 100644 index 000000000..4d4cc1b84 --- /dev/null +++ b/sprint-5-prep/2-type-checking-mypy.py @@ -0,0 +1,31 @@ +from typing import Dict + +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) + +print(f"The bank accounts total {total_string}") \ No newline at end of file diff --git a/sprint-5-prep/3-classes-and-objects.py b/sprint-5-prep/3-classes-and-objects.py new file mode 100644 index 000000000..2613ea1e1 --- /dev/null +++ b/sprint-5-prep/3-classes-and-objects.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 + +imran = Person("Imran", 22, "Ubuntu") +print(imran.name) +#print(imran.address) +#error: Person class has no attribute "address" + +eliza = Person("Eliza", 34, "Arch Linux") +print(eliza.name) +#print(eliza.address) +#the same error: Person class has no attribute "address" + +def is_adult(person: Person) -> bool: + return person.age >= 18 + +print(is_adult(imran)) + +def get_phone(person: Person) -> str: + return person.phone_number +# After running mypy this method also causes an error, because Person class has no attribute "phone_number" \ No newline at end of file diff --git a/sprint-5-prep/4-methods.py b/sprint-5-prep/4-methods.py new file mode 100644 index 000000000..b442f3052 --- /dev/null +++ b/sprint-5-prep/4-methods.py @@ -0,0 +1,44 @@ +# Exercises-1 +# Think of the advantages of using methods instead of free functions. +# Write them down in your notebook. + +# 1. Organisation: all Person-related logic lives inside the Person class. + +# 2. Encapsulation: we only have to update the method once, +# for example if we change "age" to "date_of_birth" inside the class. +# With free functions, we'd have to update every function that touches person.age across the whole codebase. + +# 3. Simple search: if we type "imran." our editor shows everything Person can do. +# With free functions, we have to remember they exist. + +# 4. Clear ownership: "person.is_adult()" is definitely about a person, +# but with "is_adult(x)" we don't know what it's about. + +# 5. Easier to catch errors with mypy, because all methods related to the class are in one place. + + +# Exercises-2 +# 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) -> bool: + 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(2003, 6, 20), "Ubuntu") +print(imran.is_adult()) diff --git a/sprint-5-prep/5-dataclasses.py b/sprint-5-prep/5-dataclasses.py new file mode 100644 index 000000000..9ebbb120a --- /dev/null +++ b/sprint-5-prep/5-dataclasses.py @@ -0,0 +1,22 @@ +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) -> bool: + 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 + + +zoi = Person("Zoi", date(2003, 3, 23), "Ubuntu") + +print("zoi:", zoi) +print("zoi.is_adult():", zoi.is_adult()) \ No newline at end of file diff --git a/sprint-5-prep/6-generics.py b/sprint-5-prep/6-generics.py new file mode 100644 index 000000000..8bbece556 --- /dev/null +++ b/sprint-5-prep/6-generics.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Person: + name: str + children: list["Person"] + age: int + +fatma = Person(name="Fatma", age=9, children=[]) +aisha = Person(name="Aisha", age=5, children=[]) + +imran = Person(name="Imran", age=35, 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 diff --git a/sprint-5-prep/7-type-guided-refactorings.py b/sprint-5-prep/7-type-guided-refactorings.py new file mode 100644 index 000000000..1d098e48d --- /dev/null +++ b/sprint-5-prep/7-type-guided-refactorings.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 = [] + 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}") \ No newline at end of file diff --git a/sprint-5-prep/8-enums.py b/sprint-5-prep/8-enums.py new file mode 100644 index 000000000..7b666efb6 --- /dev/null +++ b/sprint-5-prep/8-enums.py @@ -0,0 +1,79 @@ +from dataclasses import dataclass +from enum import Enum +from typing import List +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 + +os_input_name = input("Your name is: ") + +os_input_age = input("Your age is: ") +try: + age = int(os_input_age) +except ValueError: + print(f"Error: '{os_input_age}' is not a valid age", file=sys.stderr) + sys.exit(1) + +os_input_preferred_os = input("Preferred operating system is: ") +try: + preferred_operating_system = OperatingSystem(os_input_preferred_os) +except ValueError: + print(f"Error: '{os_input_preferred_os}' is not a valid operating system", file=sys.stderr) + sys.exit(1) + +person = Person(name = os_input_name, age=age, preferred_operating_system=preferred_operating_system) + +laptops = [ + Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13, operating_system=OperatingSystem.ARCH), + Laptop(id=2, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system=OperatingSystem.UBUNTU), + Laptop(id=3, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system=OperatingSystem.UBUNTU), + Laptop(id=4, manufacturer="Apple", model="macBook", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS), + Laptop(id=5, manufacturer="Apple", model="macBook", screen_size_in_inches=14, operating_system=OperatingSystem.MACOS), + Laptop(id=6, manufacturer="Apple", model="macBook", screen_size_in_inches=15, operating_system=OperatingSystem.MACOS), +] + +def find_specific_os(laptops: List[Laptop], preferred_os: OperatingSystem) -> List[Laptop]: + possible_laptops = [] + + for laptop in laptops: + if (laptop.operating_system == preferred_os): + possible_laptops.append(laptop) + + possible_laptops_amount = len(possible_laptops) + + print(f"The amount of possible laptops with preferred operating system is", possible_laptops_amount) + + return possible_laptops + +result = find_specific_os(laptops, preferred_operating_system) + +counts_laptop_per_os: dict[OperatingSystem, int] = {} + +for laptop in laptops: + os = laptop.operating_system + if os in counts_laptop_per_os: + counts_laptop_per_os[os] += 1 + else: + counts_laptop_per_os[os] = 1 + +top_os = max(counts_laptop_per_os, key=lambda os: counts_laptop_per_os[os]) + +if top_os != preferred_operating_system: + print(f"If you're willing to use {top_os.value}, you're more likely to get a laptop ({counts_laptop_per_os[top_os]} available)") \ No newline at end of file diff --git a/sprint-5-prep/9-inheritance.py b/sprint-5-prep/9-inheritance.py new file mode 100644 index 000000000..af08fd167 --- /dev/null +++ b/sprint-5-prep/9-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) -> 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}" + + +# --- Predictions --- + +person1 = Child("Elizaveta", "Alekseeva") + +print(person1.get_name()) +# Prediction: "Elizaveta Alekseeva" +# Reason: Child inherits get_name() from Parent, no name change yet + +print(person1.get_full_name()) +# Prediction: "Elizaveta Alekseeva" +# Reason: no previous last names, so suffix is empty string + +person1.change_last_name("Tyurina") +# Saves "Alekseeva" into previous_last_names list, changes last_name to "Tyurina" + +print(person1.get_name()) +# Prediction: "Elizaveta Tyurina" +# Reason: get_name() uses self.last_name which is now "Tyurina" + +print(person1.get_full_name()) +# Prediction: "Elizaveta Tyurina (nee Alekseeva)" +# Reason: previous_last_names is not empty, so suffix shows the original last name + +person2 = Parent("Elizaveta", "Alekseeva") + +print(person2.get_name()) +# Prediction: "Elizaveta Alekseeva" +# Reason: Parent has get_name(), works normally + +# print(person2.get_full_name()) +# Prediction: ERROR - AttributeError +# Reason: Parent class does not have get_full_name() method, only Child does + +# person2.change_last_name("Tyurina") +# Prediction: ERROR - AttributeError +# Reason: Parent class does not have change_last_name() method, only Child does + +# print(person2.get_name()) +# Would print "Elizaveta Alekseeva" but we can't reach this line because of the error above + +# print(person2.get_full_name()) +# Would also error - Parent still doesn't have get_full_name() \ No newline at end of file