← Strona główna

03 — Funkcje własne

Definiowanie funkcji, parametry, return, zasięg zmiennych, rekurencja.

Start i podstawy

1. Definiowanie funkcji — def

Funkcja własna to nazwany blok kodu, który możesz wywołać wielokrotnie. Definiujesz ją słowem kluczowym def.

  • Definicja zaczyna się od def nazwa(parametry):
  • Ciało funkcji jest wcięte (4 spacje)
  • Funkcja jest wykonywana dopiero gdy ją wywołasz
  • Definicja musi być przed pierwszym wywołaniem
  • Nazwa funkcji — te same zasady co nazwa zmiennej (snake_case)
Po co pisać własne funkcje? Zasada DRY — Don't Repeat Yourself. Kod który powtarzasz więcej niż raz — umieść w funkcji. Łatwiej go wtedy poprawić, przetestować i zrozumieć.
Schemat i przykład — def
# Schemat definicji
def nazwa_funkcji(parametr1, parametr2):
    """Opis funkcji (docstring) — opcjonalny."""
    # ciało funkcji
    wynik = parametr1 + parametr2
    return wynik

# Wywołanie
rezultat = nazwa_funkcji(3, 5)
print(rezultat)   # 8

# Funkcja bez parametrów i bez return
def przywitaj():
    print("Cześć!")

przywitaj()        # Cześć!
przywitaj()        # Cześć! — można wywołać wiele razy

2. Parametry i argumenty

Parametr — nazwa w definicji funkcji. Argument — wartość przekazana przy wywołaniu.

  • Pozycyjne — kolejność ma znaczenie
  • Nazwane (keyword) — podajesz nazwę: f(x=5)
  • Domyślne — parametr ma wartość domyślną jeśli nie podasz argumentu
  • *args — dowolna liczba argumentów pozycyjnych (krotka)
  • **kwargs — dowolna liczba argumentów nazwanych (słownik)
Kolejność parametrów Zawsze: najpierw zwykłe, potem z wartością domyślną, potem *args, na końcu **kwargs. Parametry z wartością domyślną muszą być po tych bez.
Przykład — rodzaje parametrów
# Parametry pozycyjne
def dodaj(a, b):
    return a + b

print(dodaj(3, 5))          # 8
print(dodaj(b=5, a=3))      # 8 — nazwane, kolejność nieważna

# Parametry z wartością domyślną
def przywitaj(imie, pozdrowienie="Cześć"):
    print(f"{pozdrowienie}, {imie}!")

przywitaj("Kasia")              # Cześć, Kasia!
przywitaj("Marek", "Hej")       # Hej, Marek!

# *args — dowolna liczba argumentów
def suma(*liczby):
    return sum(liczby)

print(suma(1, 2, 3))        # 6
print(suma(1, 2, 3, 4, 5))  # 15

# **kwargs — argumenty nazwane
def info(**dane):
    for klucz, wartosc in dane.items():
        print(f"{klucz}: {wartosc}")

info(imie="Kasia", wiek=17, miasto="Kraków")

3. Zwracanie wartości — return

return kończy działanie funkcji i zwraca wartość do miejsca wywołania. Bez return funkcja zwraca None.

  • Możesz mieć wiele instrukcji return — wykona się pierwsza napotkana
  • Możesz zwrócić wiele wartości naraz — zostaną spakowane w krotkę
  • return bez wartości — kończy funkcję, zwraca None
  • Wartość zwróconą przez funkcję możesz przypisać do zmiennej lub użyć bezpośrednio
Częsty błąd print() wewnątrz funkcji wypisuje na ekran, ale nic nie zwraca. Jeśli potrzebujesz użyć wyniku później — użyj return, nie print().
Przykład — return
# Funkcja z return
def kwadrat(x):
    return x ** 2

wynik = kwadrat(5)
print(wynik)            # 25
print(kwadrat(3) + 1)   # 10 — wynik od razu użyty

# Wiele return — warunki
def znak(x):
    if x > 0:
        return "dodatnia"
    elif x < 0:
        return "ujemna"
    else:
        return "zero"

