Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Funkcje z parametrami i wartościami zwracanymi

Cześć! W poprzedniej lekcji poznaliśmy listy, słowniki, zbiory i krotki — struktury, w których będziesz przechowywać dane testowe, odpowiedzi API i oczekiwane rezultaty. Teraz zrobimy kolejny ważny krok: zamiast powielać operacje na tych danych w wielu miejscach, zamkniemy je w funkcjach.

Funkcja to nazwany fragment kodu, który można uruchamiać wielokrotnie, przekazując mu różne dane. W automatyzacji testów funkcje są podstawą utrzymywalności: mogą np. budować dane użytkownika, sprawdzać fragment odpowiedzi API lub przygotowywać powtarzalny stan testowy.

Po tej lekcji będziesz umieć definiować funkcje, przekazywać do nich argumenty pozycyjne, nazwane i domyślne oraz zwracać wyniki za pomocą return.


Funkcja: definicja nie jest jeszcze wykonaniem

Najprostsza funkcja w Pythonie ma:

  1. słowo kluczowe def,
  2. nazwę,
  3. nawiasy z parametrami,
  4. dwukropek,
  5. wcięte ciało.
def show_test_started():
    print("Test rozpoczęty")

Ta instrukcja definiuje funkcję, ale jeszcze jej nie uruchamia. Aby kod w środku został wykonany, trzeba funkcję wywołać:

show_test_started()

Wynik:

Test rozpoczęty
Schemat pokazuje elementy definicji funkcji: słowo `def`, nazwę, argumenty w nawiasach oraz wcięte ciało funkcji.

W Pythonie wcięcie jest częścią składni. Wszystko, co należy do funkcji, musi być przesunięte o jednakowy poziom, najczęściej cztery spacje:

def show_test_started():
    print("Test rozpoczęty")
    print("Przygotowuję dane")

Natomiast ten zapis jest błędny, bo ciało funkcji nie ma wcięcia:

# Błędnie
def show_test_started():
print("Test rozpoczęty")

Nazwy funkcji pisz zwykle małymi literami, używając podkreśleń między wyrazami — jest to konwencja snake_case.

def create_test_user():
    print("Tworzę użytkownika testowego")

Dobra nazwa opisuje czynność: create_test_user, build_payload, check_status_code. W testach szczególnie warto unikać nazw ogólnych, takich jak process_data() czy do_test().

Obejrzyj początkową część materiału, aby zobaczyć definicję i wywołanie funkcji w działającym programie.

9. Kurs Python 3 - funkcje

Materiał „9. Kurs Python 3 – funkcje” z kanału Kanał o Wszystkim pokazuje podstawową składnię funkcji i sposób przekazywania danych do jej wnętrza.

W części definicja funkcji zwróć uwagę na rolę def, nawiasów, dwukropka i wcięcia oraz na to, dlaczego definicja nie uruchamia kodu. Następnie obejrzyj parametry funkcji, obserwując, jak wartości podane przy wywołaniu stają się dostępne wewnątrz ciała funkcji.


Parametry i argumenty: dwa podobne, lecz różne pojęcia

Rozróżnienie jest proste, ale bardzo przydatne podczas czytania błędów i dokumentacji:

  • parametr to nazwa w definicji funkcji;
  • argument to konkretna wartość przekazana przy jej wywołaniu.
def greet_user(name):
    print(f"Cześć, {name}!")

W tym przykładzie name jest parametrem. W wywołaniu:

greet_user("Ala")

napis "Ala" jest argumentem.

Przykład rozróżnia parametry `how` i `who` zapisane w definicji funkcji od argumentów „Cześć” i „czytelniku” podanych podczas wywołania.

Parametr działa wewnątrz funkcji jak lokalna zmienna:

def show_status(status_code):
    print(f"Otrzymany kod: {status_code}")

show_status(201)
show_status(404)

Ta sama funkcja wykonuje ten sam rodzaj pracy, ale na różnych danych. To właśnie daje jej wartość: nie kopiujesz logiki dla każdego przypadku.

W automatyzacji testów może to wyglądać tak:

def show_response_summary(status_code, endpoint):
    print(f"Endpoint: {endpoint}")
    print(f"Status: {status_code}")

