Skip to content

Latest commit

 

History

History
2244 lines (1583 loc) · 68 KB

1.md

File metadata and controls

2244 lines (1583 loc) · 68 KB

🧩 BLOK 1 — Podstawy Pythona / Python Foundations


🎯 Cel / Goal

PL: Nauczyć się podstaw programowania w Pythonie: zmienne, typy danych, pętle, funkcje, klasy, type hints oraz najlepsze praktyki (DRY, KISS, Zen of Python). EN: Learn the fundamentals of Python programming: variables, data types, loops, functions, classes, type hints, and best practices (DRY, KISS, Zen of Python).


📁 Przygotowanie projektu / Project Setup

Before you start, create a proper project structure and initialize Git — this lets you practice skills from Phase 0!

Zanim zaczniesz, stwórz odpowiednią strukturę projektu i zainicjuj Git — to pozwala przećwiczyć umiejętności z Fazy 0!

🧩 Zadanie / Task:

Step 1: Create project folder / Krok 1: Utwórz folder projektu

# Navigate to your learning directory / Przejdź do katalogu nauki
# Replace with your own path, for example:
# Windows: cd C:\learning\python
# Mac/Linux: cd ~/learning/python
cd your-learning-directory

# Create Phase 1 folder / Utwórz folder dla Fazy 1
mkdir 01_basics
cd 01_basics

Step 2: Initialize uv project / Krok 2: Zainicjuj projekt uv

# Initialize new Python project with uv / Zainicjuj nowy projekt Pythona z uv
uv init

# This creates: / To tworzy:
# - pyproject.toml (dependency file / plik zależności)
# - .python-version (Python version / wersja Pythona)
# - hello.py (example file / przykładowy plik)

Step 3: Initialize Git repository (if not already in one) / Krok 3: Zainicjuj repozytorium Git

# Check if you're already in a Git repo / Sprawdź czy jesteś już w repo Git
git status

# If not, initialize Git / Jeśli nie, zainicjuj Git
git init

# Create .gitignore if you don't have one / Utwórz .gitignore jeśli go nie masz
cat > .gitignore << 'EOF'
# Python
__pycache__/
*.py[cod]
.Python
.venv/
venv/
.env
*.log

# IDE
.idea/
.vscode/
EOF

# Add initial files / Dodaj początkowe pliki
git add .
git commit -m "Initialize Phase 1 - Python Basics project"

Step 4: Create README for this phase / Krok 4: Utwórz README dla tej fazy

Create a file README.md:

Utwórz plik README.md:

# Phase 1 — Python Basics

This folder contains exercises for learning Python fundamentals.

Ten folder zawiera ćwiczenia do nauki podstaw Pythona.

## Files / Pliki

- `basics.py` — Variables and data types / Zmienne i typy danych
- `collections.py` — Lists, dicts, tuples, sets / Listy, słowniki, tuple, zbiory
- `control_flow.py` — if/elif/else, match-case, None checking / Instrukcje warunkowe, match-case, sprawdzanie None
- `loops.py` — for and while loops / Pętle for i while
- `functions.py` — Functions with type hints / Funkcje z type hints
- `classes.py` — Classes, OOP, LSP examples / Klasy, OOP, przykłady LSP
- `type_hints.py` — Type hints / Wskazówki typów
- `dataclass_demo.py` — Dataclasses / Klasy danych
- `pydantic_demo.py` — Pydantic (alternative to dataclass) / Pydantic (alternatywa dla dataclass)
- `task_manager.py` — Mini-project / Mini-projekt

## Running the code / Uruchamianie kodu

```bash
# Run any file with uv / Uruchom dowolny plik z uv
uv run python basics.py
uv run python collections.py
# etc.

Progress / Postęp

  • Variables and data types
  • Collections
  • Control flow (if/elif/else, match-case, None checking)
  • Loops
  • Functions (with type hints)
  • Classes and OOP (with LSP)
  • Type hints
  • Dataclasses
  • Pydantic (optional alternative)
  • Mini-project

Save and commit:

Zapisz i commituj:

```bash
git add README.md
git commit -m "Add Phase 1 README with file structure"

Expected result / Oczekiwany rezultat:

You now have a clean project structure ready for Python exercises.

Masz teraz czystą strukturę projektu gotową na ćwiczenia Pythona.


💡 1️⃣ Zmienne i typy danych / Variables and Data Types

A variable is like a labeled box that stores information — you give it a name and put data inside.

Zmienna to jak opisane pudełko, w którym przechowujesz informacje — dajesz jej nazwę i wkładasz do środka dane.

Python has several built-in data types for storing different kinds of information:

Python ma kilka wbudowanych typów danych do przechowywania różnych rodzajów informacji:

  • int — whole numbers / liczby całkowite (e.g., 42, -10)
  • float — decimal numbers / liczby zmiennoprzecinkowe (e.g., 3.14, -0.5)
  • str — text / tekst (e.g., "Hello", 'Python')
  • bool — truth values / wartości logiczne (True, False)
  • list — ordered collections / uporządkowane kolekcje (e.g., [1, 2, 3])
  • dict — key-value pairs / pary klucz-wartość (e.g., {"name": "Alice"})
  • tuple — immutable ordered collections / niemutowalne kolekcje (e.g., (1, 2, 3))
  • set — unordered collections of unique items / nieuporządkowane zbiory unikalnych elementów (e.g., {1, 2, 3})

📚 Basic Data Types / Podstawowe typy danych

📘 Official documentation / Oficjalna dokumentacja:

Python Built-in Types

Read sections: "Truth Value Testing", "Numeric Types", and "Text Sequence Type" / Przeczytaj sekcje: "Truth Value Testing", "Numeric Types" i "Text Sequence Type"

🎥 Video / Wideo:

Corey Schafer – Python Tutorial: Variables and Data Types (0:00–15:00)

Covers variables, numbers, strings, and basic operations / Omawia zmienne, liczby, stringi i podstawowe operacje

🧩 Zadania / Tasks:

Create a file basics.py:

Utwórz plik basics.py:

# Numbers / Liczby
age = 25              # int / liczba całkowita
height = 1.75         # float / liczba zmiennoprzecinkowa
temperature = -5      # negative int / ujemna liczba całkowita

# Text / Tekst
name = "Alice"        # str / string
greeting = 'Hello'    # single quotes work too / pojedyncze cudzysłowy też działają

# Boolean / Wartości logiczne
is_student = True     # bool
has_license = False   # bool

# Print with type information / Wyświetl z informacją o typie
print(f"{age=}, type: {type(age)}")
print(f"{height=}, type: {type(height)}")
print(f"{name=}, type: {type(name)}")
print(f"{is_student=}, type: {type(is_student)}")

# Basic operations / Podstawowe operacje
sum_result = 10 + 5
difference = 10 - 3
product = 4 * 3
quotient = 15 / 3        # float division / dzielenie zwraca float
floor_div = 15 // 4      # integer division / dzielenie całkowite
remainder = 15 % 4       # modulo / reszta z dzielenia
power = 2 ** 3           # exponentiation / potęgowanie

print(f"\n{sum_result=}, {difference=}, {product=}")
print(f"{quotient=}, {floor_div=}, {remainder=}, {power=}")

# String operations / Operacje na stringach
full_name = name + " Smith"        # concatenation / łączenie
repeated = "Ha" * 3                # repetition / powtarzanie
length = len(name)                 # length / długość

print(f"\n{full_name=}, {repeated=}, {length=}")

Run the program:

Uruchom program:

uv run python basics.py

Expected output / Oczekiwany rezultat:

age=25, type: <class 'int'>
height=1.75, type: <class 'float'>
name='Alice', type: <class 'str'>
is_student=True, type: <class 'bool'>

sum_result=15, difference=7, product=12
quotient=5.0, floor_div=3, remainder=3, power=8

full_name='Alice Smith', repeated='HaHaHa', length=5

💾 Save to Git / Zapisz w Git:

After completing this exercise, save your work to Git:

Po ukończeniu tego ćwiczenia, zapisz pracę w Git:

git add basics.py
git commit -m "Add basics.py - variables and data types exercise"

📦 Collections / Kolekcje

Collections store multiple items — like containers that organize related data.

Kolekcje przechowują wiele elementów — jak pojemniki organizujące powiązane dane.

🧩 Zadanie / Task:

Create a file collections.py:

Utwórz plik collections.py:

# List — ordered, mutable / uporządkowana, mutowalna
fruits = ["apple", "banana", "cherry"]
fruits.append("orange")        # add item / dodaj element
fruits[0] = "apricot"         # change item / zmień element
print(f"Fruits: {fruits}")
print(f"First fruit: {fruits[0]}")
print(f"Last fruit: {fruits[-1]}")

