Tenzan Academy
Motyw
  • 8 sekcji
  • 24 pytania
  • 21 fiszek

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

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.

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

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.

Quiz

Domyślne argumenty i wyrażenia przypisania

na egzaminie

Argument domyślny jest ewaluowany dokładnie raz, przy wykonaniu instrukcji def, nie przy każdym wywołaniu. Wynik trafia do __defaults__ obiektu funkcji. Dla typów mutowalnych (list, dict, set) to droga do buga produkcyjnego: 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 z PEP 572 (Python 3.8). Przypisuje wartość do zmiennej i używa jej w tym samym wyrażeniu, np. w warunku while, if czy liście 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 przez := w liście składanej wiąże się w zasięgu otaczającej funkcji (lub modułu), nie w prywatnym zasięgu listy jak zwykła zmienna pętli for. To celowe zachowanie z PEP 572, ale zaskakuje: po wykonaniu przykładu zmienna y istnieje poza listą.

  • 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.

Dalej: 02 GIL, wątki, procesy i asyncio

GIL, wątki, procesy i asyncio

na egzaminie

CPython (referencyjna implementacja) chroni swoje wewnętrzne struktury danych jednym globalnym zamkiem: GIL (Global Interpreter Lock). Tylko jeden wątek naraz wykonuje bytecode Pythona, niezależnie od liczby rdzeni CPU. To nie drobny szczegół implementacji: GIL 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) był eksperymentalny w Python 3.13 (opt-in, binarka python3.13t). W Python 3.14 jest oficjalnie wspierany (PEP 779). GIL wciąż jest domyślnie włączony.

Fakt na test

Standardowa binarka python3.14 nadal używa GIL. 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 blokującego wywołania) 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: tysiące współbieżnych operacji I/O w jednym wątku 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.

Dalej: 03 Generatory i iteratory: pamięć kontra listy

Generatory i iteratory: pamięć kontra listy

na egzaminie

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

Wyrażenie sum(x * x for x in range(10**8)) trzyma w pamięci jeden element na raz (kilkadziesiąt bajtów). Wyrażenie sum([x * x for x in range(10**8)]) najpierw tworzy pełną listę ~10**8 elementów (setki megabajtów) i dopiero potem sumuje.

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 elementów (wymaga posortowanych danych wg klucza)
  • itertools.pairwise(data) - kolejne pary sąsiadujących elementów (od Python 3.10)
itertools.pairwise - kolejne pary sąsiednich elementów 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.

Dalej: 04 Dekoratory i functools

Dekoratory i functools

na egzaminie

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 introspekcję 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 konfigurowalną liczbą prób 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 pozostałe operatory porównania z jednego (__eq__ + __lt__)
Dalej: 05 Menedżery kontekstu i contextlib

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 menedżer 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 menedżerów 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) - wywołuje obj.close() na wyjściu dla obiektów bez pełnego protokołu with
  • contextlib.suppress(*exc_types) - ignoruje wskazane wyjątki
  • contextlib.redirect_stdout/redirect_stderr - tymczasowe przekierowanie strumieni
  • contextlib.nullcontext() - placeholder gdy menedżer kontekstu jest opcjonalny
Dalej: 06 Dopasowanie wzorców: match/case

Dopasowanie wzorców: match/case

na egzaminie

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 literałów 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 po dopasowaniu wzorca. Zawęża dopasowanie o logikę, której sama struktura nie wyrazi, 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 w case (np. case x:) to zawsze wzorzec przechwytujący (capture pattern). Wiąże x z dopasowaną wartością i pasuje do wszystkiego, nigdy nie porównuje z istniejącą zmienną. 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 definiować __match_args__ (dataclass generuje to automatycznie) aby wspierać pozycyjne wzorce klas jak Point(0, 0)
Dalej: 07 Modelowanie danych i typowanie statyczne

Modelowanie danych i typowanie statyczne

na egzaminie

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

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
Dalej: 08 Nowoczesna składnia, pakowanie i wydajność

Nowoczesna składnia, pakowanie i wydajność

na egzaminie

