Przejdź do treści

Wszystkie kursy / Programowanie

Python dla inżynierów: głęboka semantyka 3.12‑3.14

Zaawansowana, aktualna semantyka Pythona 3.12‑3.14: GIL i współbieżność, generatory, dekoratory, dopasowanie wzorców, modelowanie danych, typowanie statyczne i wydajność dla praktykujących inżynierów.

Sekcji08
Pytań24
Fiszek21
Quiz losuje20 z 24

Tematy egzaminacyjne w tym kursie

  1. 01 Mutowalne argumenty domyślne: dlaczego ewaluują się raz i jak tego uniknąć wzorcem None-sentinel.
  2. 02 GIL i free-threaded build (PEP 703/779): kiedy threading, multiprocessing, a kiedy asyncio.
  3. 03 Generatory vs listy: leniwa ewaluacja i pamięć O(1) kontra O(n).
  4. 04 Dekoratory sparametryzowane i functools.wraps dla poprawnej introspekcji.
  5. 05 match/case: capture pattern, wzorce klas/sekwencji/mapowań, guard i OR-pattern.
  6. 06 Modelowanie danych (dataclass/NamedTuple/Pydantic) i exception groups (except*).

Domyślne argumenty i wyrażenia przypisania

★ Egzamin

Argument domyślny w Pythonie jest ewaluowany dokładnie raz, w momencie wykonania instrukcji def, a nie przy każdym wywołaniu funkcji. Rezultat tej jednorazowej ewaluacji trafia do atrybutu __defaults__ obiektu funkcji i jest reużywany przy każdym wywołaniu bez podanego argumentu. Dla typów mutowalnych (list, dict, set) to prosta droga do buga produkcyjnego: różne wywołania dzielą ten sam obiekt.

Pułapka
Klasyczna pułapka: def add_item(item, bucket=[]): bucket.append(item); return bucket. Każde wywołanie bez drugiego argumentu mutuje tę samą listę utworzoną raz przy definicji funkcji, kolejne wywołania widzą śmieci z poprzednich.
Bug i poprawka: mutowalny argument domyślny python
def add_item(item, bucket=[]):
    bucket.append(item)
    return bucket

add_item("a")  # ['a']
add_item("b")  # ['a', 'b'] - ta sama lista, nie nowa!

def add_item_fixed(item, bucket=None):
    if bucket is None:
        bucket = []
    bucket.append(item)
    return bucket

add_item_fixed("a")  # ['a']
add_item_fixed("b")  # ['b'] - nowa lista przy każdym wywołaniu
Dlaczego tak działa
Definicja funkcji to zwykła instrukcja wykonywana raz przy imporcie modułu. Wyrażenie w sygnaturze (np. []) jest ewaluowane wtedy i zapisane jako pojedynczy obiekt w function.__defaults__, współdzielony między wszystkimi wywołaniami.
Termin kluczowy
Walrus operator (:=) - Wyrażenie przypisania wprowadzone w PEP 572 (Python 3.8): pozwala przypisać wartość do zmiennej i użyć jej w tym samym wyrażeniu, np. wewnątrz warunku while, if czy listy składanej, bez osobnej linii przypisania.
Walrus operator w pętli i comprehension python
# bez walrusa: dwa odczyty tej samej wartości
chunk = file.read(8192)
while chunk:
    process(chunk)
    chunk = file.read(8192)

# z walrusem: jeden odczyt, czytelniejszy warunek
while (chunk := file.read(8192)):
    process(chunk)

# w liscie skladanej: policz raz, uzyj dwa razy
results = [y for x in data if (y := expensive(x)) is not None]
Pułapka
Zmienna przypisana operatorem := wewnątrz listy składanej wiąże się w zasięgu otaczającej funkcji (lub modułu), nie w prywatnym zasięgu comprehension jak zwykła zmienna pętli for. To celowe zachowanie z PEP 572, ale zaskakuje: po wykonaniu powyższego przykładu zmienna y istnieje poza listą składaną.
  • Używaj walrusa gdy unikasz podwójnej ewaluacji tego samego wyrażenia (np. odczyt, regex match)
  • Nie nadużywaj go do upychania wielu przypisań w jednej linii kosztem czytelności
  • W warunkach while i if wewnątrz comprehension jest to idiomatyczne, w zwykłym kodzie liniowym zwykłe przypisanie bywa czytelniejsze