# Tuple — ordered, immutable / uporządkowana, niemutowalna
coordinates = (10, 20)
# coordinates[0] = 15  # This would raise an error! / To spowodowałoby błąd!
x, y = coordinates     # unpacking / rozpakowywanie
print(f"\nCoordinates: {coordinates}, x={x}, y={y}")

# Dictionary — key-value pairs / pary klucz-wartość
person = {
    "name": "Alice",
    "age": 25,
    "city": "Warsaw"
}
person["email"] = "alice@example.com"  # add new key / dodaj nowy klucz
print(f"\nPerson: {person}")
print(f"Name: {person['name']}")
print(f"Age: {person.get('age')}")

# Set — unique items, unordered / unikalne elementy, nieuporządkowane
numbers = {1, 2, 3, 3, 4, 4}  # duplicates removed / duplikaty usunięte
numbers.add(5)
print(f"\nNumbers: {numbers}")

# Common operations / Powszechne operacje
print(f"\nList length: {len(fruits)}")
print(f"Is 'apple' in fruits? {'apple' in fruits}")
print(f"Is 'grape' in fruits? {'grape' in fruits}")

Run the program:

Uruchom program:

uv run python collections.py

Expected output / Oczekiwany rezultat:

Fruits: ['apricot', 'banana', 'cherry', 'orange']
First fruit: apricot
Last fruit: orange

Coordinates: (10, 20), x=10, y=20

Person: {'name': 'Alice', 'age': 25, 'city': 'Warsaw', 'email': 'alice@example.com'}
Name: Alice
Age: 25

Numbers: {1, 2, 3, 4, 5}

List length: 4
Is 'apple' in fruits? False
Is 'grape' in fruits? False

💾 Save to Git / Zapisz w Git:

git add collections.py
git commit -m "Add collections.py - lists, dicts, tuples, sets"

💡 2️⃣ Kontrola przepływu / Control Flow

Control flow lets your program make decisions and repeat actions — like following a recipe that says "if eggs are cold, wait 5 minutes; otherwise, continue."

Kontrola przepływu pozwala programowi podejmować decyzje i powtarzać działania — jak przepis mówiący "jeśli jajka są zimne, poczekaj 5 minut; w przeciwnym razie kontynuuj."


🔀 Conditional statements / Instrukcje warunkowe

Conditional statements with if, elif, and else let you make decisions based on conditions.

Instrukcje warunkowe if, elif i else pozwalają podejmować decyzje na podstawie warunków.

📘 Documentation / Dokumentacja:

Python Control Flow Tutorial

Read sections: "if Statements" / Przeczytaj sekcję: "if Statements"

🎥 Video / Wideo:

Corey Schafer – Conditionals and Booleans (0:00–20:00)

Explains if/elif/else and comparison operators / Wyjaśnia if/elif/else i operatory porównania

🧩 Zadanie / Task:

Create a file control_flow.py:

Utwórz plik control_flow.py:

# Simple if statement / Prosta instrukcja if
age = 20

if age >= 18:
    print("You are an adult")  # Jesteś dorosły
else:
    print("You are a minor")   # Jesteś niepełnoletni

# Multiple conditions / Wiele warunków
score = 85

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

print(f"Score: {score}, Grade: {grade}")

# Match-case statement (Python 3.10+) — like switch in other languages
# Match-case (Python 3.10+) — jak switch w innych językach
status_code = 200

match status_code:
    case 200:
        message = "OK"
    case 404:
        message = "Not Found"
    case 500:
        message = "Server Error"
    case _:  # default case / domyślny przypadek
        message = "Unknown Status"

print(f"\nStatus {status_code}: {message}")

# Match-case with ranges (combining with if) / Match-case z zakresami (łącząc z if)
grade_score = 85

match grade_score:
    case score if score >= 90:
        grade_letter = "A"
    case score if score >= 80:
        grade_letter = "B"
    case score if score >= 70:
        grade_letter = "C"
    case _:
        grade_letter = "F"

print(f"Grade score {grade_score}: {grade_letter}")

# Comparison operators / Operatory porównania
x = 10
y = 20

print(f"\n{x} == {y}: {x == y}")  # equal / równe
print(f"{x} != {y}: {x != y}")    # not equal / nierówne
print(f"{x} < {y}: {x < y}")      # less than / mniejsze
print(f"{x} > {y}: {x > y}")      # greater than / większe
print(f"{x} <= {y}: {x <= y}")    # less or equal / mniejsze lub równe
print(f"{x} >= {y}: {x >= y}")    # greater or equal / większe lub równe

# Logical operators / Operatory logiczne
is_student = True
has_id = True

if is_student and has_id:
    print("\nYou get a student discount")  # Dostajesz zniżkę studencką

is_weekend = False
is_holiday = True

if is_weekend or is_holiday:
    print("Time to relax!")  # Czas na odpoczynek!

is_raining = True
if not is_raining:
    print("Let's go outside!")  # Chodźmy na zewnątrz!
else:
    print("Stay inside")  # Zostań w środku

# Checking for None — use 'is' not '==' / Sprawdzanie None — użyj 'is' nie '=='
# 'is' checks identity (same object), '==' checks equality (same value)
# 'is' sprawdza tożsamość (ten sam obiekt), '==' sprawdza równość (ta sama wartość)
# For None, always use 'is' — it's more precise and idiomatic
# Dla None, zawsze używaj 'is' — jest bardziej precyzyjne i idiomatyczne

user_name = None
default_name = "Guest"

if user_name is None:
    print(f"\nWelcome, {default_name}!")  # Witaj, Gościu!
else:
    print(f"\nWelcome, {user_name}!")

# Also check 'is not None' / Również sprawdź 'is not None'
email = "user@example.com"

if email is not None:
    print(f"Sending email to: {email}")  # Wysyłanie emaila do: {email}

Run the program:

Uruchom program:

uv run python control_flow.py

Expected output / Oczekiwany rezultat:

You are an adult
Score: 85, Grade: B

Status 200: OK
Grade score 85: B

10 == 20: False
10 != 20: True
10 < 20: True
10 > 20: False
10 <= 20: True
10 >= 20: False

You get a student discount
Time to relax!
Stay inside

Welcome, Guest!
Sending email to: user@example.com

💾 Save to Git / Zapisz w Git:

git add control_flow.py
git commit -m "Add control_flow.py - if/elif/else, match-case, logical operators, None checking"

🔁 Loops / Pętle

Loops repeat actions — like doing dishes one by one until the sink is empty.

Pętle powtarzają działania — jak zmywanie naczyń jedno po drugim, aż zlew będzie pusty.

for loop — iterate over sequences / iteruj po sekwencjach

I find for loops work well when you know how many times to repeat or have a collection to iterate through.

Stwierdziłem, że pętle for działają dobrze, gdy wiesz ile razy powtórzyć lub masz kolekcję do iteracji.

while loop — repeat while condition is true / powtarzaj dopóki warunek jest prawdziwy

I reach for while loops when I don't know in advance how many iterations will be needed.

Sięgam po pętle while, gdy nie wiem z góry ile iteracji będzie potrzebnych.

🎥 Video / Wideo:

Corey Schafer – Python Loops Tutorial (0:00–25:00)

Covers for and while loops with examples / Omawia pętle for i while z przykładami

🧩 Zadanie / Task:

Create a file loops.py:

Utwórz plik loops.py:

# For loop with list / Pętla for z listą
fruits = ["apple", "banana", "cherry"]

print("Fruits:")
for fruit in fruits:
    print(f"  - {fruit}")

# For loop with range / Pętla for z zakresem
print("\nNumbers 0-4:")
for i in range(5):  # 0, 1, 2, 3, 4
    print(i, end=" ")

print("\n\nNumbers 1-5:")
for i in range(1, 6):  # 1, 2, 3, 4, 5
    print(i, end=" ")

print("\n\nEven numbers 0-10:")
for i in range(0, 11, 2):  # start, stop, step
    print(i, end=" ")

# For loop with enumerate / Pętla for z enumerate
print("\n\nFruits with index:")
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")

# For loop with dictionary / Pętla for ze słownikiem
person = {"name": "Alice", "age": 25, "city": "Warsaw"}

print("\nPerson details:")
for key, value in person.items():
    print(f"{key}: {value}")

# While loop / Pętla while
count = 0
print("\nCounting to 5:")
while count < 5:
    print(count, end=" ")
    count += 1

# Break and continue / Break i continue
print("\n\nFinding first even number:")
numbers = [1, 3, 5, 8, 9, 10]
for num in numbers:
    if num % 2 == 0:
        print(f"Found: {num}")
        break  # exit loop / wyjdź z pętli