print(znak(-5))         # ujemna
print(znak(0))          # zero

# Zwracanie wielu wartości
def min_max(lista):
    return min(lista), max(lista)

mini, maxi = min_max([3, 1, 7, 2, 9])
print(mini, maxi)       # 1 9

# Funkcja bez return → None
def nic_nie_zwraca():
    print("Działam!")

x = nic_nie_zwraca()    # Działam!
print(x)                # None

4. Zasięg zmiennych — local i global

Zmienna zdefiniowana wewnątrz funkcji istnieje tylko w tej funkcji (zasięg lokalny). Zmienna poza funkcją ma zasięg globalny.

  • Funkcja może czytać zmienne globalne
  • Funkcja nie może modyfikować zmiennej globalnej bez słowa global
  • Słowo global — rzadko używane, zazwyczaj lepiej przekazać wartość przez parametr i return
  • Zmienna lokalna o tej samej nazwie co globalna — to inna zmienna, przesłania globalną
Zasada dobrej praktyki Unikaj słowa global. Przekazuj dane przez parametry i odbieraj przez return. Funkcje powinny być niezależne od stanu zewnętrznego.
Przykład — zasięg zmiennych
x = 10   # zmienna globalna

def pokaz():
    print(x)   # można CZYTAĆ globalną

pokaz()          # 10

def sprobuj_zmienic():
    x = 99     # to NOWA zmienna lokalna, nie globalna!
    print(x)   # 99

sprobuj_zmienic()
print(x)         # 10 — globalna niezmieniona

# Użycie global (niezalecane)
def zmien_global():
    global x
    x = 99

zmien_global()
print(x)         # 99 — teraz zmieniona

# Lepsze podejście: parametr + return
def dodaj_do(wartosc, ile):
    return wartosc + ile

x = 10
x = dodaj_do(x, 5)
print(x)         # 15

5. Funkcje anonimowe — lambda

Lambda to krótka funkcja jednolinijkowa bez nazwy. Przydatna gdy potrzebujesz prostej funkcji na chwilę — np. jako argument do sorted() czy map().

  • Składnia: lambda parametry: wyrażenie
  • Zawsze zwraca wartość wyrażenia (jak return)
  • Może mieć wiele parametrów
  • Nie może zawierać bloków kodu — tylko jedno wyrażenie
Kiedy używać lambda? Gdy funkcja jest prosta i jednorazowa — głównie jako argument do sorted(key=...), map(), filter(). Dla złożonej logiki zawsze lepiej napisać normalną funkcję def.
Przykład — lambda
# Zwykła funkcja vs lambda
def kwadrat(x):
    return x ** 2

kwadrat_l = lambda x: x ** 2

print(kwadrat(5))    # 25
print(kwadrat_l(5))  # 25

# Lambda z wieloma parametrami
dodaj = lambda a, b: a + b
print(dodaj(3, 4))   # 7

# Lambda jako key w sorted
osoby = [("Kasia", 17), ("Marek", 15), ("Zofia", 19)]

wg_wieku = sorted(osoby, key=lambda osoba: osoba[1])
print(wg_wieku)
# [('Marek', 15), ('Kasia', 17), ('Zofia', 19)]

wg_imienia = sorted(osoby, key=lambda osoba: osoba[0])
print(wg_imienia)

# Lambda w filter i map
liczby = [1, 2, 3, 4, 5, 6]
parzyste = list(filter(lambda x: x % 2 == 0, liczby))
kwadraty = list(map(lambda x: x ** 2, liczby))
print(parzyste)   # [2, 4, 6]
print(kwadraty)   # [1, 4, 9, 16, 25, 36]

6. Rekurencja

Rekurencja to technika, w której funkcja wywołuje samą siebie. Każde wywołanie rekurencyjne rozwiązuje mniejszy fragment problemu.

  • Warunek bazowy (base case) — zatrzymuje rekurencję, musi być zawsze!
  • Wywołanie rekurencyjne — funkcja wywołuje się z mniejszym argumentem
  • Bez warunku bazowego — nieskończona rekurencja → błąd RecursionError
  • Python domyślnie ogranicza głębokość do ~1000 wywołań