Mnemonik
Domyślny argument mutowalny to wspólna szafka firmowa: kto pierwszy coś w niej zostawi, każdy kolejny to zastanie. None-sentinel to własna szafka na każde wejście.

GIL, wątki, procesy i asyncio

★ Egzamin

CPython (referencyjna implementacja) chroni swoje wewnętrzne struktury danych jednym globalnym zamkiem: GIL (Global Interpreter Lock). W danym momencie tylko jeden wątek może wykonywać bytecode Pythona, niezależnie od liczby rdzeni CPU. To nie jest szczegół implementacyjny bez znaczenia, to fundamentalnie kształtuje wybór modelu współbieżności.

Termin kluczowy
GIL (Global Interpreter Lock) - Blokada wewnątrz interpretera CPython serializująca wykonanie bytecode Pythona do jednego wątku naraz. Chroni liczniki referencji i struktury interpretera przed race condition bez potrzeby drobnoziarnistego blokowania w każdym obiekcie.
PEP 703 / PEP 779, stan na 2026
Free-threaded build (interpreter bez GIL, opcjonalny) przeszedł fazę eksperymentalną w Python 3.13 (opt-in, binarka python3.13t) do statusu oficjalnie wspieranego w Python 3.14 zgodnie z PEP 779. GIL wciąż jest domyślnie włączony, standardowa binarka python3.14 dalej go używa; przejście na domyślny brak GIL to faza 3 planu PEP 703, bez ustalonej daty.
Pułapka
Powszechne mylne przekonanie: 'Python 3.13/3.14 usunęły GIL'. Nieprawda, usunięcie GIL wymaga świadomej instalacji free-threaded builda (np. python3.13t) i sprawdzenia, czy używane rozszerzenia C są z nim kompatybilne. Domyślna instalacja Pythona nadal ma GIL włączony.
Sprawdzenie stanu GIL w free-threaded buildzie (3.13+) python
import sys

# w standardowym buildzie atrybut może nie istnieć / zawsze True
if hasattr(sys, "_is_gil_enabled"):
    print(sys._is_gil_enabled())  # False w binarce python3.13t / 3.14t bez rozszerzeń wymuszających GIL
threading: dobre dla I/O-bound (GIL zwalniany na czas blokujacego wywolania) python
from concurrent.futures import ThreadPoolExecutor
import httpx

def fetch_size(url: str) -> int:
    return len(httpx.get(url).content)

urls = ["https://example.com"] * 20
with ThreadPoolExecutor(max_workers=20) as pool:
    sizes = list(pool.map(fetch_size, urls))
multiprocessing: realnie równoległe CPU dla zadań CPU-bound python
from concurrent.futures import ProcessPoolExecutor

def is_prime(n: int) -> bool:
    return n > 1 and all(n % i for i in range(2, int(n ** 0.5) + 1))

# guard obowiązkowy: worker (spawn, domyślny na macOS/Windows) re-importuje ten moduł
if __name__ == "__main__":
    with ProcessPoolExecutor() as pool:
        results = list(pool.map(is_prime, range(10_000_000, 10_000_200)))
asyncio: tysiace wspolbieznych operacji I/O w jednym watku python
import asyncio
import httpx

async def fetch_size(client: httpx.AsyncClient, url: str) -> int:
    response = await client.get(url)
    return len(response.content)

async def main(urls: list[str]) -> list[int]:
    async with httpx.AsyncClient() as client:
        async with asyncio.TaskGroup() as tg:
            tasks = [tg.create_task(fetch_size(client, u)) for u in urls]
    return [t.result() for t in tasks]