print("\nSkipping odd numbers:")
for num in range(10):
    if num % 2 != 0:
        continue  # skip this iteration / pomiń tę iterację
    print(num, end=" ")

print()  # new line / nowa linia

Run the program:

Uruchom program:

uv run python loops.py

Expected output / Oczekiwany rezultat:

Fruits:
  - apple
  - banana
  - cherry

Numbers 0-4:
0 1 2 3 4

Numbers 1-5:
1 2 3 4 5

Even numbers 0-10:
0 2 4 6 8 10

Fruits with index:
0: apple
1: banana
2: cherry

Person details:
name: Alice
age: 25
city: Warsaw

Counting to 5:
0 1 2 3 4

Finding first even number:
Found: 8

Skipping odd numbers:
0 2 4 6 8

💾 Save to Git / Zapisz w Git:

git add loops.py
git commit -m "Add loops.py - for and while loops with break/continue"

💡 3️⃣ Funkcje / Functions

A function is a reusable block of code that performs a specific task — like a recipe you can follow multiple times.

Funkcja to wielokrotnego użytku blok kodu, który wykonuje konkretne zadanie — jak przepis, którego możesz używać wielokrotnie.

Functions help you:

Funkcje pomagają Ci:

  • Avoid repetition (DRY principle) / Unikać powtórzeń (zasada DRY)
  • Organize code into logical chunks / Organizować kod w logiczne części
  • Make code easier to test and debug / Ułatwiać testowanie i debugowanie kodu

📘 Documentation / Dokumentacja:

Python Functions Tutorial

Read sections: "Defining Functions" and "Default Argument Values" / Przeczytaj sekcje: "Defining Functions" i "Default Argument Values"

🎥 Video / Wideo:

Corey Schafer – Python Functions (0:00–30:00)

Covers function basics, parameters, return values, and scope / Omawia podstawy funkcji, parametry, wartości zwracane i zakres


🛠️ Function basics / Podstawy funkcji

🧩 Zadanie / Task:

Create a file functions.py:

Utwórz plik functions.py:

from typing import List, Tuple, Any

# Simple function / Prosta funkcja
def greet() -> None:
    print("Hello, World!")

greet()

# Function with parameters / Funkcja z parametrami
def greet_person(name: str) -> None:
    print(f"Hello, {name}!")

greet_person("Alice")
greet_person("Bob")

# Function with return value / Funkcja ze zwracaną wartością
def add(a: int, b: int) -> int:
    return a + b

result = add(5, 3)
print(f"\n5 + 3 = {result}")

# Function with default parameters / Funkcja z domyślnymi parametrami
def greet_with_title(name: str, title: str = "Mr.") -> str:
    return f"Hello, {title} {name}!"

print(greet_with_title("Smith"))
print(greet_with_title("Smith", "Dr."))

# Function with multiple return values / Funkcja zwracająca wiele wartości
def get_min_max(numbers: List[int]) -> Tuple[int, int]:
    return min(numbers), max(numbers)

nums = [3, 7, 1, 9, 2]
minimum, maximum = get_min_max(nums)
print(f"\nNumbers: {nums}")
print(f"Min: {minimum}, Max: {maximum}")

# Function with *args (variable arguments) / Funkcja z *args (zmienne argumenty)
def sum_all(*numbers: int) -> int:
    total = 0
    for num in numbers:
        total += num
    return total

print(f"\nsum_all(1, 2, 3) = {sum_all(1, 2, 3)}")
print(f"sum_all(1, 2, 3, 4, 5) = {sum_all(1, 2, 3, 4, 5)}")

# Function with **kwargs (keyword arguments) / Funkcja z **kwargs (argumenty nazwane)
def print_info(**info: Any) -> None:
    for key, value in info.items():
        print(f"{key}: {value}")

print("\nUser info:")
print_info(name="Alice", age=25, city="Warsaw")

# Docstrings — function documentation / Docstringi — dokumentacja funkcji
def calculate_area(width: float, height: float) -> float:
    """
    Calculate rectangle area.

    Args:
        width: Rectangle width in meters
        height: Rectangle height in meters

    Returns:
        Area in square meters
    """
    return width * height

# Access docstring / Dostęp do docstringa
print(f"\n{calculate_area.__doc__}")
print(f"Area: {calculate_area(5, 3)} m²")

Run the program:

Uruchom program:

uv run python functions.py

Expected output / Oczekiwany rezultat:

Hello, World!
Hello, Alice!
Hello, Bob!

5 + 3 = 8
Hello, Mr. Smith!
Hello, Dr. Smith!

Numbers: [3, 7, 1, 9, 2]
Min: 1, Max: 9

sum_all(1, 2, 3) = 6
sum_all(1, 2, 3, 4, 5) = 15

User info:
name: Alice
age: 25
city: Warsaw


    Calculate rectangle area.

    Args:
        width: Rectangle width in meters
        height: Rectangle height in meters

    Returns:
        Area in square meters

Area: 15 m²

💾 Save to Git / Zapisz w Git:

git add functions.py
git commit -m "Add functions.py - functions, parameters, docstrings"

🎯 Lambda functions / Funkcje lambda

Lambda functions are small, anonymous functions — I find them handy for simple operations where writing a full function would be overkill.

Funkcje lambda to małe, anonimowe funkcje — uważam je za przydatne do prostych operacji, gdzie pisanie pełnej funkcji byłoby przesadą.

🧩 Zadanie / Task:

Add to functions.py:

Dodaj do functions.py:

# Lambda function / Funkcja lambda
square = lambda x: x ** 2
print(f"\nSquare of 5: {square(5)}")

# Lambda with multiple parameters / Lambda z wieloma parametrami
add = lambda a, b: a + b
print(f"3 + 4 = {add(3, 4)}")

# Lambda with map / Lambda z map
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(f"\nNumbers: {numbers}")
print(f"Squared: {squared}")

# Lambda with filter / Lambda z filter
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(f"Even numbers: {even_numbers}")

# Lambda with sorted / Lambda z sorted
people = [
    {"name": "Alice", "age": 25},
    {"name": "Bob", "age": 30},
    {"name": "Charlie", "age": 20}
]

sorted_by_age = sorted(people, key=lambda person: person["age"])
print(f"\nSorted by age:")
for person in sorted_by_age:
    print(f"  {person['name']}: {person['age']}")

💾 Update Git / Zaktualizuj Git:

You've added lambda functions to functions.py. Update your commit:

Dodałeś funkcje lambda do functions.py. Zaktualizuj commit:

git add functions.py
git commit -m "Update functions.py - add lambda functions and map/filter"

🎯 Mini-projekt 1 / Mini-project 1: Monty Hall Problem

Napisz program, który symuluje paradoks Monty'ego Halla.

Write a program that simulates the Monty Hall problem.

Problem description / Opis problemu

There are three doors: one has a prize, two have zonks.

Są trzy drzwi: za jednymi jest nagroda, za dwoma zonk.

Player chooses one door.

Gracz wybiera jedne drzwi.

Host opens one of the remaining doors, revealing a zonk (never the prize!).

Prowadzący otwiera jedne z pozostałych drzwi, pokazując zonk (nigdy nagrody!).

Player can switch their choice or stay with the original.

Gracz może zmienić swój wybór lub pozostać przy oryginalnym.

Task / Zadanie

Create a file game.py to simulate two strategies:

Utwórz plik game.py aby zasymulować dwie strategie:

Scenario 1: Player always stays with their original choice

Scenariusz 1: Gracz zawsze pozostaje przy swoim oryginalnym wyborze

Scenario 2: Player always switches doors

Scenariusz 2: Gracz zawsze zmienia drzwi

Run 1000 simulation rounds for each scenario. What % of wins does the player get in each scenario?

Przeprowadź 1000 rund symulacji dla każdego scenariusza. Jaki % wygranych ma gracz w każdym scenariuszu?

Hints / Podpowiedzi

Find the right library for randomization (check Python docs for the random module).

Znajdź odpowiednią bibliotekę do losowania (sprawdź dokumentację Pythona o module random).

Think about how to represent the game steps in code.

Przemyśl, jak przedstawić kolejne kroki gry w kodzie.

How to collect results for 1 run, then for multiple runs?

Jak zebrać wyniki dla 1 wykonania, a później dla wielu?

Use the debugger if needed.

Używaj debugera, jeśli potrzebujesz.

Don't ask AI for code*

Nie pytaj AI o kod

Save and compare / Zapisz i porównaj

git add game.py
git commit -m "Add game.py - Monty Hall simulation"

After completing the simulation, compare your results with the theory:

Po ukończeniu symulacji, porównaj wyniki z teorią:


💡 4️⃣ Klasy i programowanie obiektowe / Classes and Object-Oriented Programming

Object-oriented programming (OOP) organizes code into objects that combine data and behavior — like modeling real-world things in code.

Programowanie obiektowe (OOP) organizuje kod w obiekty, które łączą dane i zachowanie — jak modelowanie rzeczy z rzeczywistego świata w kodzie.

A class is a blueprint for creating objects — like a cookie cutter that defines the shape.

Klasa to szablon do tworzenia obiektów — jak foremka do ciastek określająca kształt.

An object (or instance) is a specific example created from a class — like an actual cookie made from the cutter.

Obiekt (lub instancja) to konkretny przykład utworzony z klasy — jak prawdziwe ciastko zrobione z foremki.

📘 Documentation / Dokumentacja:

Python Classes Tutorial

Read sections: "Classes" and "Class Objects" / Przeczytaj sekcje: "Classes" i "Class Objects"

🎥 Video / Wideo:

Corey Schafer – OOP Tutorial Series (0:00–40:00)

Part 1 covers classes, instances, and methods / Część 1 omawia klasy, instancje i metody


🏗️ Creating classes / Tworzenie klas

🧩 Zadanie / Task:

Create a file classes.py:

Utwórz plik classes.py:

from typing import Any

# Simple class / Prosta klasa
class Dog:
    """Represents a dog / Reprezentuje psa"""

    def __init__(self, name: str, age: int) -> None:
        """Initialize dog attributes / Zainicjuj atrybuty psa"""
        self.name = name  # instance variable / zmienna instancji
        self.age = age

    def bark(self) -> str:
        """Make the dog bark / Spraw, że pies szczeka"""
        return f"{self.name} says Woof!"

    def get_info(self) -> str:
        """Return dog information / Zwróć informacje o psie"""
        return f"{self.name} is {self.age} years old"

# Create instances / Utwórz instancje
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)

print(dog1.bark())
print(dog2.bark())
print(dog1.get_info())
print(dog2.get_info())

# Class with class variables / Klasa ze zmiennymi klasowymi
class Cat:
    """Represents a cat / Reprezentuje kota"""

    species: str = "Felis catus"  # class variable / zmienna klasowa

    def __init__(self, name: str, color: str) -> None:
        self.name = name      # instance variable / zmienna instancji
        self.color = color

    def describe(self) -> str:
        return f"{self.name} is a {self.color} cat of species {Cat.species}"

cat1 = Cat("Whiskers", "orange")
cat2 = Cat("Shadow", "black")

print(f"\n{cat1.describe()}")
print(f"{cat2.describe()}")

# Inheritance / Dziedziczenie
# Important: follows Liskov Substitution Principle (LSP) / Ważne: zgodne z zasadą podstawienia Liskov (LSP)
# LSP: You can write code for base class, and it will work with ANY subclass
# LSP: Możesz napisać kod dla klasy bazowej i będzie działał z KAŻDĄ podklasą
#
# Think of inheritance as: "A can be REPLACED by B", not "B is A"
# Myśl o dziedziczeniu jako: "A może być ZASTĄPIONE przez B", nie "B jest A"
# Example: "Employee can be replaced by PartTimeEmployee" ✓
# Przykład: "Employee może być zastąpiony przez PartTimeEmployee" ✓
# Read more / Czytaj więcej: https://en.wikipedia.org/wiki/Liskov_substitution_principle

class Employee:
    """
    Base class for employees - defines the contract / Klasa bazowa dla pracowników - definiuje kontrakt
    Subclasses MUST implement get_hours_per_week() / Podklasy MUSZĄ zaimplementować get_hours_per_week()
    """

    def __init__(self, name: str, employee_id: str, hourly_rate: float) -> None:
        self.name = name
        self.employee_id = employee_id
        self.hourly_rate = hourly_rate

    def get_hours_per_week(self) -> int:
        """
        Subclasses must implement this / Podklasy muszą to zaimplementować
        This is the contract that all employees must fulfill / To jest kontrakt, który wszyscy pracownicy muszą spełnić
        """
        raise NotImplementedError("Subclass must implement get_hours_per_week()")

    def calculate_weekly_pay(self) -> float:
        """Calculate weekly payment / Oblicz tygodniowe wynagrodzenie"""
        return self.hourly_rate * self.get_hours_per_week()

    def get_info(self) -> str:
        """Get employee information / Uzyskaj informacje o pracowniku"""
        return f"{self.name} (ID: {self.employee_id}), {self.get_hours_per_week()}h/week"

class PartTimeEmployee(Employee):
    """Part-time employee / Pracownik na część etatu"""

    def __init__(self, name: str, employee_id: str, hourly_rate: float, hours: int) -> None:
        super().__init__(name, employee_id, hourly_rate)
        self.hours = hours

    def get_hours_per_week(self) -> int:
        """Implementation: part-time has custom hours / Implementacja: część etatu ma własne godziny"""
        return self.hours

class FullTimeEmployee(Employee):
    """Full-time employee with benefits / Pracownik pełnoetatowy z benefitami"""

    def __init__(self, name: str, employee_id: str, hourly_rate: float, benefits: float) -> None:
        super().__init__(name, employee_id, hourly_rate)
        self.benefits = benefits

    def get_hours_per_week(self) -> int:
        """Implementation: full-time is standard 40 hours / Implementacja: pełny etat to standardowe 40 godzin"""
        return 40

    def calculate_weekly_pay(self) -> float:
        """Override: add benefits to payment / Nadpisz: dodaj benefity do wypłaty"""
        return super().calculate_weekly_pay() + self.benefits

# Create instances / Utwórz instancje
part_time = PartTimeEmployee("Alice", "E001", 25.0, 20)
full_time = FullTimeEmployee("Bob", "E002", 30.0, 200)

print(f"\n{part_time.get_info()}")
print(f"Weekly pay: ${part_time.calculate_weekly_pay():.2f}")

print(f"\n{full_time.get_info()}")
print(f"Weekly pay: ${full_time.calculate_weekly_pay():.2f}")

# ✅ LSP in action: code written for Employee works with ANY subclass
# ✅ LSP w akcji: kod napisany dla Employee działa z KAŻDĄ podklasą
def process_payroll(employees: list[Employee]) -> None:
    """
    This function works with Employee and ALL its subclasses!
    Ta funkcja działa z Employee i WSZYSTKIMI jego podklasami!

    We wrote code for Employee, but it works with PartTimeEmployee and FullTimeEmployee too.
    Napisaliśmy kod dla Employee, ale działa z PartTimeEmployee i FullTimeEmployee też.
    """
    total = 0.0
    print("\n--- Payroll Processing / Przetwarzanie wypłat ---")
    for emp in employees:
        pay = emp.calculate_weekly_pay()
        total += pay
        print(f"{emp.name}: ${pay:.2f}")
    print(f"Total weekly payroll: ${total:.2f}")

# Works with any Employee subclass! / Działa z każdą podklasą Employee!
process_payroll([part_time, full_time])

print("\n" + "="*60)
print("❌ COUNTER-EXAMPLE 1: Bird/Penguin - VIOLATES LSP")
print("❌ KONTR-PRZYKŁAD 1: Ptak/Pingwin - NARUSZA LSP")
print("="*60)

# ❌ This is a classic WRONG example of inheritance / To jest klasyczny ZŁY przykład dziedziczenia
class Bird:
    """Bird that can fly / Ptak, który potrafi latać"""

    def __init__(self, name: str) -> None:
        self.name = name

    def fly(self) -> str:
        """All birds can fly... right? / Wszystkie ptaki potrafią latać... prawda?"""
        return f"{self.name} is flying!"

class Penguin(Bird):
    """Penguin is a bird, but... / Pingwin jest ptakiem, ale..."""

    def fly(self) -> str:
        # Problem: Penguins can't fly! / Problem: Pingwiny nie potrafią latać!
        raise Exception(f"{self.name} can't fly - penguins are flightless!")  # ❌ Breaks LSP!

# Code written for Bird / Kod napisany dla Bird
def make_bird_fly(bird: Bird) -> None:
    """
    This function expects all Birds can fly / Ta funkcja oczekuje, że wszystkie Birds potrafią latać
    But Penguin breaks this expectation! / Ale Penguin psuje to oczekiwanie!
    """
    try:
        result = bird.fly()
        print(result)
    except Exception as e:
        print(f"✗ ERROR: {e}")

print("\nTesting regular Bird:")
sparrow = Bird("Sparrow")
make_bird_fly(sparrow)  # ✓ Works / Działa

print("\nTesting Penguin:")
penguin = Penguin("Pingu")
make_bird_fly(penguin)  # ✗ CRASHES! Violates LSP / WYWALA SIĘ! Narusza LSP

