Last modified: June 06, 2026
This article is written in: 🇵🇱
Funkcje są blokami instrukcji zamkniętymi pod jedną nazwą i pozwalającymi na kontrolowanie z zewnątrz poprzez przekazywanie argumentów. Definicja funkcji polega na określeniu, które instrukcje należą do ciała funkcji, ile argumentów oczekuje funkcja oraz jaką nazwą będzie ona wywoływana w innych miejscach kodu. Definicja sama w sobie nie uruchamia jeszcze żadnych instrukcji - potrzebne jest użycie nazwy funkcji wraz z wartościami argumentów w innym miejscu kodu, aby instrukcje zostały wykonane.
W matematyce funkcja może wyglądać tak:
$$f(x) = x^2 + 1$$
Tutaj funkcja f przyjmuje argument x, wykonuje operację ( x^2 + 1 ) i zwraca wynik. W programowaniu funkcje działają na podobnej zasadzie, ale zamiast operacji matematycznych mogą wykonywać dowolny kod.
Główne powody używania funkcji w programowaniu to:
Funkcje mają następującą postać:
def nazwa_funkcji(argumenty):
kod # ciało funkcji
Ciało funkcji może być dowolnie rozbudowane, ale zaleca się dzielenie większych funkcji na mniejsze, które mają jasno określony cel. W ten sposób zmniejsza się złożoność kodu i ułatwia jego czytanie.
Zdefiniowaną funkcję wywołujemy w kodzie poprzez jej nazwę. Poniżej znajduje się podstawowy przykład wywołania funkcji.
# w tym miejscu definiujemy funkcję
def ryba():
print('rybka')
# w tym miejscu wywołujemy funkcję
ryba()
# wyświetli: rybka
Funkcje mogą mieć dowolną ilość argumentów - możliwe jest stworzenie funkcji bez argumentów lub funkcji z wieloma argumentami. Argumenty te pozwalają na przekazywanie danych do funkcji, które mogą być wykorzystywane w jej wnętrzu. Poniżej znajduje się przykład funkcji z jednym argumentem.
def ryba(argument):
# oczekujemy, że argument będzie liczbą naturalną
for i in range(argument):
print('ryba')
# wywołanie funkcji z argumentem 3
ryba(3)
# wyświetli:
# ryba
# ryba
# ryba
returnSłowo kluczowe return powoduje opuszczenie funkcji (instrukcje umieszczone poniżej nie są wykonywane). Return pozwala również na przekazanie wartości z wnętrza funkcji do reszty programu. Taka wartość po wywołaniu funkcji jest często zapisywana w zmiennej w innym miejscu programu. Na przykład:
def suma_trzech(a, b, c):
return a + b + c
suma_a = suma_trzech(3, 6, 2)
suma_b = suma_trzech(4, 1, 7)
print(suma_a) # wyświetli 11
print(suma_b) # wyświetli 12
Możemy również zdefiniować funkcję z domyślnymi argumentami, które zostaną użyte, jeśli nie zostaną przekazane żadne inne. Domyślne argumenty muszą być umieszczone po argumentach obowiązkowych. Przykład:
def suma_trzech(a, b, c=0):
return a + b + c
suma_a = suma_trzech(3, 6) # a + b + c = 3 + 6 + 0 = 9
suma_b = suma_trzech(4, 1, 7) # a + b + c = 4 + 1 + 7 = 12
print(suma_a) # wyświetli 9
print(suma_b) # wyświetli 12
Istnieje sposób na zdefiniowanie funkcji z nieograniczoną liczbą argumentów obowiązkowych, przy czym nie możemy ich użyć w połączeniu z argumentami domyślnymi. Przykład:
def suma_n(*args):
return sum(args)
suma_a = suma_n(1, 2, 3, 4) # 1 + 2 + 3 + 4 = 10
suma_b = suma_n(10, 20, 30) # 10 + 20 + 30 = 60
print(suma_a) # wyświetli 10
print(suma_b) # wyświetli 60
Argumenty nazwane, przekazywane w postaci słownika za pomocą **kwargs, pozwalają na elastyczne i czytelne wywoływanie funkcji, szczególnie gdy mamy do czynienia z dużą liczbą opcjonalnych parametrów. **kwargs jest używane do przekazywania zmiennej liczby argumentów nazwanych do funkcji. Umożliwia to tworzenie bardziej uniwersalnych i wielokrotnego użytku funkcji, które mogą przyjmować różne zestawy argumentów w zależności od potrzeb wywołania.
Przykład:
def zbuduj_adres(**kwargs):
adres = ""
if "ulica" in kwargs:
adres += kwargs["ulica"] + ", "
if "miasto" in kwargs:
adres += kwargs["miasto"] + ", "
if "kod_pocztowy" in kwargs:
adres += kwargs["kod_pocztowy"] + ", "
if "kraj" in kwargs:
adres += kwargs["kraj"]
return adres
# Przykład wywołania z pełnym zestawem argumentów
adres1 = zbuduj_adres(ulica="Kwiatowa 15", miasto="Warszawa", kod_pocztowy="00-001", kraj="Polska")
print(adres1) # wyświetli: Kwiatowa 15, Warszawa, 00-001, Polska
# Przykład wywołania z brakującym argumentem "kod_pocztowy"
adres2 = zbuduj_adres(ulica="Kwiatowa 15", miasto="Warszawa", kraj="Polska")
print(adres2) # wyświetli: Kwiatowa 15, Warszawa, Polska
# Przykład wywołania z brakującym argumentem "miasto" i "kod_pocztowy"
adres3 = zbuduj_adres(ulica="Kwiatowa 15", kraj="Polska")
print(adres3) # wyświetli: Kwiatowa 15, Polska
W powyższym przykładzie, funkcja zbuduj_adres przyjmuje dowolną liczbę argumentów nazwanych. Argumenty te są następnie używane do zbudowania pełnego adresu, ale tylko te, które zostały przekazane do funkcji. Dzięki **kwargs, funkcja jest bardzo elastyczna i może być używana z różnymi zestawami argumentów bez konieczności ich wcześniejszego definiowania.
Zalety używania **kwargs
| Typ argumentu | Składnia | Przykład wywołania | Opis |
| Pozycyjny obowiązkowy | def f(a, b) |
f(1, 2) |
Musi być podany w odpowiedniej kolejności |
| Domyślny (opcjonalny) | def f(a, b=10) |
f(1) lub f(1, 20) |
Użyje wartości domyślnej jeśli nie podano |
*args (pozycyjne zbiorcze) |
def f(*args) |
f(1, 2, 3) |
Krotka dowolnej liczby argumentów pozycyjnych |
**kwargs (nazwane zbiorcze) |
def f(**kwargs) |
f(x=1, y=2) |
Słownik dowolnej liczby argumentów nazwanych |
Wyłącznie pozycyjne (/) |
def f(a, b, /) |
f(1, 2) |
Nie można podać nazwy argumentu |
Wyłącznie nazwane (*) |
def f(*, a, b) |
f(a=1, b=2) |
Muszą być przekazane z nazwą |
| Mieszane | def f(a, /, b, *, c) |
f(1, 2, c=3) lub f(1, b=2, c=3) |
Łączenie różnych typów |
Dokumentowanie funkcji jest kluczowe dla utrzymania przejrzystości i zrozumienia kodu. Python pozwala na dodawanie docstringów, które są specjalnymi komentarzami umieszczonymi bezpośrednio pod definicją funkcji. Przykład:
def oblicz_pole_prostokata(dlugosc, szerokosc):
"""
Funkcja oblicza pole prostokąta.
Args:
dlugosc (float): Długość prostokąta.
szerokosc (float): Szerokość prostokąta.
Returns:
float: Pole prostokąta.
"""
return dlugosc * szerokosc
pole = oblicz_pole_prostokata(5.0, 3.5)
print(pole) # wyświetli: 17.5
Zasięg zmiennej określa, gdzie w kodzie dana zmienna jest widoczna i dostępna. Python stosuje reguły zasięgu opisywane skrótem LEGB (Local → Enclosing → Global → Built-in):
len, print, range).Python przeszukuje te poziomy dokładnie w tej kolejności.
Zmienne utworzone wewnątrz funkcji są lokalne — widoczne tylko w tej funkcji i niszczone po jej zakończeniu:
def powitaj():
wiadomosc = "Cześć!" # zmienna lokalna
print(wiadomosc)
powitaj() # Cześć!
# print(wiadomosc) # NameError — zmienna nie istnieje poza funkcją
Zmienne zdefiniowane na poziomie modułu są globalne — widoczne wewnątrz funkcji, ale domyślnie tylko do odczytu:
licznik = 0 # zmienna globalna
def pokaz_licznik():
print(licznik) # można czytać
pokaz_licznik() # 0
Aby modyfikować zmienną globalną wewnątrz funkcji, należy użyć słowa kluczowego global:
licznik = 0
def zwieksz():
global licznik
licznik += 1
zwieksz()
zwieksz()
print(licznik) # 2
Nadużywanie zmiennych globalnych utrudnia zrozumienie i testowanie kodu. Preferuj przekazywanie wartości przez argumenty i zwracanie przez
return.
Funkcja zdefiniowana wewnątrz innej funkcji ma dostęp do zmiennych funkcji zewnętrznej (zasięg enclosing):
def zewnetrzna():
x = 10 # zasięg enclosing dla wewnetrzna()
def wewnetrzna():
print(x) # dostęp do x z funkcji zewnętrznej
wewnetrzna()
zewnetrzna() # 10
Aby modyfikować zmienną z zasięgu enclosing, używamy słowa kluczowego nonlocal:
def licznik():
wartosc = 0
def zwieksz():
nonlocal wartosc
wartosc += 1
return wartosc
return zwieksz
krok = licznik()
print(krok()) # 1
print(krok()) # 2
print(krok()) # 3
Domknięcie (ang. closure) to funkcja wewnętrzna, która "zapamiętuje" zmienne z zasięgu funkcji zewnętrznej nawet po jej zakończeniu. Powyższy przykład z licznik() jest właśnie domknięciem — zwieksz nadal ma dostęp do wartosc, mimo że licznik() już zakończyła działanie.
Domknięcia są przydatne do tworzenia fabryk funkcji:
def mnoznik(n):
def pomnoz(x):
return x * n # n pochodzi z zasięgu enclosing
return pomnoz
podwoj = mnoznik(2)
potroil = mnoznik(3)
print(podwoj(5)) # 10
print(potroil(5)) # 15
Python jest dynamicznie typowany, ale od wersji 3.5 wspiera opcjonalne adnotacje typów, które zwiększają czytelność kodu i pozwalają narzędziom (np. mypy, pyright) wykrywać błędy:
def dodaj(a: int, b: int) -> int:
return a + b
def powiedz_czesc(imie: str, razy: int = 1) -> None:
for _ in range(razy):
print(f"Cześć, {imie}!")
def przetworz(dane: list[int]) -> dict[str, int]:
return {"suma": sum(dane), "max": max(dane), "min": min(dane)}
Adnotacje nie są egzekwowane w czasie wykonania — są jedynie wskazówką dla programisty i narzędzi. Dostęp do nich można uzyskać przez __annotations__:
print(dodaj.__annotations__)
# {'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}
Moduł typing dostarcza dodatkowych typów:
from typing import Optional, Union, Callable, Tuple
def znajdz(lista: list[int], wartosc: int) -> Optional[int]:
"""Zwraca indeks lub None."""
try:
return lista.index(wartosc)
except ValueError:
return None
def zastosuj(f: Callable[[int], int], x: int) -> int:
return f(x)
# Python 3.10+ — skrócona składnia Union za pomocą |
def parsuj(wartosc: str | int) -> int:
return int(wartosc)
/) i wyłącznie nazwane (*)Python 3.8+ pozwala precyzyjnie kontrolować, jak argumenty muszą być przekazywane:
# Argumenty PRZED / → wyłącznie pozycyjne (nie można podać nazwy)
# Argumenty PO * → wyłącznie nazwane (muszą być podane z nazwą)
def funkcja(poz1, poz2, /, normalny, *, kw1, kw2):
pass
# Poprawne wywołania:
funkcja(1, 2, 3, kw1=4, kw2=5)
funkcja(1, 2, normalny=3, kw1=4, kw2=5)
# Niepoprawne — błąd:
# funkcja(poz1=1, poz2=2, normalny=3, kw1=4, kw2=5) # poz1/poz2 muszą być pozycyjne
# funkcja(1, 2, 3, 4, 5) # kw1/kw2 muszą być nazwane
Praktyczny przykład:
def oblicz_sile(masa: float, przyspieszenie: float, /) -> float:
"""F = ma. Argumenty wyłącznie pozycyjne — kolejność ma znaczenie."""
return masa * przyspieszenie
def nasluchuj(host: str, *, port: int, timeout: float = 30.0) -> None:
"""port jest wyłącznie nazwany — musi być podany explicite."""
print(f"Słucham na {host}:{port} (timeout={timeout}s)")
nasluchuj("localhost", port=8080)
nasluchuj("0.0.0.0", port=443, timeout=60.0)