asyncio.run(main(["https://example.com"] * 1000))
ModelNajlepszy dlaJednostka izolacjiTypowy narzutKluczowe ograniczenie
threadingI/O-bound (sieć, dysk, DB)wątek OS, współdzielona pamięćniskiGIL serializuje bytecode - brak realnego przyspieszenia CPU-bound
multiprocessingCPU-bound (obliczenia, ML preprocessing)proces OS, osobna pamięćwysoki (start procesu, pickling, IPC)brak współdzielonego stanu, komunikacja przez kolejki/pickle
asynciomasowa współbieżność I/O (tysiące połączeń)coroutine w jednej pętli zdarzeńbardzo niskiwymaga bibliotek async-native, blokujące wywołanie zawiesza całą pętlę
Pułapka
Wywołanie blokującej funkcji synchronicznej (np. time.sleep, requests.get) wewnątrz coroutine asyncio zamraża całą pętlę zdarzeń, nie tylko bieżące zadanie. Każda operacja I/O w kodzie async musi mieć wariant async-native (httpx.AsyncClient, asyncpg) albo być odpalona w wątku przez asyncio.to_thread.
Mnemonik
threading = wspólne biurko, wielu ludzi, jeden długopis (GIL) do pisania kodu Pythona na raz. multiprocessing = osobne biurka, brak wspólnego długopisu, ale trzeba nosić dokumenty (pickling) między nimi. asyncio = jedna osoba, wiele zadań, przełącza się tylko gdy czeka na coś zewnętrznego.

Generatory i iteratory: pamięć kontra listy

★ Egzamin

Lista buduje i przechowuje wszystkie elementy naraz w pamięci, zużycie rośnie liniowo z rozmiarem danych (O(n)). Generator produkuje elementy leniwie, jeden na żądanie, przez protokół iteratora, zużycie pamięci pozostaje stałe (O(1)) niezależnie od rozmiaru źródła danych.

Termin kluczowy
Protokół iteratora - Obiekt implementujący __iter__ (zwraca siebie) i __next__ (zwraca kolejny element lub rzuca StopIteration) jest iteratorem. Funkcja z instrukcją yield automatycznie zwraca obiekt generatora, który implementuje ten protokół bez pisania klasy ręcznie.
Generator: przetwarzanie pliku bez wczytywania go w całości python
def read_large_log(path: str):
    with open(path) as f:
        for line in f:
            yield line.rstrip("\n")

errors = sum(1 for line in read_large_log("app.log") if "ERROR" in line)
Pamięć: generator expression vs list comprehension
sum(x * x for x in range(10**8)) trzyma w pamięci jeden element na raz (kilkadziesiąt bajtów). sum([x * x for x in range(10**8)]) najpierw materializuje pełną listę ~10**8 elementów (setki megabajtów) zanim sum w ogóle zacznie sumować.
Ten sam wynik, radykalnie inny profil pamięci python
# ~0.8 GB tymczasowej listy zanim sum zacznie liczyc
total = sum([x * x for x in range(10**8)])

# stala, minimalna pamiec - jeden element generatora na raz
total = sum(x * x for x in range(10**8))
Pułapka
Generator jest jednorazowy: po jednym pełnym przejściu jest wyczerpany i kolejna iteracja zwraca od razu StopIteration (pustą sekwencję), nie resetuje się. Generator też nie ma len() ani dostępu po indeksie, to nie jest sekwencja, tylko strumień.
  • itertools.islice(gen, n) - leniwe pobranie n elementów bez materializacji reszty
  • itertools.chain(a, b) - leniwe połączenie kilku iterowalnych w jeden strumień
  • itertools.groupby(data, key) - grupowanie kolejnych elementow (wymaga posortowanych danych wg klucza)
  • itertools.pairwise(data) - kolejne pary sasiadujacych elementow (od Python 3.10)
itertools.pairwise - kolejne pary sasiednich elementow python
from itertools import pairwise

list(pairwise([1, 2, 3, 4]))
# [(1, 2), (2, 3), (3, 4)]
Mnemonik
Lista to pełny magazyn zamówiony z góry, generator to dostawa na żądanie, jedna paczka na raz, nigdy nie zapełnia całego magazynu.