print("\n💡 Problem: 'Penguin IS-A Bird' in real life, but NOT in code!")
print("💡 Problem: 'Pingwin JEST ptakiem' w rzeczywistości, ale NIE w kodzie!")
print("The base class Bird promises fly() works, but Penguin breaks that promise.")
print("Klasa bazowa Bird obiecuje, że fly() działa, ale Penguin łamie tę obietnicę.")

print("\n" + "="*60)
print("❌ COUNTER-EXAMPLE 2: Rectangle/Square - VIOLATES LSP")
print("❌ KONTR-PRZYKŁAD 2: Prostokąt/Kwadrat - NARUSZA LSP")
print("="*60)

# ❌ This is a classic WRONG example of inheritance / To jest klasyczny ZŁY przykład dziedziczenia
class Rectangle:
    """Rectangle with independent width and height / Prostokąt z niezależnymi szerokością i wysokością"""

    def __init__(self, width: float, height: float) -> None:
        self.width = width
        self.height = height

    def set_width(self, width: float) -> None:
        self.width = width

    def set_height(self, height: float) -> None:
        self.height = height

    def get_area(self) -> float:
        return self.width * self.height

class Square(Rectangle):
    """Square - seems like "Square IS-A Rectangle" but BREAKS substitution!"""
    """Kwadrat - wygląda jak "Kwadrat JEST prostokątem" ale PSUJE podstawienie!"""

    def set_width(self, width: float) -> None:
        # Square must keep width == height / Kwadrat musi trzymać width == height
        self.width = width
        self.height = width  # ❌ This breaks LSP!

    def set_height(self, height: float) -> None:
        # Square must keep width == height / Kwadrat musi trzymać width == height
        self.width = height  # ❌ This breaks LSP!
        self.height = height

# Code written for Rectangle / Kod napisany dla Rectangle
def test_rectangle(rect: Rectangle) -> None:
    """
    This function expects Rectangle behavior / Ta funkcja oczekuje zachowania Rectangle
    But Square breaks this expectation! / Ale Square psuje to oczekiwanie!
    """
    rect.set_width(5)
    rect.set_height(10)
    area = rect.get_area()
    print(f"Width: {rect.width}, Height: {rect.height}, Area: {area}")

    # For Rectangle: we expect area = 50 (5 * 10)
    # Dla Rectangle: oczekujemy area = 50 (5 * 10)
    if area == 50:
        print("✓ Works as expected / Działa zgodnie z oczekiwaniami")
    else:
        print(f"✗ BROKEN! Expected 50, got {area} / ZEPSUTE! Oczekiwano 50, otrzymano {area}")

print("\nTesting Rectangle:")
test_rectangle(Rectangle(0, 0))  # ✓ Works / Działa

print("\nTesting Square:")
test_rectangle(Square(0, 0))  # ✗ FAILS! Violates LSP / PSUJE SIĘ! Narusza LSP

print("\n💡 Key Lessons from BOTH counter-examples:")
print("💡 Kluczowe lekcje z OBUDWU kontr-przykładów:")
print("1. Bird/Penguin: Don't inherit if subclass can't fulfill base class contract (fly)")
print("1. Ptak/Pingwin: Nie dziedzicz, jeśli podklasa nie może spełnić kontraktu klasy bazowej (fly)")
print("2. Rectangle/Square: Don't inherit if subclass changes base class behavior")
print("2. Prostokąt/Kwadrat: Nie dziedzicz, jeśli podklasa zmienia zachowanie klasy bazowej")
print("\n✓ Use inheritance ONLY when: A can be REPLACED by B without breaking anything")
print("✓ Używaj dziedziczenia TYLKO gdy: A może być ZASTĄPIONE przez B bez psowania czegokolwiek")

# Special methods / Metody specjalne
class Book:
    """Represents a book / Reprezentuje książkę"""

    def __init__(self, title: str, author: str, pages: int) -> None:
        self.title = title
        self.author = author
        self.pages = pages

    def __str__(self) -> str:
        """String representation for users / Reprezentacja stringowa dla użytkowników"""
        return f"'{self.title}' by {self.author}"

    def __repr__(self) -> str:
        """String representation for developers / Reprezentacja stringowa dla programistów"""
        return f"Book(title='{self.title}', author='{self.author}', pages={self.pages})"

    def __len__(self) -> int:
        """Return book length / Zwróć długość książki"""
        return self.pages

    def __eq__(self, other: Any) -> bool:
        """Check equality / Sprawdź równość"""
        if not isinstance(other, Book):
            return False
        return self.title == other.title and self.author == other.author

book1 = Book("1984", "George Orwell", 328)
book2 = Book("1984", "George Orwell", 328)

print(f"\n{book1}")           # uses __str__
print(f"{repr(book1)}")       # uses __repr__
print(f"Pages: {len(book1)}") # uses __len__
print(f"Books equal? {book1 == book2}")  # uses __eq__

Run the program:

Uruchom program:

uv run python classes.py

Expected output / Oczekiwany rezultat:

Buddy says Woof!
Max says Woof!
Buddy is 3 years old
Max is 5 years old

Whiskers is a orange cat of species Felis catus
Shadow is a black cat of species Felis catus

Alice (ID: E001), 20h/week
Weekly pay: $500.00

Bob (ID: E002), 40h/week
Weekly pay: $1400.00

--- Payroll Processing / Przetwarzanie wypłat ---
Alice: $500.00
Bob: $1400.00
Total weekly payroll: $1900.00

============================================================
❌ COUNTER-EXAMPLE 1: Bird/Penguin - VIOLATES LSP
❌ KONTR-PRZYKŁAD 1: Ptak/Pingwin - NARUSZA LSP
============================================================

Testing regular Bird:
Sparrow is flying!

Testing Penguin:
✗ ERROR: Pingu can't fly - penguins are flightless!

💡 Problem: 'Penguin IS-A Bird' in real life, but NOT in code!
💡 Problem: 'Pingwin JEST ptakiem' w rzeczywistości, ale NIE w kodzie!
The base class Bird promises fly() works, but Penguin breaks that promise.
Klasa bazowa Bird obiecuje, że fly() działa, ale Penguin łamie tę obietnicę.

============================================================
❌ COUNTER-EXAMPLE 2: Rectangle/Square - VIOLATES LSP
❌ KONTR-PRZYKŁAD 2: Prostokąt/Kwadrat - NARUSZA LSP
============================================================

Testing Rectangle:
Width: 5, Height: 10, Area: 50.0
✓ Works as expected / Działa zgodnie z oczekiwaniami

Testing Square:
Width: 10, Height: 10, Area: 100.0
✗ BROKEN! Expected 50, got 100.0 / ZEPSUTE! Oczekiwano 50, otrzymano 100.0

💡 Key Lessons from BOTH counter-examples:
💡 Kluczowe lekcje z OBUÓW kontr-przykładów:
1. Bird/Penguin: Don't inherit if subclass can't fulfill base class contract (fly)
1. Ptak/Pingwin: Nie dziedzicz, jeśli podklasa nie może spełnić kontraktu klasy bazowej (fly)
2. Rectangle/Square: Don't inherit if subclass changes base class behavior
2. Prostokąt/Kwadrat: Nie dziedzicz, jeśli podklasa zmienia zachowanie klasy bazowej

✓ Use inheritance ONLY when: A can be REPLACED by B without breaking anything
✓ Używaj dziedziczenia TYLKO gdy: A może być ZASTĄPIONE przez B bez psowania czegokolwiek

'1984' by George Orwell
Book(title='1984', author='George Orwell', pages=328)
Pages: 328
Books equal? True

💾 Save to Git / Zapisz w Git:

git add classes.py
git commit -m "Add classes.py - OOP with LSP (Employee, Bird/Penguin, Rectangle/Square), special methods"

💡 5️⃣ Type hints i dataclass / Type Hints and Dataclass

Type hints add optional type information to your code — like labels on storage boxes telling you what's inside.

Type hints dodają opcjonalne informacje o typach do kodu — jak etykiety na pudłach mówiące, co jest w środku.

They don't change how code runs, but help catch errors early and make code more readable.

Nie zmieniają działania kodu, ale pomagają wcześniej wychwytywać błędy i czynią kod bardziej czytelnym.

dataclass is a decorator that automatically generates common methods for classes that mainly store data — reducing boilerplate code.

dataclass to dekorator, który automatycznie generuje powszechne metody dla klas przechowujących głównie dane — redukując kod kotłowy.

📘 Documentation / Dokumentacja:

Python Type Hints

Read sections: "Type Hints" and "Support for type hints" / Przeczytaj sekcje: "Type Hints" i "Support for type hints"