Rekurencja na egzaminie INF.04 Najczęściej pojawia się silnia, ciąg Fibonacciego i NWD (algorytm Euklidesa). Naucz się ich na pamięć — to klasyki.
Przykład — rekurencja
# Silnia: n! = n * (n-1)!  ;  0! = 1
def silnia(n):
    if n == 0:          # warunek bazowy
        return 1
    return n * silnia(n - 1)

print(silnia(5))        # 120
# 5 * 4 * 3 * 2 * 1 * 1

# Fibonacci: F(n) = F(n-1) + F(n-2)  ;  F(0)=0, F(1)=1
def fibonacci(n):
    if n <= 1:          # warunek bazowy
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

for i in range(8):
    print(fibonacci(i), end=" ")
# 0 1 1 2 3 5 8 13

# NWD — algorytm Euklidesa
def nwd(a, b):
    if b == 0:          # warunek bazowy
        return a
    return nwd(b, a % b)

print(nwd(48, 18))      # 6
print(nwd(100, 75))     # 25

Zadania przykładowe z omówieniem

Zadanie 1 Funkcja sprawdzająca liczbę pierwszą średnie

Napisz funkcję czy_pierwsza(n), która zwraca True jeśli liczba jest pierwsza, lub False w przeciwnym razie. Następnie wypisz wszystkie liczby pierwsze od 2 do 50.