Dekoratory i functools

★ Egzamin

Dekorator to funkcja wyższego rzędu przyjmująca funkcję i zwracająca (zwykle) inną funkcję ją opakowującą. Składnia @decorator nad definicją funkcji to czysty cukier składniowy dla func = decorator(func), wykonywany raz, w momencie definicji.

Dekorator bez functools.wraps psuje introspekcje python
def timed(func):
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@timed
def compute(n: int) -> int:
    """Sum of squares up to n."""
    return sum(i * i for i in range(n))

compute.__name__  # 'wrapper' - zle, powinno byc 'compute'
compute.__doc__   # None - docstring zgubiony
Pułapka
Bez functools.wraps opakowana funkcja traci __name__, __doc__, __module__ i __wrapped__. Łamie to introspekcję (help(), inspect.signature), generowanie dokumentacji, a czasem serializację i debugging, stack trace pokazuje 'wrapper', nie oryginalną nazwę.
Poprawka: functools.wraps zachowuje metadane python
import functools
import time

def timed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@timed
def compute(n: int) -> int:
    """Sum of squares up to n."""
    return sum(i * i for i in range(n))

compute.__name__  # 'compute'
compute.__doc__   # 'Sum of squares up to n.'
Termin kluczowy
Dekorator sparametryzowany - Fabryka dekoratorów: funkcja przyjmująca własne argumenty (np. liczbę prób, timeout) i zwracająca właściwy dekorator, który dopiero opakowuje docelową funkcję. Wymaga jednego dodatkowego poziomu zagnieżdżenia funkcji względem zwykłego dekoratora.
Parametrized decorator: retry z konfigurowalna liczba prob python
import functools
import time

def retry(times: int = 3, delay: float = 0.5):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_exc = None
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except ConnectionError as exc:
                    last_exc = exc
                    time.sleep(delay)
            raise last_exc
        return wrapper
    return decorator

@retry(times=5, delay=1.0)
def call_flaky_api() -> dict:
    ...
Kanoniczny dekorator stdlib
functools.lru_cache(maxsize=...) (lub functools.cache bez limitu, od 3.9) memoizuje wynik funkcji na podstawie hashowalnych argumentów. To najczęściej używany dekorator produkcyjny do prostego cache'owania w pamięci procesu.
  • @staticmethod / @classmethod - metody nie/związane z instancją
  • @functools.cached_property - property liczone raz i zapamiętane na instancji
  • @functools.singledispatch - dekorator generyczny dispatchujący po typie pierwszego argumentu
  • @functools.total_ordering - dogenerowuje pozostale operatory porownania z jednego (__eq__ + __lt__)

Menedżery kontekstu i contextlib

Instrukcja with gwarantuje wywołanie sprzątania niezależnie od tego, czy blok zakończył się normalnie, czy wyjątkiem. Kontrakt jest prosty: __enter__ zwraca wartość dla as, __exit__ dostaje informację o wyjątku (albo None) i decyduje, czy go wygasić.

Termin kluczowy
Protokół menedżera kontekstu - Klasa implementująca __enter__(self) i __exit__(self, exc_type, exc_value, traceback). __exit__ zwraca True aby wygasić wyjątek, False (lub None) aby pozwolić mu propagować dalej.
Klasowy menedzer kontekstu: transakcja bazy danych python
class Transaction:
    def __init__(self, conn):
        self.conn = conn

    def __enter__(self):
        self.conn.execute("BEGIN")
        return self.conn

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            self.conn.execute("COMMIT")
        else:
            self.conn.execute("ROLLBACK")
        return False  # nie tłumimy wyjątku, propagujemy dalej

with Transaction(conn) as cur:
    cur.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
contextlib.contextmanager: to samo bez pisania klasy python
from contextlib import contextmanager

@contextmanager
def transaction(conn):
    conn.execute("BEGIN")
    try:
        yield conn
        conn.execute("COMMIT")
    except Exception:
        conn.execute("ROLLBACK")
        raise