Python Dataclasses

Read sections: "Module-level decorators" and "Post-init processing" / Przeczytaj sekcje: "Module-level decorators" i "Post-init processing"

🎥 Video / Wideo:

ArjanCodes – Python Type Hints Tutorial (0:00–20:00)

Covers type hints basics and benefits / Omawia podstawy type hints i korzyści

mCoding – Dataclasses in Python (0:00–15:00)

Explains dataclasses and when to use them / Wyjaśnia dataclass i kiedy ich używać

🧠 Dlaczego to ważne / Why it matters:

I've found that type hints make code self-documenting and help IDEs catch errors before you even run the program. Dataclasses eliminate repetitive boilerplate code for data-centric classes — something I really appreciate when working with data models.

Odkryłem, że type hints czynią kod samodokumentującym i pomagają IDE wychwytywać błędy zanim jeszcze uruchomisz program. Dataclass eliminuje powtarzalny kod kotłowy dla klas skoncentrowanych na danych — coś, co naprawdę doceniam podczas pracy z modelami danych.


🏷️ Type hints / Type hints

🧩 Zadanie / Task:

Create a file type_hints.py:

Utwórz plik type_hints.py:

from typing import List, Dict, Optional, Union, Tuple

# Basic type hints / Podstawowe type hints
def greet(name: str) -> str:
    """Greet a person by name / Powitaj osobę po imieniu"""
    return f"Hello, {name}!"

def add(a: int, b: int) -> int:
    """Add two integers / Dodaj dwie liczby całkowite"""
    return a + b

def calculate_average(numbers: List[float]) -> float:
    """Calculate average of numbers / Oblicz średnią liczb"""
    return sum(numbers) / len(numbers)

# Testing functions / Testowanie funkcji
print(greet("Alice"))
print(f"5 + 3 = {add(5, 3)}")
print(f"Average: {calculate_average([10.5, 20.0, 15.5])}")

# Optional type — can be value or None / Typ opcjonalny — może być wartością lub None
def find_user(user_id: int) -> Optional[Dict[str, str]]:
    """Find user by ID, return None if not found / Znajdź użytkownika po ID, zwróć None jeśli nie znaleziono"""
    users = {
        1: {"name": "Alice", "email": "alice@example.com"},
        2: {"name": "Bob", "email": "bob@example.com"}
    }
    return users.get(user_id)

user = find_user(1)
if user:
    print(f"\nFound user: {user['name']}")
else:
    print("\nUser not found")

# Union type — can be multiple types / Typ Union — może być wieloma typami
def process_id(id_value: Union[int, str]) -> str:
    """Process ID as int or string / Przetwórz ID jako int lub string"""
    return f"ID: {id_value}"

print(f"\n{process_id(123)}")
print(f"{process_id('ABC')}")

# Multiple return types / Wiele typów zwracanych
def get_coordinates() -> Tuple[float, float]:
    """Return x and y coordinates / Zwróć współrzędne x i y"""
    return 10.5, 20.3

x, y = get_coordinates()
print(f"\nCoordinates: x={x}, y={y}")

# Type hints for variables / Type hints dla zmiennych
name: str = "Alice"
age: int = 25
scores: List[int] = [85, 90, 78]
person: Dict[str, Union[str, int]] = {"name": "Bob", "age": 30}

print(f"\n{name} ({age} years old)")
print(f"Scores: {scores}")
print(f"Person: {person}")

Run the program:

Uruchom program:

uv run python type_hints.py

Expected output / Oczekiwany rezultat:

Hello, Alice!
5 + 3 = 8
Average: 15.333333333333334

Found user: Alice

ID: 123
ID: ABC

Coordinates: x=10.5, y=20.3

Alice (25 years old)
Scores: [85, 90, 78]
Person: {'name': 'Bob', 'age': 30}

💾 Save to Git / Zapisz w Git:

git add type_hints.py
git commit -m "Add type_hints.py - type hints with typing module"

📦 Dataclass

🧩 Zadanie / Task:

Create a file dataclass_demo.py:

Utwórz plik dataclass_demo.py:

from dataclasses import dataclass, field
from typing import List, Any

# Without dataclass — lots of boilerplate / Bez dataclass — dużo kodu kotłowego
class PersonOld:
    def __init__(self, name: str, age: int, email: str) -> None:
        self.name = name
        self.age = age
        self.email = email

    def __repr__(self) -> str:
        return f"PersonOld(name='{self.name}', age={self.age}, email='{self.email}')"

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, PersonOld):
            return False
        return (self.name == other.name and
                self.age == other.age and
                self.email == other.email)

# With dataclass — clean and concise / Z dataclass — czysto i zwięźle
@dataclass
class Person:
    name: str
    age: int
    email: str

# Create instances / Utwórz instancje
person1 = Person("Alice", 25, "alice@example.com")
person2 = Person("Alice", 25, "alice@example.com")
person3 = Person("Bob", 30, "bob@example.com")

print(person1)  # __repr__ generated automatically / __repr__ wygenerowany automatycznie
print(f"person1 == person2: {person1 == person2}")  # __eq__ generated / __eq__ wygenerowany
print(f"person1 == person3: {person1 == person3}")

# Dataclass with default values / Dataclass z wartościami domyślnymi
@dataclass
class Product:
    name: str
    price: float
    quantity: int = 0  # default value / wartość domyślna
    in_stock: bool = True

product1 = Product("Laptop", 999.99, 5)
product2 = Product("Mouse", 29.99)  # uses default quantity and in_stock

print(f"\n{product1}")
print(f"{product2}")

# Dataclass with field() / Dataclass z field()
@dataclass
class ShoppingCart:
    customer: str
    items: List[str] = field(default_factory=list)  # mutable default / mutowalna wartość domyślna

    def add_item(self, item: str) -> None:
        self.items.append(item)

    def total_items(self) -> int:
        return len(self.items)

cart1 = ShoppingCart("Alice")
cart1.add_item("Book")
cart1.add_item("Pen")

cart2 = ShoppingCart("Bob")
cart2.add_item("Laptop")

print(f"\n{cart1.customer}'s cart: {cart1.items} ({cart1.total_items()} items)")
print(f"{cart2.customer}'s cart: {cart2.items} ({cart2.total_items()} items)")

# Frozen dataclass — immutable / Zamrożony dataclass — niezmienny
@dataclass(frozen=True)
class Point:
    x: float
    y: float

point = Point(10.5, 20.3)
print(f"\n{point}")
# point.x = 15  # This would raise an error! / To spowodowałoby błąd!

# Dataclass with post-init / Dataclass z post-init
@dataclass
class Rectangle:
    width: float
    height: float
    area: float = field(init=False)  # not in __init__ / nie w __init__

    def __post_init__(self) -> None:
        """Calculate area after initialization / Oblicz pole po inicjalizacji"""
        self.area = self.width * self.height

rect = Rectangle(5, 3)
print(f"\n{rect}")
print(f"Rectangle area: {rect.area}")

Run the program:

Uruchom program:

uv run python dataclass_demo.py

Expected output / Oczekiwany rezultat:

Person(name='Alice', age=25, email='alice@example.com')
person1 == person2: True
person1 == person3: False

Product(name='Laptop', price=999.99, quantity=5, in_stock=True)
Product(name='Mouse', price=29.99, quantity=0, in_stock=True)

Alice's cart: ['Book', 'Pen'] (2 items)
Bob's cart: ['Laptop'] (1 items)

Point(x=10.5, y=20.3)

Rectangle(width=5, height=3, area=15)
Rectangle area: 15

💾 Save to Git / Zapisz w Git:

git add dataclass_demo.py
git commit -m "Add dataclass_demo.py - dataclasses with field() and frozen"

🔧 Alternative: Pydantic / Alternatywa: Pydantic

Pydantic is a popular alternative to dataclass with automatic data validation.

Pydantic to popularna alternatywa dla dataclass z automatyczną walidacją danych.

🧠 When to use what / Kiedy czego użyć:

Feature / Cecha dataclass pydantic
Built-in / Wbudowane ✅ (Python 3.7+) ❌ Need to install / Trzeba zainstalować
Data validation / Walidacja ❌ Manual / Ręczna ✅ Automatic / Automatyczna
Type coercion / Konwersja typów ❌ ✅ (e.g., "123" → 123)
JSON parsing / Parsowanie JSON ❌ Manual / Ręczne ✅ Built-in / Wbudowane
Best for / Najlepsze do Simple data containers / Proste kontenery APIs, config, validation / API, konfiguracja, walidacja

📘 Documentation / Dokumentacja:

Pydantic Documentation

