Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Python
__pycache__/
.mypy_cache/
.venv/

# Node
node_modules/

# Mac
.DS_Store
27 changes: 27 additions & 0 deletions classes_and_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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")
eliza = Person("Eliza", 34, "Arch Linux")

print(imran.name)
# print(imran.address) # mypy error: Person has no attribute "address"

print(eliza.name)
# print(eliza.address) # mypy error again

def is_adult(person: Person) -> bool:
return person.age >= 18

print(is_adult(imran)) # no mypy error

def print_address(person: Person) -> None:
print(person.address) # mypy will catch this too

# I learned that a class defines what attributes each object will have.
# Mypy can check if I try to access an attribute that doesn't exist.
# It helps to avoid mistakes like typing person.addres instead of person.address.

Choose a reason for hiding this comment

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

In this case, is there a difference between person.addres and person.address?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, there is.

person.address would work only if the Person class actually had an attribute called address, but person.addres (with the typo) doesn’t exist. So mypy can catch that before running the code.

23 changes: 23 additions & 0 deletions dataclasses_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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 - (
(today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day)
)
return age >= 18


imran = Person("Imran", date(2002, 5, 15), "Ubuntu")
imran2 = Person("Imran", date(2002, 5, 15), "Ubuntu")

print(imran) # Person(name='Imran', date_of_birth=datetime.date(2002, 5, 15), preferred_operating_system='Ubuntu')
print(imran == imran2) # True
print(imran.is_adult()) # True
56 changes: 56 additions & 0 deletions enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import sys
from enum import Enum
from dataclasses import dataclass

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

@dataclass(frozen=True)
class Laptop:
id: int
manufacturer: str
model: str
screen_size_in_inches: float
operating_system: OperatingSystem

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="Apple", model="MacBook", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS),
Laptop(id=4, manufacturer="Apple", model="MacBook Air", screen_size_in_inches=15, operating_system=OperatingSystem.MACOS),
]

# user input
try:
name = input("Enter your name: ")
age = int(input("Enter your age: "))
os_input = input("Enter your preferred operating system (macOS / Arch Linux / Ubuntu): ")

# Convert string to enum
preferred_os = OperatingSystem(os_input)
except ValueError:
print("Invalid input. Please check your age or operating system name.", file=sys.stderr)
sys.exit(1)

# count available laptops
matching_laptops = [l for l in laptops if l.operating_system == preferred_os]
print(f"{name}, there are {len(matching_laptops)} laptops available with {preferred_os.value}.")

# suggest alternative OS if another has more laptops
os_counts = {
os: sum(1 for l in laptops if l.operating_system == os)
for os in OperatingSystem
}

most_common_os = max(os_counts, key=lambda os: os_counts[os])

if most_common_os != preferred_os:
print(f"If you can use {most_common_os.value}, you have a higher chance of getting a laptop.")


# I learned how to use Enums to make my code safer and avoid typos.
# I also learned how to handle invalid user input safely using try and except.
# Using lambda helps mypy understand the type more clearly.
# Without it, mypy didn't know what os_counts.get returns.
25 changes: 25 additions & 0 deletions generics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from dataclasses import dataclass
from typing import List
from datetime import date

@dataclass(frozen=True)
class Person:
name: str
birth_year: int
children: List["Person"]

@property
def age(self) -> int:
current_year = date.today().year
return current_year - self.birth_year

fatma = Person(name="Fatma", birth_year=2018, children=[])
aisha = Person(name="Aisha", birth_year=2020, children=[])
imran = Person(name="Imran", birth_year=1990, children=[fatma, aisha])

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

print_family_tree(imran)
48 changes: 48 additions & 0 deletions inheritance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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):
# Call the parent class constructor
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" (née {self.previous_last_names[0]})"
return f"{self.first_name} {self.last_name}{suffix}"


person1 = Child("Elizaveta", "Alekseeva")
print(person1.get_name()) # from Parent class
print(person1.get_full_name()) # from Child class

person1.change_last_name("Tyurina")
print(person1.get_name())
print(person1.get_full_name())

person2 = Parent("Elizaveta", "Alekseeva")
print(person2.get_name())

# The next lines will cause errors, because Parent doesn't have these methods
# print(person2.get_full_name())
# person2.change_last_name("Tyurina")

print(person2.get_name())
# print(person2.get_full_name())


# I learned that Parent and Child classes can have different methods — Parent can’t access methods only defined in Child
# I understood that the child class can add new methods or override existing ones

19 changes: 19 additions & 0 deletions methods_advantages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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.is_adult()) # True

# Advantages of using methods instead of free functions:
# 1. Easier documentation - all related behaviors are attached to the class.
# 2. Encapsulation - data and logic are kept together in one place.
# 3. Easier maintenance - when class details change, methods can be updated easily.
# 4. Clearer code - you can write person.is_adult() instead of is_adult(person).
21 changes: 21 additions & 0 deletions methods_with_date_of_birth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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 - (
(today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day)
)
return age >= 18


imran = Person("Imran", date(2002, 5, 15), "Ubuntu")
print(imran.is_adult()) # True

# I learned that methods can easily adapt to changes inside a class.
# Now the class stores a date of birth instead of age, but the method still works correctly.
30 changes: 30 additions & 0 deletions type_checking_with_mypy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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}")
47 changes: 47 additions & 0 deletions type_guided_refactorings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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", "Arch Linux"]),
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}")


# I learned how type checking helps when refactoring code.
# Mypy shows exactly which parts of the code need to change when we rename or change a field type.
# This makes large codebases easier to update safely.
33 changes: 33 additions & 0 deletions why_we_use_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
def half(value):
return value / 2

def double(value):
return value * 2

def second(value):
return value[1]


print(half(22)) # My prediction was correct. It returned 11
# print(half("hello")) # My prediction was correct. It gave an error since it is a string
# print(half("22")) # # I thought maybe Python would see it as a number and return 11


print(double(22)) # My prediction was correct. It returned 44
print(double("hello")) # I expected an error
print(double("22")) # I thought maybe Python would treat it as a number and return 44

# print(second(22)) # # My prediction was correct. It gave an error since it is a number
# print(second(0x16)) # # My prediction was correct. It gave an error
print(second("hello")) # My prediction was correct. It returned 'e'
print(second("22")) # My prediction was correct. It returned '2'

def double1(number):
return number * 3

print(double1(10))
# When you check the function name, it doesn’t fit what we have to expect.

Choose a reason for hiding this comment

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

Can you suggest a better name?

Copy link
Author

Choose a reason for hiding this comment

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

A better name would be triple, since it multiplies by 3. I’ve added a short comment in the code. Thanks 🙏

# A better name would be 'triple' or 'multiply_by_three'.
# If you use this func you would expect it to give you double like 20, not 30.
# It might cause a problem for your code.

Loading