with transaction(conn) as cur:
    cur.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
contextlib.suppress
contextlib.suppress(FileNotFoundError) to zwięzła alternatywa dla try/except Pass: wygasza wskazany wyjątek w bloku with bez dodatkowych linii try/except.
ExitStack: dynamiczna liczba menedzerow kontekstu python
from contextlib import ExitStack

def process_files(paths: list[str]):
    with ExitStack() as stack:
        files = [stack.enter_context(open(p)) for p in paths]
        # wszystkie pliki zamknięte automatycznie przy wyjściu, w tym przy wyjątku
        return [f.read() for f in files]
Pułapka
__exit__ zwracające True (lub dowolną wartość truthy) wygasza wyjątek całkowicie, nawet jeśli był niezamierzony, np. AssertionError z bloku testowego zniknie bez śladu. Zwracaj True świadomie, tylko dla konkretnego, oczekiwanego typu wyjątku.
  • contextlib.closing(obj) - wywoluje obj.close() na wyjsciu dla obiektow bez pelnego protokolu with
  • contextlib.suppress(*exc_types) - ignoruje wskazane wyjątki
  • contextlib.redirect_stdout/redirect_stderr - tymczasowe przekierowanie strumieni
  • contextlib.nullcontext() - placeholder gdy menedzer kontekstu jest opcjonalny

Dopasowanie wzorców: match/case

★ Egzamin

match/case (PEP 634-636, od Python 3.10) to strukturalne dopasowanie wzorców, nie prosty switch po wartości. Potrafi jednocześnie sprawdzić kształt danych (typ, długość, obecność kluczy) i wydobyć z nich wartości do zmiennych w jednym kroku.

Dopasowanie literalow i wildcard python
def http_status_label(code: int) -> str:
    match code:
        case 200 | 201 | 204:
            return "OK"
        case 400 | 404 | 422:
            return "Client error"
        case 500:
            return "Server error"
        case _:
            return "Unknown"
Wzorzec klasy: dopasowanie po typie i atrybutach python
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

def describe(shape) -> str:
    match shape:
        case Point(x=0, y=0):
            return "origin"
        case Point(x=0, y=y):
            return f"on y-axis at {y}"
        case Point(x=x, y=y) if x == y:
            return "on diagonal"
        case Point():
            return "generic point"
        case _:
            return "not a point"
Wzorzec sekwencji i mapowania python
def handle(event: dict | list) -> None:
    match event:
        case {"type": "click", "x": x, "y": y}:
            print(f"click at ({x}, {y})")
        case {"type": "key", "code": code}:
            print(f"key {code}")
        case [first, *rest] if rest:
            print(f"batch starting with {first}, {len(rest)} more")
        case []:
            print("empty batch")
Termin kluczowy
Guard clause (if w case) - Dodatkowy warunek po wzorcu, sprawdzany dopiero gdy wzorzec się dopasuje, pozwala zawęzić dopasowanie o logikę niewyrażalną samą strukturą, np. case Point(x=x, y=y) if x == y.
Termin kluczowy
OR-pattern (|) - Łączy kilka alternatywnych wzorców w jednym case, dopasowuje jeśli którykolwiek pasuje, np. case 400 | 404 | 500:.
Pułapka
Goła nazwa zmiennej w case (np. case x:) jest zawsze wzorcem przechwytującym (capture pattern), wiąże x z dopasowaną wartością i pasuje do wszystkiego, nigdy nie porównuje z istniejącą zmienną o tej nazwie. Aby porównać ze stałą (np. enumem), użyj nazwy kropkowanej: case Color.RED:.
  • Używaj match/case, gdy masz kilka strukturalnie różnych kształtów danych do rozróżnienia (np. różne typy zdarzeń)
  • Dla prostego porównania kilku wartości skalarnych zwykłe if/elif jest równie czytelne i nie wymaga uczenia się składni wzorców
  • Klasa musi definiowac __match_args__ (dataclass generuje to automatycznie) aby wspierac pozycyjne wzorce klas jak Point(0, 0)