Read sections: "Models" and "Validators" / Przeczytaj sekcje: "Models" i "Validators"

🎥 Video / Wideo:

ArjanCodes – Pydantic Tutorial (0:00–15:00)

Covers Pydantic basics and validation / Omawia podstawy Pydantic i walidację

🧩 Zadanie / Task:

First, install Pydantic:

Najpierw zainstaluj Pydantic:

uv add pydantic

Create a file pydantic_demo.py:

Utwórz plik pydantic_demo.py:

from pydantic import BaseModel, Field, field_validator, ValidationError
from typing import List

# Basic Pydantic model / Podstawowy model Pydantic
class User(BaseModel):
    """User with automatic validation / Użytkownik z automatyczną walidacją"""
    name: str
    age: int
    email: str

# Pydantic automatically validates! / Pydantic automatycznie waliduje!
try:
    user1 = User(name="Alice", age=25, email="alice@example.com")
    print(f"✓ Valid user: {user1}")

    # This will fail validation / To nie przejdzie walidacji
    user2 = User(name="Bob", age="invalid", email="bob@example.com")
except ValidationError as e:
    print(f"\n✗ Validation error:\n{e}")

# Pydantic with field constraints / Pydantic z ograniczeniami pól
class Product(BaseModel):
    """Product with validation rules / Produkt z zasadami walidacji"""
    name: str = Field(min_length=1, max_length=100)
    price: float = Field(gt=0, description="Price must be positive")
    quantity: int = Field(ge=0, description="Quantity cannot be negative")
    tags: List[str] = Field(default_factory=list)

product = Product(name="Laptop", price=999.99, quantity=5)
print(f"\n✓ Valid product: {product}")

try:
    # This will fail: negative price / To nie przejdzie: ujemna cena
    bad_product = Product(name="Mouse", price=-10, quantity=1)
except ValidationError as e:
    print(f"\n✗ Validation error: {e.error_count()} error(s)")
    for error in e.errors():
        print(f"  - {error['loc'][0]}: {error['msg']}")

# Custom validators / Własne walidatory
class Employee(BaseModel):
    """Employee with custom validation / Pracownik z własną walidacją"""
    name: str
    employee_id: str
    salary: float

    @field_validator('employee_id')
    @classmethod
    def validate_employee_id(cls, v: str) -> str:
        """Employee ID must start with 'E' / ID pracownika musi zaczynać się od 'E'"""
        if not v.startswith('E'):
            raise ValueError("Employee ID must start with 'E'")
        if len(v) != 5:
            raise ValueError("Employee ID must be 5 characters")
        return v

emp = Employee(name="Alice", employee_id="E0001", salary=50000)
print(f"\n✓ Valid employee: {emp}")

try:
    bad_emp = Employee(name="Bob", employee_id="123", salary=60000)
except ValidationError as e:
    print(f"\n✗ Validation error: {e.errors()[0]['msg']}")

# JSON parsing - Pydantic advantage! / Parsowanie JSON - zaleta Pydantic!
import json

json_data = '{"name": "Charlie", "age": 30, "email": "charlie@example.com"}'
user_from_json = User.model_validate_json(json_data)
print(f"\n✓ Parsed from JSON: {user_from_json}")

# Convert back to JSON / Konwertuj z powrotem do JSON
print(f"✓ To JSON: {user_from_json.model_dump_json()}")

# Type coercion - Pydantic converts automatically! / Konwersja - Pydantic konwertuje automatycznie!
user_with_coercion = User(name="Dave", age="35", email="dave@example.com")  # age is string but gets converted
print(f"\n✓ Type coercion: age was string '35', now int: {user_with_coercion.age} (type: {type(user_with_coercion.age).__name__})")

Run the program:

Uruchom program:

uv run python pydantic_demo.py

Expected output / Oczekiwany rezultat:

✓ Valid user: name='Alice' age=25 email='alice@example.com'

✗ Validation error:
1 validation error for User
age
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='invalid', input_type=str]

✓ Valid product: name='Laptop' price=999.99 quantity=5 tags=[]

✗ Validation error: 1 error(s)
  - price: Input should be greater than 0

✓ Valid employee: name='Alice' employee_id='E0001' salary=50000.0

✗ Validation error: Employee ID must start with 'E'

✓ Parsed from JSON: name='Charlie' age=30 email='charlie@example.com'
✓ To JSON: {"name":"Charlie","age":30,"email":"charlie@example.com"}

✓ Type coercion: age was string '35', now int: 35 (type: int)

💾 Save to Git / Zapisz w Git:

git add pydantic_demo.py
git commit -m "Add pydantic_demo.py - alternative to dataclass with validation"

💡 Summary / Podsumowanie:

Use dataclass when / Użyj dataclass gdy:

  • You need simple data containers / Potrzebujesz prostych kontenerów danych
  • No external dependencies / Brak zewnętrznych zależności
  • Basic structure without validation / Podstawowa struktura bez walidacji

Use pydantic when / Użyj pydantic gdy:

  • You need automatic data validation / Potrzebujesz automatycznej walidacji
  • Working with APIs or JSON / Pracujesz z API lub JSON
  • Type coercion is helpful / Konwersja typów jest pomocna
  • Building FastAPI applications / Budujesz aplikacje FastAPI

💡 6️⃣ Zasady i dobre praktyki / Principles and Best Practices

Good code isn't just code that works — it's code that's easy to read, maintain, and change.

Dobry kod to nie tylko kod, który działa — to kod łatwy do czytania, utrzymania i zmiany.


🔄 DRY — Don't Repeat Yourself

Don't Repeat Yourself means avoid duplicating code — if you write the same thing twice, extract it into a function or class.

Don't Repeat Yourself (Nie powtarzaj się) oznacza unikanie duplikacji kodu — jeśli piszesz to samo dwa razy, wyodrębnij to do funkcji lub klasy.

❌ Bad example / Zły przykład:

# Repeated code / Powtarzalny kod
print("Processing file1.txt...")
with open("file1.txt") as f:
    data = f.read()
    print(f"Read {len(data)} characters")

print("Processing file2.txt...")
with open("file2.txt") as f:
    data = f.read()
    print(f"Read {len(data)} characters")

✅ Good example / Dobry przykład:

def process_file(filename: str) -> None:
    print(f"Processing {filename}...")
    with open(filename) as f:
        data = f.read()
        print(f"Read {len(data)} characters")

process_file("file1.txt")
process_file("file2.txt")

💋 KISS — Keep It Simple Stupid

Keep It Simple Stupid means write straightforward code — I've learned not to overcomplicate things just to look clever.

Keep It Simple Stupid (Zachowaj prostotę) oznacza pisanie prostego kodu — nauczyłem się nie komplikować rzeczy tylko po to, by wyglądać mądrze.

❌ Bad example / Zły przykład:

# Overcomplicated / Przekomplikowany
def is_even(n: int) -> bool:
    return True if n % 2 == 0 else False if n % 2 != 0 else None

✅ Good example / Dobry przykład:

def is_even(n: int) -> bool:
    return n % 2 == 0

🧘 Zen of Python

The Zen of Python is a collection of 19 guiding principles for writing Python code.

Zen Pythona to zbiór 19 zasad kierunkowych pisania kodu w Pythonie.

You can view it by running:

Możesz go zobaczyć uruchamiając:

python -c "import this"

Key principles / Kluczowe zasady:

  • Beautiful is better than ugly / Piękne jest lepsze niż brzydkie
  • Explicit is better than implicit / Jednoznaczne jest lepsze niż domniemane
  • Simple is better than complex / Proste jest lepsze niż złożone
  • Readability counts / Czytelność się liczy
  • There should be one obvious way to do it / Powinien być jeden oczywisty sposób

📘 Read more / Przeczytaj więcej:

PEP 20 — The Zen of Python

Read the full text and explanations / Przeczytaj pełny tekst i wyjaśnienia

🎥 Video / Wideo:

Real Python – The Zen of Python Explained (0:00–10:00)

Explains key principles with examples / Wyjaśnia kluczowe zasady z przykładami

🧩 Zadanie / Task:

Run the Zen of Python:

Uruchom Zen Pythona:

python -c "import this"

Expected output / Oczekiwany rezultat:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
...

📝 Naming conventions / Konwencje nazewnictwa

PEP 8 is Python's style guide — like grammar rules for code.

PEP 8 to przewodnik stylu Pythona — jak zasady gramatyki dla kodu.

