Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions prep-exercises/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Virtual Environment
venv/
env/
ENV/

# Python cache
__pycache__/
*.pyc
*.pyo
*.pyd

# mypy cache
.mypy_cache/

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store

25 changes: 25 additions & 0 deletions prep-exercises/classes_and_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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(self) -> bool:
return self.age >= 18


imran = Person("Imran", 22, "Ubuntu")
print(imran.name)
# print(imran.address) # Error: address is not an attribute
print(imran.is_adult())

eliza = Person("Eliza", 34, "Arch Linux")
print(eliza.name)
# print(eliza.address) # Error: address is not an attribute


# This function will error at runtime if called
def get_address(person: Person) -> str:
return person.address # Error: Person has no attribute 'address'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you suggest a change that would fix this error?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an address parameter to the Person class init and stored it as self.address


# print(get_address(imran)) # Uncommenting this will cause an error
25 changes: 25 additions & 0 deletions prep-exercises/dataclasses_exercise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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:
current_date = date.today()
age = current_date.year - self.date_of_birth.year - (
(current_date.month, current_date.day) < (self.date_of_birth.month, self.date_of_birth.day)
)
return age >= 18

# Example usage:
imran = Person("Imran", date(2001, 5, 15), "Ubuntu")
print(imran)
print(imran.is_adult())

eliza = Person("Eliza", date(1990, 3, 20), "Arch Linux")
print(eliza)
print(eliza.is_adult())

70 changes: 70 additions & 0 deletions prep-exercises/enums_exercise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from dataclasses import dataclass
from enum import Enum

class OperatingSystem(Enum):
MACOS = "macOS"
UBUNTU = "Ubuntu"
ARCH = "Arch Linux"

@dataclass
class Laptop:
id: int
manufacturer: str
model: str
screen_size_in_inches: float
operating_system: OperatingSystem

laptops = [
Laptop(id=1, manufacturer="Lenovo", model="ThinkPad", screen_size_in_inches=14, operating_system=OperatingSystem.ARCH),
Laptop(id=2, manufacturer="HP", model="Pavilion", screen_size_in_inches=15.6, operating_system=OperatingSystem.UBUNTU),
Laptop(id=3, manufacturer="Asus", model="ZenBook", screen_size_in_inches=13.3, operating_system=OperatingSystem.UBUNTU),
Laptop(id=4, manufacturer="Apple", model="MacBook Air", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS),
]

@dataclass
class Person:
name: str
age: int
preferred_operating_system: OperatingSystem


name = input('Please enter your name: ')

age_input = input('Please enter your age: ')
try:
age = int(age_input)
except ValueError:
print(f"Error: '{age_input}' is not a valid age. Please enter a number.")
exit()

preferred_os_input = input('Please enter your preferred operating system: ')
try:
preferred_operating_system = OperatingSystem(preferred_os_input)
except ValueError:
available_options = ', '.join([os.value for os in OperatingSystem])
print(f"Error: '{preferred_os_input}' is not available. Please choose from: {available_options}")
exit()


person = Person(name=name, age=age, preferred_operating_system=preferred_operating_system)


number_of_available_laptops = sum(
1 for laptop in laptops if laptop.operating_system == person.preferred_operating_system
)

def offer_laptop_to_user() -> None:
offer = input('Would you like a laptop with this OS (yes / no) ? ')
if offer.lower() == "y" or offer.lower() == "yes":
print('Great! Please come on monday next week to collect your laptop')
else:
print('No problem. See you later.')

if number_of_available_laptops == 1:
print(f'There is {number_of_available_laptops} laptop available with your preferred operating system.')
offer_laptop_to_user()
elif number_of_available_laptops > 1:
print(f'There are {number_of_available_laptops} laptops available with your preferred operating system.')
offer_laptop_to_user()
else:
print('There are no laptops available with your preferred operating system.')
32 changes: 32 additions & 0 deletions prep-exercises/generics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from dataclasses import dataclass
from typing import List

@dataclass(frozen=True)
class Person:
name: str
age: int
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you solve this in a way that it always prints the current age, rather than hard-coding a specific age?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

children: List["Person"]

sara = Person(name="Sara", age=5, children=[])
ahmed = Person(name="Ahmed", age=8, children=[])

ali = Person(name="Ali", age=28, children=[sara])
aya = Person(name="Aya", age=32, children=[ahmed])

imran = Person(name="Imran", age=55, children=[ali, aya])