Modelowanie danych i typowanie statyczne

★ Egzamin

Wybór między @dataclass, typing.NamedTuple a Pydantic BaseModel to wybór między trzema różnymi kompromisami: zero zależności i brak walidacji (dataclass), niemutowalność i lekkość krotki (NamedTuple), albo walidacja w runtime kosztem zależności zewnętrznej (Pydantic).

dataclass: mutowalny domyślnie, field(default_factory) dla mutowalnych domyślnych python
from dataclasses import dataclass, field

@dataclass(slots=True, frozen=True)
class Config:
    name: str
    tags: list[str] = field(default_factory=list)
    retries: int = 3
NamedTuple: niemutowalny, lekki, oparty na krotce python
from typing import NamedTuple

class Point(NamedTuple):
    x: int
    y: int

p = Point(1, 2)
p.x  # 1
p[0]  # 1 - działa też jak zwykła krotka
Pydantic: walidacja typów w runtime python
from pydantic import BaseModel, Field

class UserCreate(BaseModel):
    email: str
    age: int = Field(ge=0, le=150)

UserCreate(email="a@b.com", age=200)
# ValidationError: age - Input should be less than or equal to 150

dataclass / NamedTuple

  • Część biblioteki standardowej, zero zewnętrznych zależności
  • Zero narzutu walidacji w runtime - złe dane wchodzą bez ostrzeżenia
  • Optymalne, gdy dane pochodzą z zaufanego źródła (wewnętrzna logika)

Pydantic BaseModel

  • Wymaga zależności (pydantic), rdzeń walidacji napisany w Rust (pydantic-core) dla wydajności
  • Waliduje i konwertuje typy w runtime, rzuca ValidationError przy złych danych
  • Optymalne na granicy systemu: API request/response, parsowanie configu, dane z zewnątrz
Termin kluczowy
typing.Protocol - Definiuje typowanie strukturalne (duck typing sprawdzany statycznie): klasa spełnia Protocol jeśli ma pasujące metody/atrybuty, bez jawnego dziedziczenia po nim. Do isinstance() w runtime wymaga dekoratora @runtime_checkable.
Protocol: strukturalne typowanie bez dziedziczenia python
from typing import Protocol, runtime_checkable

@runtime_checkable
class Renderable(Protocol):
    def render(self) -> str: ...

class Button:
    def render(self) -> str:
        return "<button/>"

def show(item: Renderable) -> None:
    print(item.render())

show(Button())  # OK - Button pasuje strukturalnie, nie dziedziczy po Renderable
Termin kluczowy
TypedDict - Opisuje statycznie oczekiwany kształt słownika (nazwy kluczy i typy wartości) dla narzędzi jak mypy/pyright. W przeciwieństwie do Pydantic nie waliduje niczego w runtime, to zwykły dict z adnotacją.
TypedDict: typowany kształt słownika bez walidacji w runtime python
from typing import TypedDict

class UserPayload(TypedDict):
    id: int
    email: str
    is_admin: bool

def handle(payload: UserPayload) -> None:
    print(payload["email"])  # pyright/mypy znają typ, ale runtime nie sprawdza niczego
Generyki bez importu TypeVar (PEP 695, od 3.12)
class Stack[T]: ... i def first[T](items: list[T]) -> T: ... to nowa, krótsza składnia generyków wbudowana w język. Starsza forma (T = TypeVar('T'); class Stack(Generic[T])) dalej działa i jest potrzebna dla kodu wspierającego wersje przed 3.12.
NarzędzieTwórca / statusDomyślna surowośćTypowe użycie
mypyReferencyjny checker PEP 484, communityumiarkowana, konfigurowalna stopniowoduże bazy kodu z bogatym ekosystemem wtyczek (Django, SQLAlchemy)
pyrightMicrosoft, używany w VS Code (Pylance)wyższa domyślnie, szybszy silnikszybka pętla informacji zwrotnej w edytorze, CI

