{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "k00",
   "metadata": {},
   "source": [
    "# Funkcje, moduły i dobre praktyki\n",
    "\n",
    "*Wersja notatnika: 2026-10-16 · przygotowanie: dr inż. Szczepan Górtowski*\n",
    "\n",
    "Funkcje znasz od zajęć 1, a metody — od zajęć 5. Dziś domykasz temat: parametry domyślne i nazwane, zasięg zmiennych,\n",
    "własne moduły i importy oraz zasady, dzięki którym twój kod przeczyta ktoś inny — albo ty za pół roku.\n",
    "\n",
    "### Jak korzystać\n",
    "1. Uruchamiaj komórki po kolei: `Ctrl + Enter` albo `Shift + Enter`.\n",
    "2. Przy ramkach **Przewidź, zanim uruchomisz** najpierw zgadnij, potem uruchom, na końcu rozwiń odpowiedź.\n",
    "3. W zadaniach zamień `pass` i `TODO` na swój kod i uruchom komórkę **Sprawdź**."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k01",
   "metadata": {},
   "source": [
    "## Spis treści\n",
    "1. Funkcja: powtórka w trzech linijkach\n",
    "2. Parametry pozycyjne, nazwane i domyślne\n",
    "3. Pułapka: lista jako wartość domyślna\n",
    "4. Dowolna liczba argumentów: `*args` i `**kwargs`\n",
    "5. Zasięg zmiennych: lokalne, globalne, LEGB\n",
    "6. Funkcja to też obiekt: `key=` i `lambda`\n",
    "7. Moduły: `import`, własny plik `.py`, `if __name__ == \"__main__\"`\n",
    "8. Zen Pythona i dobre praktyki\n",
    "9. Zadania 7.0–7.3 i zadanie 7.4 dla chętnych"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k02",
   "metadata": {},
   "source": [
    "## 1) Funkcja: powtórka w trzech linijkach\n",
    "\n",
    "`def` tworzy funkcję, parametry stoją w nawiasie, `return` oddaje wynik (zajęcia 1). Funkcja może zwrócić kilka\n",
    "wartości naraz — wtedy zwraca krotkę (zajęcia 3)."
   ]
  },
  {
   "cell_type": "code",
   "id": "k03",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def pole_prostokata(a, b):\n",
    "    return a * b\n",
    "\n",
    "def min_max(liczby):\n",
    "    return min(liczby), max(liczby)\n",
    "\n",
    "print(pole_prostokata(3, 4))\n",
    "najmniejsza, najwieksza = min_max([4, 8, 1, 9])\n",
    "print(najmniejsza, najwieksza)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k04",
   "metadata": {},
   "source": [
    "## 2) Parametry pozycyjne, nazwane i domyślne\n",
    "\n",
    "- **pozycyjne** — argumenty trafiają do parametrów po kolei,\n",
    "- **nazwane** (*keyword arguments*) — `nazwa=wartość`, kolejność nie ma znaczenia,\n",
    "- **domyślne** — `def f(x, vat=0.23)`: jeśli nie podasz `vat`, funkcja użyje `0.23`.\n",
    "\n",
    "Parametry z wartością domyślną stoją **po** parametrach bez niej."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k05",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** co wypiszą trzy wywołania `powitanie` poniżej?\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> `Cześć, Ola!` · `Dzień dobry, Jan!` · `Hej, Ewa!` — nazwany argument może stać w dowolnym miejscu wywołania, a brakujący bierze wartość domyślną.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k06",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def powitanie(imie, zwrot=\"Cześć\", znak=\"!\"):\n",
    "    return f\"{zwrot}, {imie}{znak}\"\n",
    "\n",
    "print(powitanie(\"Ola\"))\n",
    "print(powitanie(\"Jan\", \"Dzień dobry\"))\n",
    "print(powitanie(zwrot=\"Hej\", imie=\"Ewa\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k07",
   "metadata": {},
   "source": [
    "## 3) Pułapka: lista jako wartość domyślna\n",
    "\n",
    "Oficjalne FAQ Pythona: *„Default values are created exactly once, when the function is defined.”* Wartość domyślna\n",
    "powstaje **raz** — przy `def` — a nie przy każdym wywołaniu. Jeśli to lista, każde wywołanie bez argumentu dopisuje\n",
    "do **tej samej** listy. To znowu dwie etykiety na jeden obiekt (zajęcia 2 i 5)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k08",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** co wypiszą dwa wywołania `dopisz(1)` i `dopisz(2)`?\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> `[1]`, a potem `[1, 2]` — druga liczba trafiła do tej samej listy co pierwsza. Poprawka: `wynik=None` i nowa lista w środku funkcji.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k09",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def dopisz(x, wynik=[]):    # BŁĄD: jedna lista na wszystko\n",
    "    wynik.append(x)\n",
    "    return wynik\n",
    "\n",
    "print(dopisz(1))\n",
    "print(dopisz(2))\n",
    "\n",
    "def dopisz_dobrze(x, wynik=None):\n",
    "    if wynik is None:\n",
    "        wynik = []                # nowa lista przy każdym wywołaniu\n",
    "    wynik.append(x)\n",
    "    return wynik\n",
    "\n",
    "print(dopisz_dobrze(1))\n",
    "print(dopisz_dobrze(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k10",
   "metadata": {},
   "source": [
    "## 4) Dowolna liczba argumentów: `*args` i `**kwargs`\n",
    "\n",
    "- `*args` zbiera nadmiarowe argumenty **pozycyjne** w krotkę,\n",
    "- `**kwargs` zbiera nadmiarowe argumenty **nazwane** w słownik.\n",
    "\n",
    "Tak działa `print`: przyjmuje dowolnie wiele wartości i nazwane `sep=` oraz `end=`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k11",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def suma(*liczby):\n",
    "    print(\"liczby =\", liczby)\n",
    "    return sum(liczby)\n",
    "\n",
    "def opis(**cechy):\n",
    "    for klucz, wartosc in cechy.items():\n",
    "        print(f\"{klucz}: {wartosc}\")\n",
    "\n",
    "print(suma(1, 2, 3, 4))\n",
    "opis(przedmiot=\"ASD\", ects=5, semestr=\"zimowy\")\n",
    "print(\"a\", \"b\", \"c\", sep=\" | \")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k12",
   "metadata": {},
   "source": [
    "## 5) Zasięg zmiennych: lokalne, globalne, LEGB\n",
    "\n",
    "Zmienna utworzona w funkcji jest **lokalna**: znika po `return`. Python szuka nazwy w czterech miejscach, po kolei:\n",
    "\n",
    "| Litera | Zasięg | Przykład |\n",
    "|---|---|---|\n",
    "| **L** | lokalny — wnętrze bieżącej funkcji | parametry, zmienne z `def` |\n",
    "| **E** | otaczający — funkcja, w której ta jest zagnieżdżona | `def zewnetrzna(): x = 1; def wewnetrzna(): print(x)` |\n",
    "| **G** | globalny — poziom pliku albo notatnika | stałe, importy |\n",
    "| **B** | wbudowany — Python | `len`, `print`, `sum` |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k13",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** co wypiszą trzy `print` w kodzie poniżej?\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> `f: lokalne` (funkcja `g` nie ma własnego `x`, więc bierze go z otaczającej `f` — E), znowu `f: lokalne` (L), a na końcu `globalne` (G): po wyjściu z `f` jej `x` znika.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k14",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "x = \"globalne\"\n",
    "\n",
    "def f():\n",
    "    x = \"f: lokalne\"\n",
    "    def g():\n",
    "        print(x)          # E — z funkcji otaczającej\n",
    "    g()\n",
    "    print(x)              # L\n",
    "\n",
    "f()\n",
    "print(x)                  # G"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k15",
   "metadata": {},
   "source": [
    "### Pułapka: `UnboundLocalError`\n",
    "\n",
    "Jeśli funkcja **przypisuje** do nazwy, Python uznaje tę nazwę za lokalną w całej funkcji — także w linii przed\n",
    "przypisaniem. `licznik += 1` to odczyt i przypisanie naraz, więc odczyt trafia na pustą zmienną lokalną.\n",
    "Lepszym wyjściem niż `global` jest zwykle przekazanie wartości przez parametr i `return`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k16",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "licznik = 0\n",
    "\n",
    "def zwieksz():\n",
    "    licznik += 1          # odczyt lokalnej zmiennej, której jeszcze nie ma\n",
    "\n",
    "try:\n",
    "    zwieksz()\n",
    "except UnboundLocalError as e:\n",
    "    print(\"UnboundLocalError:\", e)\n",
    "\n",
    "def zwieksz_global():\n",
    "    global licznik        # działa, ale ukrywa zależność od stanu globalnego\n",
    "    licznik += 1\n",
    "\n",
    "def zwieksz_czysto(stan):\n",
    "    return stan + 1       # lepiej: wejście → wyjście, bez stanu globalnego\n",
    "\n",
    "zwieksz_global()\n",
    "print(\"global:\", licznik, \"| czysto:\", zwieksz_czysto(licznik))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k17",
   "metadata": {},
   "source": [
    "## 6) Funkcja to też obiekt: `key=` i `lambda`\n",
    "\n",
    "Funkcję można przekazać jak każdą wartość — tak działało `sorted(…, key=len)` na zajęciach 3 i 4.\n",
    "`lambda` to funkcja w jednej linii, bez nazwy: przydaje się właśnie jako `key=`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k18",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "studenci = [(\"Ola\", 4.5), (\"Jan\", 3.5), (\"Ewa\", 5.0)]\n",
    "\n",
    "def ocena(para):\n",
    "    return para[1]\n",
    "\n",
    "print(sorted(studenci, key=ocena))\n",
    "print(sorted(studenci, key=lambda para: para[1], reverse=True))\n",
    "print(type(ocena), type(lambda x: x))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k19",
   "metadata": {},
   "source": [
    "## 7) Moduły: `import`, własny plik `.py`, `if __name__ == \"__main__\"`\n",
    "\n",
    "Moduł to plik `.py`. Importujesz go na kilka sposobów:\n",
    "\n",
    "| Zapis | Co dostajesz | Wywołanie |\n",
    "|---|---|---|\n",
    "| `import math` | cały moduł | `math.sqrt(16)` |\n",
    "| `from math import sqrt` | jedną nazwę | `sqrt(16)` |\n",
    "| `import numpy as np` | moduł pod krótszą nazwą | `np.mean(...)` |\n",
    "\n",
    "Paczki **zewnętrzne** (spoza biblioteki standardowej) instalujesz poleceniem `pip install nazwa` — w Colab:\n",
    "`!pip install nazwa`. Popularne paczki (`numpy`, `pandas`, `matplotlib`) Colab ma już zainstalowane.\n",
    "\n",
    "**Własny moduł** to po prostu twój plik. W Jupyterze i Colab zapiszesz go komórką `%%writefile`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k20",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "%%writefile geometria.py\n",
    "\"\"\"Moduł z funkcjami geometrycznymi.\"\"\"\n",
    "\n",
    "PI = 3.14159\n",
    "\n",
    "\n",
    "def pole_kola(r):\n",
    "    return PI * r * r\n",
    "\n",
    "\n",
    "print(\"Moduł geometria: __name__ =\", __name__)\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    print(\"Uruchomiony jako program — tu zwykle są testy.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k21",
   "metadata": {},
   "source": [
    "> **Przewidź, zanim uruchomisz:** co wypisze `import geometria`? Pojawi się linia „Uruchomiony jako program”?\n",
    ">\n",
    "> <details><summary>Odpowiedź</summary>\n",
    ">\n",
    "> Tylko `Moduł geometria: __name__ = geometria`. Przy imporcie `__name__` to nazwa modułu, więc blok `if __name__ == \"__main__\"` się nie wykona — wykona się, gdy uruchomisz `python geometria.py`.\n",
    ">\n",
    "> </details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "k22",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import geometria\n",
    "from geometria import pole_kola\n",
    "\n",
    "print(round(geometria.pole_kola(2), 2), round(pole_kola(1), 2))\n",
    "print(\"stała z modułu:\", geometria.PI)"
   ]
  },
  {
   "cell_type": "code",
   "id": "k23",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from importlib.metadata import version\n",
    "\n",
    "print(\"matplotlib\", version(\"matplotlib\"))\n",
    "help(geometria.pole_kola)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k24",
   "metadata": {},
   "source": [
    "## 8) Zen Pythona i dobre praktyki\n",
    "\n",
    "PEP 20 zbiera zasady Pythona w 19 aforyzmach. Wyświetlisz je poleceniem `import this`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k25",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import this"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k26",
   "metadata": {},
   "source": [
    "### Pięć zasad, które sprawdzamy w zadaniach\n",
    "\n",
    "1. **Nazwa mówi, co funkcja robi:** `srednia_ocen`, nie `f` czy `licz2`. Zmienne i funkcje w `snake_case` (zajęcia 1).\n",
    "2. **Docstring** w pierwszej linii funkcji: co robi i co zwraca. `help()` go wyświetli.\n",
    "3. **Podpowiedzi typów** (`def f(x: int) -> float`) — Python ich nie sprawdza, ale czyta je człowiek i edytor.\n",
    "4. **Jedna funkcja — jedno zadanie.** Funkcja liczy *albo* drukuje; wynik oddaje przez `return`.\n",
    "5. **Test:** kilka `assert` na przypadki typowe i brzegowe (pusta lista, zero, wartość ujemna).\n",
    "\n",
    "Przed i po — ta sama logika:"
   ]
  },
  {
   "cell_type": "code",
   "id": "k27",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# PRZED: zła nazwa, stan globalny, liczenie i drukowanie w jednym\n",
    "dane = [4, 8, 1, 9]\n",
    "wynik = 0\n",
    "\n",
    "def f():\n",
    "    global wynik\n",
    "    for i in range(len(dane)):\n",
    "        wynik = wynik + dane[i]\n",
    "    print(\"srednia\", wynik / len(dane))\n",
    "\n",
    "f()\n",
    "\n",
    "\n",
    "# PO: nazwa, docstring, typy, wejście → wyjście, test\n",
    "def srednia(liczby: list[float]) -> float:\n",
    "    \"\"\"Średnia arytmetyczna niepustej listy liczb.\"\"\"\n",
    "    return sum(liczby) / len(liczby)\n",
    "\n",
    "assert srednia([4, 8, 1, 9]) == 5.5\n",
    "print(\"srednia\", srednia([4, 8, 1, 9]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k28",
   "metadata": {},
   "source": [
    "## 9) Zadanie 7.0 — parametr domyślny\n",
    "\n",
    "Napisz `cena_brutto(netto, vat=0.23)`, która zwraca cenę brutto **zaokrągloną do groszy** (`round(…, 2)`)."
   ]
  },
  {
   "cell_type": "code",
   "id": "k29",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def cena_brutto(netto: float, vat: float = 0.23) -> float:\n",
    "    \"\"\"Cena brutto zaokrąglona do groszy.\"\"\"\n",
    "    pass  # TODO"
   ]
  },
  {
   "cell_type": "code",
   "id": "k30",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    assert cena_brutto(100) == 123.0\n",
    "    assert cena_brutto(100, 0.08) == 108.0\n",
    "    assert cena_brutto(vat=0.05, netto=50) == 52.5\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw rozwiązanie.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Kod kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Zadanie 7.0 zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k31",
   "metadata": {},
   "source": [
    "## Zadanie 7.1 — napraw pułapkę wartości domyślnej\n",
    "\n",
    "Funkcja `dodaj_do_koszyka(produkt, koszyk=[])` ma błąd z sekcji 3: kolejni klienci dostają cudze produkty.\n",
    "Popraw ją wzorcem z FAQ Pythona: `koszyk=None` i nowa lista w środku."
   ]
  },
  {
   "cell_type": "code",
   "id": "k32",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def dodaj_do_koszyka(produkt: str, koszyk=[]) -> list:\n",
    "    \"\"\"Dodaje produkt do koszyka; bez koszyka zaczyna nowy.\"\"\"\n",
    "    koszyk.append(produkt)  # TODO: popraw wartość domyślną\n",
    "    return koszyk"
   ]
  },
  {
   "cell_type": "code",
   "id": "k33",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    a = dodaj_do_koszyka(\"chleb\")\n",
    "    b = dodaj_do_koszyka(\"mleko\")\n",
    "    assert b == [\"mleko\"], \"drugi klient dostał cudzy chleb\"\n",
    "    assert dodaj_do_koszyka(\"ser\", a) == [\"chleb\", \"ser\"]\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw rozwiązanie.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Kod kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Zadanie 7.1 zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k34",
   "metadata": {},
   "source": [
    "## Zadanie 7.2 — dowolna liczba argumentów\n",
    "\n",
    "Napisz `srednia(*liczby)`: `srednia(2, 3, 4)` zwraca `3.0`. Bez argumentów funkcja podnosi `ValueError`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k35",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def srednia(*liczby: float) -> float:\n",
    "    \"\"\"Średnia dowolnej liczby argumentów.\"\"\"\n",
    "    pass  # TODO"
   ]
  },
  {
   "cell_type": "code",
   "id": "k36",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    assert srednia(2, 3, 4) == 3.0\n",
    "    assert srednia(5) == 5.0\n",
    "    try:\n",
    "        srednia()\n",
    "        bez_bledu = True\n",
    "    except ValueError:\n",
    "        bez_bledu = False\n",
    "    assert not bez_bledu, 'brak liczb powinien dać ValueError'\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw rozwiązanie.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Kod kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Zadanie 7.2 zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k37",
   "metadata": {},
   "source": [
    "## Zadanie 7.3 — własny moduł\n",
    "\n",
    "Komórka poniżej zapisuje plik `narzedzia.py`. Uzupełnij w nim funkcję `licz_slowa(tekst)`: liczba słów oddzielonych\n",
    "białymi znakami. Uruchom komórkę (zapisze plik), potem komórkę Sprawdź — ona importuje twój moduł."
   ]
  },
  {
   "cell_type": "code",
   "id": "k38",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "%%writefile narzedzia.py\n",
    "\"\"\"Narzędzia do tekstu.\"\"\"\n",
    "\n",
    "\n",
    "def licz_slowa(tekst: str) -> int:\n",
    "    \"\"\"Liczba słów oddzielonych białymi znakami.\"\"\"\n",
    "    return 0  # TODO\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    print(licz_slowa(\"Ala ma kota\"))"
   ]
  },
  {
   "cell_type": "code",
   "id": "k39",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    import importlib\n",
    "    import narzedzia\n",
    "    importlib.reload(narzedzia)  # wczytaj plik od nowa po każdej zmianie\n",
    "    assert narzedzia.licz_slowa(\"Ala ma kota\") == 3\n",
    "    assert narzedzia.licz_slowa(\"  dwa   słowa \") == 2\n",
    "    assert narzedzia.licz_slowa(\"\") == 0\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw rozwiązanie.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Kod kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Zadanie 7.3 zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k40",
   "metadata": {},
   "source": [
    "## Zadanie 7.4 dla chętnych — refaktoryzacja\n",
    "\n",
    "Przepisz poniższy kod jako funkcję `statystyki(liczby)`, która **zwraca** krotkę `(minimum, maksimum, średnia)`,\n",
    "ma docstring i typy, nie używa zmiennych globalnych, a dla pustej listy podnosi `ValueError`."
   ]
  },
  {
   "cell_type": "code",
   "id": "k41",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# kod do przepisania (nie zmieniaj go — napisz nową funkcję poniżej)\n",
    "l = [3, 7, 1]\n",
    "a = l[0]\n",
    "b = l[0]\n",
    "for i in range(len(l)):\n",
    "    if l[i] < a:\n",
    "        a = l[i]\n",
    "    if l[i] > b:\n",
    "        b = l[i]\n",
    "print(a, b, sum(l) / len(l))\n",
    "\n",
    "\n",
    "def statystyki(liczby):\n",
    "    pass  # TODO"
   ]
  },
  {
   "cell_type": "code",
   "id": "k42",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Sprawdź\n",
    "try:\n",
    "    assert statystyki([3, 7, 1]) == (1, 7, 11 / 3)\n",
    "    assert statystyki([5]) == (5, 5, 5.0)\n",
    "    try:\n",
    "        statystyki([])\n",
    "        bez_bledu = True\n",
    "    except ValueError:\n",
    "        bez_bledu = False\n",
    "    assert not bez_bledu, 'pusta lista powinna dać ValueError'\n",
    "except AssertionError:\n",
    "    print(\"❌ Testy nie przeszły — popraw rozwiązanie.\")\n",
    "except Exception as e:\n",
    "    print(\"❌ Kod kończy się błędem:\", type(e).__name__, e)\n",
    "else:\n",
    "    print(\"✅ Zadanie 7.4 zaliczone!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "k43",
   "metadata": {},
   "source": [
    "## Zapamiętaj\n",
    "\n",
    "- Parametry domyślne stoją na końcu; **nie** używaj list i słowników jako wartości domyślnych — `None` i nowa lista\n",
    "  w środku.\n",
    "- Python szuka nazw w kolejności **L → E → G → B**. Przypisanie w funkcji robi nazwę lokalną.\n",
    "- Moduł to plik `.py`; blok `if __name__ == \"__main__\"` działa tylko przy uruchomieniu pliku, nie przy imporcie.\n",
    "- Dobra funkcja: mówiąca nazwa, docstring, typy, jedno zadanie, `return` zamiast `print`, test.\n",
    "\n",
    "## Zadania dodatkowe\n",
    "\n",
    "1. Dlaczego `def f(x=[])` jest pułapką, a `def f(x=0)` nie? (Podpowiedź: mutowalność, zajęcia 3.)\n",
    "2. Napisz moduł `oceny.py` z funkcją `ocena_z_procentu(p)`, która zwraca ocenę według skali z zajęć 1.\n",
    "3. Uruchom `import this` i wybierz jedną zasadę, którą łamie kod „przed” z sekcji 8."
   ]
  }
 ],
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3",
   "language": "python"
  },
  "language_info": {
   "name": "python"
  }
 }
}