show_response_summary(201, "/users")
show_response_summary(404, "/users/999")

Argumenty pozycyjne: kolejność ma znaczenie

Gdy przekazujesz argumenty bez nazw, Python przypisuje je według kolejności parametrów w definicji. Są to argumenty pozycyjne.

def build_user(email, role):
    return {
        "email": email,
        "role": role,
    }

user = build_user("ania@example.test", "viewer")

Python przypisuje tu wartości następująco:

email = "ania@example.test"
role = "viewer"

Jeżeli odwrócisz argumenty, funkcja nadal formalnie się wykona, ale dane będą semantycznie błędne:

# Kod zadziała, ale wartości trafią do niewłaściwych pól.
user = build_user("viewer", "ania@example.test")

To jeden z powodów, dla których warto dobierać kolejność parametrów świadomie. Najpierw zwykle umieszcza się dane obowiązkowe i najważniejsze.

Brak wymaganego argumentu powoduje TypeError:

# build_user("ania@example.test")
# TypeError: build_user() missing 1 required positional argument: 'role'

Nie jest to błąd testowanej aplikacji. To błąd w sposobie wywołania własnej funkcji: jej definicja wymagała dwóch danych, a przekazano jedną.

Dla funkcji o jednym lub dwóch bardzo oczywistych argumentach zapis pozycyjny jest zwięzły i czytelny:

assert_response_status(201, 201)

Przy większej liczbie argumentów lub wartościach tego samego typu łatwo jednak pomylić kolejność. Wtedy użyj argumentów nazwanych.


Argumenty nazwane: kod, który sam wyjaśnia dane

Argument nazwany podajesz w formie nazwa_parametru=wartość.

def build_user(email, role, active):
    return {
        "email": email,
        "role": role,
        "active": active,
    }

user = build_user(
    email="ania@example.test",
    role="viewer",
    active=True,
)

Ten zapis jest nieco dłuższy, ale wyraźnie pokazuje znaczenie każdej wartości. Możesz też zmienić kolejność argumentów nazwanych:

user = build_user(
    active=True,
    email="ania@example.test",
    role="viewer",
)

Rezultat będzie taki sam, ponieważ Python dopasowuje wartości po nazwach, a nie po miejscach.

Argumenty nazwane są szczególnie przydatne w kodzie testowym, gdy funkcja przyjmuje kilka parametrów albo kiedy wartości są trudne do odróżnienia na pierwszy rzut oka:

def build_request_options(timeout_seconds, follow_redirects, verify_ssl):
    return {
        "timeout_seconds": timeout_seconds,
        "follow_redirects": follow_redirects,
        "verify_ssl": verify_ssl,
    }

options = build_request_options(
    timeout_seconds=10,
    follow_redirects=False,
    verify_ssl=True,
)

Porównaj to z wersją pozycyjną:

options = build_request_options(10, False, True)

Druga wersja jest poprawna, ale po kilku dniach trudno pamiętać, co dokładnie oznaczają False i True.

Łączenie argumentów pozycyjnych i nazwanych

Python pozwala użyć obu stylów w jednym wywołaniu:

def create_account(email, role, active):
    return {
        "email": email,
        "role": role,
        "active": active,
    }

account = create_account(
    "ania@example.test",
    role="viewer",
    active=True,
)

Obowiązuje jednak ważna reguła:

Argumenty pozycyjne muszą wystąpić przed nazwanymi.

# Błędnie:
# create_account(email="ania@example.test", "viewer", True)

Po rozpoczęciu przekazywania argumentów po nazwie podawaj już kolejne również po nazwie. Dzięki temu wywołanie pozostaje jednoznaczne.


Parametry domyślne: rozsądny standard, możliwość zmiany

Nie każdy parametr musi być wymagany. Parametr domyślny ma wartość zapisaną już w definicji funkcji. Gdy wywołanie nie poda argumentu, Python użyje tej wartości.

def build_user(email, role="viewer"):
    return {
        "email": email,
        "role": role,
    }

Teraz możesz wywołać funkcję na dwa sposoby:

basic_user = build_user("ania@example.test")

admin_user = build_user(
    "piotr@example.test",
    role="admin",
)

