Skip to content

Latest commit

 

History

History
340 lines (225 loc) · 8 KB

2.3.md

File metadata and controls

340 lines (225 loc) · 8 KB

Obsługa wyjątków / Exception Handling


Why it matters / Dlaczego to ważne

Exception handling lets you catch errors and handle them instead of crashing.

Obsługa wyjątków pozwala wychwytywać błędy i je obsługiwać zamiast crashować.

Real programs encounter unexpected situations — files don't exist, network fails, user enters invalid data.

Prawdziwe programy napotykają nieoczekiwane sytuacje — pliki nie istnieją, sieć zawodzi, użytkownik wpisuje nieprawidłowe dane.

Exceptions let you implement fallbacks, retries, alternative paths — not just "stop the program".

Wyjątki pozwalają implementować fallbacki, retry, alternatywne ścieżki — nie tylko "zatrzymaj program".

Documentation:

Python Errors and Exceptions

Video:

Corey Schafer — Try/Except (0:00–25:00)


Basic try/except / Podstawy try/except

Create exceptions_demo.py:

Utwórz exceptions_demo.py:

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Without exception handling / Bez obsługi wyjątków
# result = 10 / 0  # This crashes! / To crashuje!

# With exception handling / Z obsługą wyjątków
try:
    result = 10 / 0
except ZeroDivisionError:
    logger.error("Cannot divide by zero")

logger.info("Program continues...")

# Multiple exception types / Wiele typów wyjątków
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        logger.warning("Division by zero")
        return None
    except TypeError:
        logger.warning("Invalid types - need numbers")
        return None

print(safe_divide(10, 2))
print(safe_divide(10, 0))
print(safe_divide(10, "x"))

Run:

Uruchom:

uv run python exceptions_demo.py

Common exceptions / Powszechne wyjątki

import logging

logger = logging.getLogger(__name__)

# ValueError
try:
    number = int("not_a_number")
except ValueError as e:
    logger.error(f"Invalid number: {e}")

# FileNotFoundError
try:
    with open("missing.txt") as file:
        content = file.read()
except FileNotFoundError:
    logger.warning("File not found: missing.txt")

# KeyError
try:
    person = {"name": "Alice"}
    age = person["age"]
except KeyError as e:
    logger.error(f"Key doesn't exist: {e}")

Try/Except/Finally

import logging

logger = logging.getLogger(__name__)

def read_file(filename: str) -> str | None:
    try:
        with open(filename, "r") as file:
            content = file.read()
    except FileNotFoundError:
        logger.warning(f"File not found: {filename}")
        return None
    finally:
        logger.debug(f"Finished processing: {filename}")

    # Success code outside try block / Kod sukcesu poza blokiem try
    logger.info(f"Successfully read: {filename}")
    return content

content = read_file("sample.txt")

How it works / Jak to działa:

  • try: — code that might fail / kod który może zawieść
  • except: — handles specific errors / obsługuje konkretne błędy
  • finally: — always runs, good for cleanup / zawsze się wykonuje, dobre do sprzątania

Note: else clause exists but is rarely used — cleaner to put success code after try/except block.

Uwaga: Klauzula else istnieje ale jest rzadko używana — czytelniej umieścić kod sukcesu po bloku try/except.


Raising exceptions / Podnoszenie wyjątków

Sometimes you want to signal an error in your code.

Czasami chcesz zasygnalizować błąd we własnym kodzie.

import logging

logger = logging.getLogger(__name__)

def validate_age(age: int) -> None:
    if age < 0:
        raise ValueError("Age cannot be negative")
    if age > 150:
        raise ValueError("Age unrealistic")
    logger.info(f"Valid age: {age}")

try:
    validate_age(25)
    validate_age(-5)
except ValueError as e:
    logger.error(f"Validation error: {e}")

Custom exceptions / Własne wyjątki

Create your own exception classes for specific errors.

Twórz własne klasy wyjątków dla konkretnych błędów.

import logging

logger = logging.getLogger(__name__)

class InsufficientFundsError(Exception):
    pass

class BankAccount:
    def __init__(self, balance: float):
        self.balance = balance

    def withdraw(self, amount: float) -> None:
        if amount > self.balance:
            raise InsufficientFundsError(
                f"Cannot withdraw ${amount:.2f}. Balance: ${self.balance:.2f}"
            )
        self.balance -= amount
        logger.info(f"Withdrew ${amount:.2f}. New balance: ${self.balance:.2f}")

account = BankAccount(100.0)

try:
    account.withdraw(50)
    account.withdraw(100)
except InsufficientFundsError as e:
    logger.error(f"Transaction failed: {e}")

Real-world example: safe file operations / Przykład z życia: bezpieczne operacje na plikach

from pathlib import Path
import json
import logging

logger = logging.getLogger(__name__)

class SafeFileHandler:
    def read_json(self, filename: str) -> dict | None:
        try:
            path = Path(filename)
            if not path.exists():
                logger.warning(f"File not found: {filename}")
                return None

            content = path.read_text()
            return json.loads(content)

        except json.JSONDecodeError as e:
            logger.error(f"Invalid JSON in {filename}: {e}")
            raise e
        except PermissionError:
            logger.error(f"No permission to read {filename}")
            return None

    def write_json(self, filename: str, data: dict) -> bool:
        try:
            Path(filename).write_text(json.dumps(data, indent=2))
            return True
        except Exception:
            logger.exception(f"Failed to write {filename}")
            return False

# Usage / Użycie
logging.basicConfig(level=logging.INFO)
handler = SafeFileHandler()

data = handler.read_json("person.json")
if data:
    print(f"Loaded: {data.get('name')}")

Why re-raise? / Dlaczego podnosić ponownie?

raise e after logging lets caller decide what to do — you log the error but don't hide it.

raise e po zalogowaniu pozwala wywołującemu zdecydować co zrobić — logujesz błąd ale go nie ukrywasz.

Stack trace in logs / Stack trace w logach:

try:
    result = risky_operation()
except Exception:
    logger.exception("Operation failed")
    raise

Two ways to log with stack trace / Dwa sposoby logowania ze stack trace:

  1. logger.exception() - automatically includes stack trace, use in except blocks

    logger.exception() - automatycznie dodaje stack trace, używaj w blokach except

  2. logger.error(..., exc_info=True) - manually add stack trace to any log level

    logger.error(..., exc_info=True) - ręcznie dodaj stack trace do dowolnego poziomu logowania

Note: Use raise without argument to re-raise original exception with full stack trace.

Uwaga: Użyj raise bez argumentu aby podnieść ponownie oryginalny wyjątek z pełnym stack trace.


Best practices / Dobre praktyki

Do:

  • Only catch exceptions you can handle

    Łap tylko wyjątki które możesz obsłużyć

  • Add meaningful error messages

    Dodawaj sensowne komunikaty błędów

Don't:

  • Use bare except: — it hides bugs

    Używaj gołego except: — ukrywa błędy

  • Silence errors without logging

    Wyciszaj błędów bez logowania

  • Catch exceptions you can't handle

    Nie Łap wyjątków których nie możesz obsłużyć


Podsumowanie / Summary

After this section you can:

Po tej sekcji potrafisz:

  • handle exceptions with try/except

    obsługiwać wyjątki z try/except

  • catch specific exception types

    łapać konkretne typy wyjątków

  • raise exceptions with meaningful messages

    podnosić wyjątki z sensownymi komunikatami

  • create custom exception classes

    tworzyć własne klasy wyjątków

  • build robust error handling into programs

    budować solidną obsługę błędów w programach


Next: 2.4 — Moduły i importy