def print_family_tree(person: Person) -> None:
print(f"{person.name} ({person.age})")
for child in person.children:
print(f" - {child.name} ({child.age})")
for grandchild in child.children:
print(f" - {grandchild.name} ({grandchild.age})")

def count_family_members(person: Person) -> int:
count = 1
for child in person.children:
count += count_family_members(child)
return count

print_family_tree(imran)
print(f"\nTotal family members: {count_family_members(imran)}")
40 changes: 40 additions & 0 deletions prep-exercises/inheritance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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: list[str] = []

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" (née {self.previous_last_names[0]})"
return f"{self.first_name} {self.last_name}{suffix}"


print("Creating Child instance:")
person1 = Child("Sarah", "Johnson")
print(f"Name: {person1.get_name()}")
print(f"Full name: {person1.get_full_name()}")

print("\nChanging last name to Smith:")
person1.change_last_name("Smith")
print(f"Name: {person1.get_name()}")
print(f"Full name: {person1.get_full_name()}")

print("\nCreating Parent instance:")
person2 = Parent("Emma", "Wilson")
print(f"Name: {person2.get_name()}")

# person2.change_last_name("Brown") # Error: Parent doesn't have change_last_name method
22 changes: 22 additions & 0 deletions prep-exercises/methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from datetime import date

class Person:
def __init__(self, name: str, DoB: date, preferred_operating_system: str):
self.name = name
self.DoB = DoB
self.preferred_operating_system = preferred_operating_system

def is_adult(self) -> bool:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a benefit or drawback of writing the function this way?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stores DoB (fixed), not age (changes over time), so is_adult() stays correct.
Rtores DoB (fixed), not age (changes over time), so is_adult() stays correct.

current_date = date.today()
age = current_date.year - self.DoB.year - (
(current_date.month, current_date.day) < (self.DoB.month, self.DoB.day)
)
return age >= 18



eliza = Person("Eliza", date(2010, 5, 15), "Arch Linux")
print(f"{eliza.name} is adult: {eliza.is_adult()}")

sara = Person("Sara", date(1995, 12, 20), "macOS")
print(f"{sara.name} is adult: {sara.is_adult()}")
1 change: 1 addition & 0 deletions prep-exercises/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mypy==1.18.2
34 changes: 34 additions & 0 deletions prep-exercises/type_checking_with_mypy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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: dict[str, int] = {
"Sima": 700,
"Linn": 545,
"Georg": 831,
}

# Bugs fixed:
# 1. Added balances as first argument
# 2. Converted amounts to pence (int)
# 3. Fixed function name typo
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}")
55 changes: 55 additions & 0 deletions prep-exercises/type_guided_refactorings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from dataclasses import dataclass
from typing import Optional

# Before refactoring - unclear types
def process_user_data(data):
name = data[0]
age = data[1]
email = data[2] if len(data) > 2 else None

if age < 18:
return None

return f"{name} ({age}): {email or 'no email'}"


# After refactoring - clear types
@dataclass
class User:
name: str
age: int
email: Optional[str] = None

def is_adult(user: User) -> bool:
return user.age >= 18

def format_user_info(user: User) -> str:
email_str = user.email if user.email else "no email"
return f"{user.name} ({user.age}): {email_str}"

def process_user(user: User) -> Optional[str]:
if not is_adult(user):
return None
return format_user_info(user)


# Testing old version
print(process_user_data(("Alice", 25, "alice@example.com")))
print(process_user_data(("Bob", 16)))

# Testing new version
user1 = User("Alice", 25, "alice@example.com")
user2 = User("Bob", 16)

print(process_user(user1))
print(process_user(user2))


# Another example
def get_value(key: str, data: dict[str, int]) -> Optional[int]:
return data.get(key)

sample_data = {"a": 1, "b": 2}
result = get_value("a", sample_data)
if result is not None:
print(f"Found: {result}")
29 changes: 29 additions & 0 deletions prep-exercises/why_we_use_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
def half(value):
return value / 2

def double(value):
return value * 2

def second(value):
return value[1]

# Prediction: double("22") will return "2222" because * operator repeats strings
print(double("22"))

# Testing other cases
print(half(22))
# print(half("hello")) # This will error
# print(half("22")) # This will error

print(double(22))
print(double("hello"))

# print(second(22)) # This will error
print(second("hello"))
print(second("22"))

# The bug: this function is called double but multiplies by 3!
def double_bug(number):
return number * 3

print(double_bug(10)) # Should return 20 but returns 30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you think of any change you could make to this function to fix the bug?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change number * 3 to number * 2 so the function doubles the input.

Loading