Common conventions / Powszechne konwencje:

  • Variables and functions: snake_case (e.g., user_name, calculate_total)

    Zmienne i funkcje: snake_case (np. user_name, calculate_total)

  • Classes: PascalCase (e.g., UserProfile, ShoppingCart)

    Klasy: PascalCase (np. UserProfile, ShoppingCart)

  • Constants: UPPER_SNAKE_CASE (e.g., MAX_SIZE, API_KEY)

    Stałe: UPPER_SNAKE_CASE (np. MAX_SIZE, API_KEY)

  • Private attributes: _leading_underscore (e.g., _internal_method)

    Atrybuty prywatne: _wiodący_podkreślnik (np. _internal_method)

📘 Read more / Przeczytaj więcej:

PEP 8 — Style Guide for Python Code

Read sections: "Naming Conventions" / Przeczytaj sekcję: "Naming Conventions"


✅ Podsumowanie / Summary

Po tym bloku potrafisz / After this block you can:

  • używać zmiennych i różnych typów danych (int, float, str, list, dict, tuple, set) / use variables and different data types (int, float, str, list, dict, tuple, set)
  • kontrolować przepływ programu za pomocą if/elif/else, match-case i pętli for/while / control program flow with if/elif/else, match-case, and for/while loops
  • sprawdzać None używając is None / check for None using is None
  • tworzyć funkcje z type hints, parametrami i wartościami zwracanymi / create functions with type hints, parameters and return values
  • definiować klasy z metodami i używać dziedziczenia zgodnie z LSP / define classes with methods and use inheritance following LSP
  • rozumieć różnicę między "B is A" a "A can be replaced by B" / understand the difference between "B is A" and "A can be replaced by B"
  • dodawać type hints do funkcji i klas dla lepszej czytelności / add type hints to functions and classes for better readability
  • używać dataclass do uproszczenia klas przechowujących dane / use dataclass to simplify data-storing classes
  • używać Pydantic jako alternatywy z automatyczną walidacją / use Pydantic as an alternative with automatic validation
  • stosować zasady DRY, KISS i Zen of Python / apply DRY, KISS, and Zen of Python principles
  • pisać czytelny kod zgodny z konwencjami PEP 8 / write readable code following PEP 8 conventions
  • zarządzać kodem z Git i pushować na GitHub / manage code with Git and push to GitHub

📦 Bonus – Słowniczek / Glossary

Termin Znaczenie PL Meaning EN
Variable zmienna — pojemnik na dane labeled container for data
Type typ — rodzaj danych kind of data
Function funkcja — wielokrotnego użytku blok kodu reusable code block
Class klasa — szablon do tworzenia obiektów blueprint for creating objects
Object / Instance obiekt / instancja — konkretny przykład klasy specific example of a class
Method metoda — funkcja wewnątrz klasy function inside a class
Attribute atrybut — zmienna wewnątrz klasy variable inside a class
Inheritance dziedziczenie — klasa otrzymująca cechy od innej class receiving features from another
Type hint wskazówka typu — opcjonalna informacja o typie optional type information
Dataclass dataclass — klasa z automatycznymi metodami class with automatic methods
Loop pętla — powtarzanie kodu code repetition
Condition warunek — decyzja if/else if/else decision
DRY nie powtarzaj się Don't Repeat Yourself
KISS zachowaj prostotę Keep It Simple

🎯 Mini-projekt 2 / Mini-project 2

Practice what you learned by building a simple task manager that combines functions, classes, type hints, and collections.

Przećwicz to, czego się nauczyłeś, budując prosty menedżer zadań, który łączy funkcje, klasy, type hints i kolekcje.

🧩 Zadanie / Task:

Create a file task_manager.py:

Utwórz plik task_manager.py:

from dataclasses import dataclass, field
from typing import List
from datetime import datetime

@dataclass
class Task:
    title: str
    completed: bool = False
    created_at: datetime = field(default_factory=datetime.now)

    def mark_complete(self) -> None:
        self.completed = True

    def __str__(self) -> str:
        status = "✓" if self.completed else "○"
        return f"{status} {self.title}"

class TaskManager:

    def __init__(self):
        self.tasks: List[Task] = []

    def add_task(self, title: str) -> None:
        task = Task(title)
        self.tasks.append(task)
        print(f"Added: {title}")

    def complete_task(self, index: int) -> None:
        if 0 <= index < len(self.tasks):
            self.tasks[index].mark_complete()
            print(f"Completed: {self.tasks[index].title}")
        else:
            print("Invalid task number") # a better way raise ValueError("Invalid task number") (will cover in a future lesson)

    def show_tasks(self) -> None:
        if not self.tasks:
            print("No tasks yet!")
            return

        print("\nYour tasks:")
        for i, task in enumerate(self.tasks, start=1):
            print(f"{i}. {task}")

    def get_stats(self) -> None:
        total = len(self.tasks)
        completed = sum(1 for task in self.tasks if task.completed)
        pending = total - completed

        print(f"\nStats: {completed}/{total} completed, {pending} pending")

# Demo / Demonstracja
def main() -> None:
    manager = TaskManager()

    # Add tasks / Dodaj zadania
    manager.add_task("Learn Python basics")
    manager.add_task("Write first program")
    manager.add_task("Practice with exercises")

    # Show tasks / Pokaż zadania
    manager.show_tasks()

    # Complete a task / Ukończ zadanie
    manager.complete_task(0)
    manager.complete_task(2)

    # Show updated tasks / Pokaż zaktualizowane zadania
    manager.show_tasks()

    # Show statistics / Pokaż statystyki
    manager.get_stats()

if __name__ == "__main__":
    main()

Run the program:

Uruchom program:

uv run python task_manager.py

Expected output / Oczekiwany rezultat:

Added: Learn Python basics
Added: Write first program
Added: Practice with exercises

Your tasks:
1. ○ Learn Python basics
2. ○ Write first program
3. ○ Practice with exercises
Completed: Learn Python basics
Completed: Practice with exercises

Your tasks:
1. ✓ Learn Python basics
2. ○ Write first program
3. ✓ Practice with exercises

Stats: 2/3 completed, 1 pending

💾 Save to Git / Zapisz w Git:

git add task_manager.py
git commit -m "Add task_manager.py - mini project combining all concepts"

🎯 Challenge / Wyzwanie:

Try extending the task manager with these features:

Spróbuj rozszerzyć menedżer zadań o te funkcje:

  1. Add task priority (low, medium, high) / Dodaj priorytet zadania (niski, średni, wysoki)
  2. Add task due dates / Dodaj terminy zadań
  3. Save tasks to a file / Zapisuj zadania do pliku
  4. Load tasks from a file / Wczytuj zadania z pliku

📤 Push to GitHub / Wypchnij na GitHub

After completing all exercises, push your work to GitHub to save it online and practice your Git skills!

Po ukończeniu wszystkich ćwiczeń, wypchnij pracę na GitHub aby zapisać ją online i przećwiczyć umiejętności Git!

🧩 Zadanie / Task:

Step 1: Review your commits / Krok 1: Przejrzyj commity

# See all your commits / Zobacz wszystkie commity
git log --oneline

# Check repository status / Sprawdź status repozytorium
git status

Step 2: Create GitHub repository (if not exists) / Krok 2: Utwórz repozytorium GitHub (jeśli nie istnieje)

If you don't have a remote repository yet:

Jeśli nie masz jeszcze zdalnego repozytorium:

# Using gh CLI (recommended) / Używając gh CLI (zalecane)
gh repo create python-learning --public --source=. --push

# Or manually create repo on GitHub.com and add remote / Lub ręcznie utwórz repo na GitHub.com i dodaj remote
git remote add origin https://github.com/your-username/python-learning.git

Step 3: Push all commits / Krok 3: Wypchnij wszystkie commity

# Push to main branch / Wypchnij na główną gałąź
git push -u origin main

# Or if you already set upstream / Lub jeśli już ustawiłeś upstream
git push

Step 4: Verify on GitHub / Krok 4: Zweryfikuj na GitHubie

  1. Open your repository in browser:

    Otwórz repozytorium w przeglądarce:

    gh repo view --web
  2. Check that all files are present:

    Sprawdź czy wszystkie pliki są obecne:

    • 01_basics/basics.py
    • 01_basics/collections.py
    • 01_basics/control_flow.py
    • 01_basics/loops.py
    • 01_basics/functions.py
    • 01_basics/game.py
    • 01_basics/classes.py
    • 01_basics/type_hints.py
    • 01_basics/dataclass_demo.py
    • 01_basics/task_manager.py
    • 01_basics/README.md
  3. Review commit history to see all your progress!

    Przejrzyj historię commitów aby zobaczyć cały postęp!

Expected result / Oczekiwany rezultat:

Your Phase 1 exercises are now safely stored on GitHub and accessible from anywhere!

Twoje ćwiczenia z Fazy 1 są teraz bezpiecznie zapisane na GitHubie i dostępne z każdego miejsca!