Rozwiązanie
def czy_pierwsza(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

# Wypisanie liczb pierwszych od 2 do 50
pierwsze = []
for n in range(2, 51):
    if czy_pierwsza(n):
        pierwsze.append(n)

print("Liczby pierwsze:", pierwsze)
print("Ile ich jest:", len(pierwsze))
Omówienie krok po kroku
  1. Warunek n < 2
    Liczby 0 i 1 nie są pierwsze z definicji. Zwracamy od razu False.
  2. Sprawdzanie do pierwiastka
    int(n ** 0.5) + 1 — wystarczy sprawdzić dzielniki do √n. Jeśli n ma dzielnik większy niż √n, to ma też dzielnik mniejszy. Optymalizacja zmniejsza liczbę operacji.
  3. return False wewnątrz pętli
    Gdy znajdziemy jeden dzielnik — od razu kończymy i zwracamy False. Nie ma sensu sprawdzać dalej.
  4. return True po pętli
    Jeśli pętla zakończyła się bez znalezienia dzielnika — liczba jest pierwsza. return True jest poza pętlą.
Zadanie 2 Kalkulator z funkcjami średnie

Napisz cztery funkcje: dodaj(a,b), odejmij(a,b), mnoz(a,b), dziel(a,b). Funkcja dzielenia powinna zwrócić komunikat błędu gdy b=0. Stwórz prosty kalkulator wczytujący dwie liczby i działanie.

Rozwiązanie
def dodaj(a, b):
    return a + b

def odejmij(a, b):
    return a - b

def mnoz(a, b):
    return a * b

def dziel(a, b):
    if b == 0:
        return "Błąd: dzielenie przez zero!"
    return a / b

# Kalkulator
a      = float(input("Pierwsza liczba: "))
dzial  = input("Działanie (+, -, *, /): ")
b      = float(input("Druga liczba: "))

if dzial == "+":
    wynik = dodaj(a, b)
elif dzial == "-":
    wynik = odejmij(a, b)
elif dzial == "*":
    wynik = mnoz(a, b)
elif dzial == "/":
    wynik = dziel(a, b)
else:
    wynik = "Nieznane działanie"

print(f"Wynik: {wynik}")
Omówienie krok po kroku
  1. Każda operacja to osobna funkcja
    Zasada pojedynczej odpowiedzialności — każda funkcja robi jedną rzecz. Łatwiej testować i modyfikować.
  2. Funkcja dziel() zwraca różne typy
    Zwraca float lub str. To nie idealne rozwiązanie — lepiej byłoby użyć wyjątków — ale na poziomie INF.04 jest akceptowalne.
  3. Wywołanie funkcji przez zmienną warunkową
    if dzial == "+" decyduje którą funkcję wywołać. Wynik trafia do zmiennej wynik i jest wypisany na końcu — raz, wspólnie.
  4. float() dla obu liczb
    Używamy float() zamiast int() — kalkulator powinien działać też dla liczb z przecinkiem.
Zadanie 3 Potęga rekurencyjnie trudne

Napisz rekurencyjną funkcję potega(podstawa, wykladnik) obliczającą wartość podstawa^wykladnik dla nieujemnych wykładników całkowitych — bez użycia operatora ** ani pow(). Wypisz tabelę potęg dla podstawy 2 od 0 do 10.

Rozwiązanie
def potega(podstawa, wykladnik):
    # warunek bazowy: x^0 = 1
    if wykladnik == 0:
        return 1
    # wywołanie rekurencyjne: x^n = x * x^(n-1)
    return podstawa * potega(podstawa, wykladnik - 1)

# Tabela potęg dwójki
print(f"{'n':<5} {'2^n':>6}")
print("-" * 12)
for n in range(11):
    print(f"{n:<5} {potega(2, n):>6}")
Omówienie krok po kroku
  1. Warunek bazowy: wykladnik == 0
    Każda liczba podniesiona do potęgi 0 to 1. To punkt zatrzymania rekurencji.
  2. Wywołanie rekurencyjne
    podstawa * potega(podstawa, wykladnik - 1) — rozkładamy problem: 2^5 = 2 * 2^4 = 2 * 2 * 2^3 = ... aż dojdziemy do 2^0 = 1.
  3. Stos wywołań
    Dla potega(2, 3): wywołuje potega(2, 2)potega(2, 1)potega(2, 0) → zwraca 1. Następnie wyniki są mnożone wstecz: 1 → 2 → 4 → 8.
  4. Formatowanie tabeli
    :<5 wyrównuje do lewej, :>6 do prawej. Wynik wygląda jak profesjonalna tabela.

Zadania do samodzielnego rozwiązania

Pisz funkcje — nie wklejaj całego kodu w jedno miejsce. Każde zadanie to przynajmniej jedna funkcja z def.

1★☆☆

Pole figur

Napisz trzy funkcje: pole_kola(r), pole_prostokata(a, b), pole_trojkata(a, h). Każda zwraca pole zaokrąglone do 2 miejsc.

Wskazówka: import math, math.pi
2★☆☆

Funkcja powitalna

Napisz funkcję powitaj(imie, godzina) zwracającą "Dzień dobry", "Dobry wieczór" lub "Dobranoc" w zależności od godziny (0–23).

Wskazówka: warunki if/elif/else, return
3★★☆

Walidator hasła

Napisz funkcję sprawdz_haslo(haslo) zwracającą listę błędów. Hasło musi mieć min. 8 znaków, wielką literę, cyfrę i znak specjalny.

Wskazówka: any(), isupper(), isdigit()
4★★☆

Sortowanie własne

Napisz funkcję posortuj_wg_dlugosci(lista_slow) zwracającą listę słów posortowaną od najkrótszego do najdłuższego. Przy równej długości — alfabetycznie.

Wskazówka: sorted() z key=lambda x: (len(x), x)
5★★☆

Suma cyfr rekurencyjnie

Napisz rekurencyjną funkcję suma_cyfr(n) obliczającą sumę cyfr liczby całkowitej. Np. suma_cyfr(1234) → 10.

Wskazówka: n % 10 daje ostatnią cyfrę, n // 10 usuwa ostatnią
6★★★

Liczby doskonałe

Napisz funkcję czy_doskonala(n) — liczba doskonała to taka, której suma dzielników właściwych (bez niej samej) jest równa jej wartości. Wypisz wszystkie takie do 1000.

Wskazówka: pętla od 1 do n-1, sprawdź n % i == 0