Nowoczesna składnia, pakowanie i wydajność

★ Egzamin

Od Python 3.11 do 3.14 seria mniejszych, ale wysoko wykorzystywanych w praktyce zmian: bogatsza gramatyka f-stringów, grupy wyjątków do obsługi współbieżnych awarii, deklaratywne pakowanie przez pyproject.toml i tańsze narzędzia do profilowania.

Termin kluczowy
PEP 701: nowa gramatyka f-stringow (3.12) - Znosi stare ograniczenia f-stringów: można użyć tego samego znaku cudzysłowu co w otaczającym stringu, pisać wyrażenia wieloliniowe, komentarze i backslashe wewnątrz {}.
f-string: to, co było niemożliwe przed 3.12 python
name = "world"
# ten sam znak cudzysłowu wewnątrz wyrażenia f-stringa - błąd przed 3.12
greeting = f"hello {"mr. " + name}"

# wyrażenie wieloliniowe z komentarzem wewnątrz {}
value = f"{
    name.upper()  # normalizuj przed wstawieniem
}"
Termin kluczowy
ExceptionGroup i except* (PEP 654, od 3.11) - ExceptionGroup opakowuje wiele niezależnych wyjątków zgłoszonych równolegle (np. przez asyncio.TaskGroup, gdy kilka zadań zawiedzie naraz). except* dopasowuje i wydziela pasujące wyjątki z grupy, resztę propaguje dalej.
except*: obsługa wielu równoległych awarii z TaskGroup python
import asyncio

async def fetch(url: str) -> str:
    if "bad" in url:
        raise ValueError(f"invalid url: {url}")
    return url

async def main():
    try:
        async with asyncio.TaskGroup() as tg:
            for url in ["ok1", "bad1", "ok2", "bad2"]:
                tg.create_task(fetch(url))
    except* ValueError as eg:
        print(f"{len(eg.exceptions)} błędne URL-e")

asyncio.run(main())
PEP 758 (3.14)
Klauzule except i except* mogą od Python 3.14 pomijać nawiasy przy wielu typach wyjątków: except ValueError, TypeError: działa tak samo jak except (ValueError, TypeError):.
Termin kluczowy
pyproject.toml (PEP 518 + PEP 621) - PEP 518 (2016) wprowadza pyproject.toml jako standardowy plik z tabelą [build-system] (backend budowania i jego zależności). PEP 621 (2020) dodaje do tego samego pliku standardową tabelę [project] z metadanymi pakietu (nazwa, wersja, zależności). Razem zastąpiły rozproszoną kombinację setup.py, setup.cfg i MANIFEST.in jednym deklaratywnym standardem ekosystemu.
Minimalny pyproject.toml toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "tenzan-inspector"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "httpx>=0.27",
    "pydantic>=2.7",
]
  • cProfile (stdlib) - profilowanie deterministyczne całego wywołania, python -m cProfile -s cumulative script.py
  • timeit (stdlib) - mikrobenchmarki krótkich fragmentów kodu, izoluje szum systemowy
  • py-spy - profiler próbkujący dołączany do działającego procesu bez zmian w kodzie, bezpieczny na produkcji
  • sys.monitoring (PEP 669, od 3.12) - nowe, tańsze API zdarzeń dla profilerów, debuggerów i narzędzi coverage, zastępuje kosztowny sys.settrace
cProfile: szybki start bez zmian w kodzie bash
python -m cProfile -s cumulative pipeline.py
Pułapka
Profilowanie na małym, lokalnym zbiorze danych dev często wskazuje złe wąskie gardło. Realny koszt (np. deserializacja dużego JSON-a, latencja sieci do bazy) ujawnia się dopiero na danych i infrastrukturze zbliżonej do produkcyjnej.

Fiszki

aktywne przypominanie

Pytanie

Sprawdź się - testowanie to nauka

20 pytań losowanych z 24, w losowej kolejności. Twoje wyniki zapisują się na tym urządzeniu.

Zbudowane przez Tenzan Logic