Od Python 3.11 do 3.14 przyszła seria mniejszych, ale często używanych zmian. Są to: 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. Wewnątrz {} można użyć tego samego cudzysłowu co w otaczającym stringu oraz pisać wyrażenia wieloliniowe, komentarze i backslashe.

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. Takie grupy zgłasza np. 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 z tabelą [build-system] (backend budowania i jego zależności). PEP 621 (2020) dodaje tabelę [project] z metadanymi pakietu (nazwa, wersja, zależności). Razem zastępują setup.py, setup.cfg i MANIFEST.in deklaratywnym standardem.

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.

Sprawdź się

20 z 24 pytań, losowo, z wyjaśnieniem po każdym. Wynik zostaje na tym urządzeniu.

Rozpocznij quiz

Fiszki: Python dla inżynierów

Pytanie

O tym demo

Działające demo kursów napisanych przez Tenzan Logic. Wyniki zostają na tym urządzeniu, bez konta.

Każdy kurs, pytanie i fiszkę napisał Tenzan Logic, po polsku i od zera. Jeśli chcesz taki zestaw dla swojego zespołu, z Twoimi procedurami i Twoim słownictwem, napisz.

Napisz do Tenzan Logic

Jak to działa

100% czas (dni) bez powtórek 1 3 7 21 z powtórkami
Schemat idei, nie dane pomiarowe (Ebbinghaus 1885; Cepeda i in. 2006).
  • Odstępy

    1·3·7·21 dni do powtórki

    Fiszka z „Umiem” wraca po 1, 3, 7 i 21 dniach.

    Badanie

    Powtórki z przerwami dają trwalszą pamięć niż jedna po drugiej. Cepeda i in. 2006, Psychological Bulletin 132(3): 354-380

  • Przypominanie

    ukryta odpowiedź fiszki

    Najpierw odpowiedz z pamięci, potem odwróć.

    Badanie

    Próba przypomnienia dała lepszą pamięć po dwóch dniach i po tygodniu niż ponowne czytanie. Roediger i Karpicke 2006, Psychological Science 17(3): 249-255

  • Dwa kody

    4 kolory bloków

    Fakt, termin, mnemonik i pułapka mają swój kolor.

    Badanie

    Teoria: słowo i obraz zapisują się osobno. To teoria, nie gwarancja wyniku. Paivio 1991, Canadian Journal of Psychology 45(3): 255-287

  • Porcje

    167 krótkich sekcji

    Jedna sekcja to jedna porcja do zapamiętania.

    Badanie

    Pamięć robocza mieści około czterech porcji (Miller 1956: siedem, Cowan 2001: cztery). Cowan 2001, Behavioral and Brain Sciences 24(1): 87-114

  • Skróty

    134 zielone bloki

    Mnemonik zbiera kilka elementów w jeden, np. „SUR”.

    Badanie

    Skrót zbiera kilka elementów w jeden; przykład „SUR” jest w kursie o podstawach AI. Opis funkcji aplikacji, bez cytowanego badania

  • Testy

    20 z 24 pytań w quizie

    Po każdej odpowiedzi widzisz wyjaśnienie.

    Badanie

    Ćwiczenie, nie sprawdzian: pytania są w losowej kolejności i uzupełniają fiszki, nie zastępują ich. Efekt testowania: zob. „Przypominanie”

Szukaj

Postęp

Brak wyników. Rozwiąż pierwszy quiz.

Tu zbiera się Twój postęp: rekord i medal każdego quizu, seria dni i pytania do powtórki.

Gdzie jest zapis
Tylko w pamięci tej przeglądarki. Konta nie ma i wyniki nigdzie nie są wysyłane. Inna przeglądarka lub inne urządzenie zaczyna od zera.
Jak wyczyścić
W quizie: Historia, potem Wyczyść historię. Fiszki i serię dni usuniesz razem z danymi witryny w ustawieniach przeglądarki.
Wybierz quiz

Progi medali

Medal liczy się z najlepszego wyniku quizu: Brąz od 60%, Srebro od 75%, Złoto od 90%, Platyna przy 100%.