W pierwszym przypadku role przyjmuje domyślnie "viewer". W drugim wartość domyślna jest zastąpiona przez "admin".

To dobrze pasuje do automatyzacji. Jeżeli większość danych testowych opisuje aktywnego użytkownika, możesz zapisać tę regułę w funkcji:

def build_user(email, role="viewer", active=True):
    return {
        "email": email,
        "role": role,
        "active": active,
    }

regular_user = build_user("ania@example.test")

blocked_admin = build_user(
    "admin@example.test",
    role="admin",
    active=False,
)

Parametry domyślne powinny znajdować się po parametrach wymaganych:

# Poprawnie
def build_user(email, role="viewer"):
    return {"email": email, "role": role}
# Błędnie — Python nie wie, jak interpretować wywołanie z jednym argumentem.
# def build_user(role="viewer", email):
#     return {"email": email, "role": role}

Gdyby taki zapis był dozwolony, build_user("ania@example.test") nie mówiłoby jasno, czy przekazana wartość ma trafić do role, czy do email.

Poniższy fragment materiału utrwala parametry domyślne oraz przejście do zwracania wartości.

9. Kurs Python 3 - funkcje

Kontynuuj materiał „9. Kurs Python 3 – funkcje”, aby zobaczyć parametry opcjonalne i praktyczną różnicę między print() a return.

Obejrzyj wartości domyślne, zwłaszcza zasadę, że parametry z domyślną wartością stoją na końcu definicji. Następnie przejdź do return w praktyce; skup się na tym, jak wynik funkcji można przypisać do zmiennej i dlaczego instrukcje po return nie zostaną wykonane.


print() a return: pokazanie wyniku to nie to samo co jego zwrócenie

To jedno z najważniejszych rozróżnień w Pythonie.

Funkcja z print() wyświetla informację człowiekowi:

def print_total(items_count):
    print(f"Liczba elementów: {items_count}")

result = print_total(3)

print(result)

Wynik będzie następujący:

Liczba elementów: 3
None

Dlaczego result zawiera None? Ponieważ funkcja niczego nie zwróciła. print() jedynie wyświetlił tekst w konsoli.

Jeśli rezultat ma zostać wykorzystany w dalszym kodzie — zapisany, porównany asercją, przekazany do innej funkcji — użyj return:

def calculate_total(price, quantity):
    return price * quantity

total = calculate_total(19.99, 3)

print(total)
# 59.97

Teraz total zawiera liczbę, z którą można dalej pracować:

assert calculate_total(19.99, 3) == 59.97

W testach z reguły potrzebujesz właśnie wartości zwracanych, a nie funkcji, które tylko coś wypisują. Asercja porównuje dane, nie komunikaty w terminalu.

return kończy wykonywanie funkcji

Po wykonaniu return funkcja natychmiast kończy działanie:

def get_access_label(is_active):
    if is_active:
        return "access granted"

    return "access denied"

Dla True zostanie zwrócone "access granted" i druga instrukcja return nie zostanie już osiągnięta. Dla False warunek zostanie pominięty, a funkcja zwróci "access denied".

Kod zapisany po bezwarunkowym return jest nieosiągalny:

def get_status():
    return "ok"
    print("Ten tekst nigdy się nie wyświetli")

W prostych funkcjach return zwykle znajduje się na końcu. W funkcjach z warunkami może wystąpić w kilku gałęziach, ale każda możliwa ścieżka powinna mieć jasny rezultat.


Budujemy użyteczną funkcję dla danych testowych

Połączmy elementy poznane dzisiaj oraz struktury danych z poprzedniej lekcji. Chcemy tworzyć dane użytkownika do żądania API.

def build_user_payload(email, role="viewer", active=True):
    return {
        "email": email,
        "role": role,
        "active": active,
    }

Definicja mówi, że:

  • email jest wymagany;
  • role jest opcjonalny i domyślnie ma wartość "viewer";
  • active jest opcjonalny i domyślnie ma wartość True;
  • funkcja zwraca słownik, który później będzie można przekazać do żądania HTTP.

Możemy stworzyć kilka wariantów danych bez kopiowania całego słownika:

new_viewer = build_user_payload("ania@example.test")

new_admin = build_user_payload(
    email="piotr@example.test",
    role="admin",
)

inactive_user = build_user_payload(
    email="ola@example.test",
    active=False,
)

Po wywołaniu new_admin będzie mieć wartość:

{
    "email": "piotr@example.test",
    "role": "admin",
    "active": True,
}

Najważniejsze jest to, że funkcja nie wysyła jeszcze żadnego żądania API. Jej pojedyncza odpowiedzialność to przygotowanie danych. W przyszłych modułach wykorzystasz takie funkcje jako małe, przewidywalne elementy kodu testowego.

Możesz od razu sprawdzać jej wynik zwykłą asercją:

payload = build_user_payload(
    email="tester@example.test",
    role="editor",
)

assert payload["email"] == "tester@example.test"
assert payload["role"] == "editor"
assert payload["active"] is True

To dobry wzorzec myślenia: funkcja przyjmuje dane wejściowe, zwraca konkretny wynik, a test porównuje wynik z oczekiwaniem.


Typowe błędy i jak je rozpoznać

Sytuacja Przyczyna Poprawa
TypeError: missing ... required positional argument Brakuje wymaganego argumentu. Podaj wartość albo zdefiniuj uzasadnioną wartość domyślną.
Wynik funkcji to None Funkcja używa print(), ale nie ma return. Zwróć potrzebną wartość przez return.
SyntaxError: non-default argument follows default argument Parametr wymagany został zapisany po domyślnym. Najpierw parametry wymagane, potem domyślne.
Dane trafiają do niewłaściwych pól Pomyłka w kolejności argumentów pozycyjnych. Użyj argumentów nazwanych.
Kod funkcji wykonuje się od razu po def To błędne założenie — definicja tylko tworzy funkcję. Wywołaj ją przez nazwa_funkcji(...).
IndentationError Ciało funkcji ma nieprawidłowe wcięcie. Wcięcie całego ciała ustaw konsekwentnie na cztery spacje.

W trakcie nauki warto często uruchamiać małe fragmenty kodu w swoim projekcie z .venv. Błędy Pythona bywają bardzo konkretne: przeczytaj komunikat od końca i sprawdź nazwę funkcji oraz liczbę podanych argumentów.


Zasady, które warto stosować od teraz

Dobra funkcja pomocnicza w testach zwykle:

  • ma nazwę opisującą działanie;
  • wykonuje jedną, spójną rzecz;
  • przyjmuje tylko dane, których rzeczywiście potrzebuje;
  • używa wartości domyślnych wyłącznie dla sensownych standardów;
  • zwraca dane, jeśli dalszy kod ma je sprawdzić lub przetworzyć;
  • nie polega na ukrytej kolejności wielu podobnych argumentów — w takim przypadku korzysta z argumentów nazwanych.

Krótka ściąga składni:

def function_name(required_value, optional_value="default"):
    result = required_value + optional_value
    return result


value = function_name("A", optional_value="B")

W definicji required_value i optional_value są parametrami. W wywołaniu "A" oraz "B" są argumentami. Argument "A" przekazano pozycyjnie, a "B" nazwano wyraźnie przez optional_value=.


Najważniejsze wnioski:

  • Funkcję definiujesz przez def, nazwę, nawiasy, dwukropek i wcięte ciało.
  • Parametry są zapisane w definicji, a argumenty przekazujesz przy wywołaniu.
  • Argumenty pozycyjne zależą od kolejności; argumenty nazwane są dopasowywane po nazwie i zwiększają czytelność.
  • Parametry domyślne czynią argument opcjonalnym, ale muszą występować po wymaganych parametrach.
  • print() pokazuje wartość, natomiast return oddaje ją kodowi wywołującemu.
  • Instrukcja return kończy wykonywanie funkcji.
  • Funkcje zwracające dane są szczególnie użyteczne w automatyzacji, ponieważ ich wyniki można bezpośrednio sprawdzać asercjami.

W następnej lekcji dodasz do funkcji adnotacje typów. Dzięki nim podpis funkcji będzie informował nie tylko o nazwach parametrów, lecz także o oczekiwanych typach danych i typie zwracanego wyniku.

Can't find a good explanation? Sign up and we'll make it for you

Sign up