📦 Wydzielanie części kodu

Znamy już zmienne (przechowują dane), instrukcje warunkowe (podejmują decyzje) oraz pętle (powtarzają kod). Ale co zrobić, gdy chcemy wykonać identyczny, skomplikowany kawałek kodu w zupełnie RÓŻNYCH miejscach naszego programu?

Skopiuj i wklej! To przecież proste.
🧑‍💻
To najgorsza możliwa praktyka! 😱
👩‍🏫

Wyobraź sobie, że piszesz grę i dodajesz w kodzie proces "robienia hamburgera" składający się z 10 linii kodu. Bohater robi hamburgera rano, po pracy i wieczorem. Kopiujesz ten kod w 3 miejsca. Nagle postanawiasz dodać do hamburgera plaster sera. Musisz teraz szukać i naprawiać to w 3 miejscach!

🛠️ Czym jest Funkcja?

Funkcja to nazwany, wyizolowany blok kodu, który wykonuje określone zadanie. Wystarczy zapisać go tylko raz u góry programu, a potem w nieskończoność wywoływać go na dole, po prostu podając jego nazwę!

Używaliśmy już gotowych, wbudowanych funkcji Pythona! Przypomnij sobie: print() (wypisz na ekran), len() (zlicz długość), type() (podaj typ).

# Użycie funkcji napisanej wcześniej przez inżynierów Pythona:
print("Używam funkcji!")

Teraz my sami staniemy się inżynierami i zbudujemy swoje własne maszyny.

🏗️ Definiowanie Funkcji (Słowo 'def')

Aby stworzyć nową funkcję, musimy użyć magicznego słowa def (od angielskiego define - definiuj). Następnie wymyślamy dla niej nazwę, dajemy pusty nawias () i kończymy potężnym dwukropkiem :.

def zrob_hamburgera():
    print("Podsmaż mięso")
    print("Podsmaż bułkę")
    print("Dodaj pomidora")
    print("Zjedz hamburgera")
Zauważ, że kod WEWNĄTRZ funkcji musi posiadać wcięcie (Tab)! Dokładnie tak samo jak ciało instrukcji warunkowej if, czy też zawartość pętli.

📞 Wywoływanie Funkcji

Samo napisanie def zrob_hamburgera(): nie spowoduje wypisania tekstu na ekranie! Samo "zdefiniowanie" przepisu to tak, jak zapisanie receptury w książce kucharskiej – ciasto nie upiecze się samo od leżenia na półce.

Aby wykonać kod w niej ukryty, musimy funkcję wywołać (ang. Call) poniżej w kodzie głównym programu.

# Scenariusz z życia gracza:
print("Wstań z łóżka")
zrob_hamburgera()    # Śniadanie!

print("Idź do pracy")
zrob_hamburgera()    # Obiad!

Aby wywołać funkcję, wpisujemy po prostu jej nazwę wraz z obowiązkowymi nawiasami (). (Bez nich Python pomyśli, że próbujesz się dostać do zmiennej!)

Dzięki temu zredukowaliśmy nasz kod na slajdzie z 10 długich linijek... do zaledwie czterech słów!

⚙️ Jak komputer przetwarza funkcje?

  1. Gdy interpreter Pythona widzi w pierwszej linijce słowo def, rejestruje sobie w pamięci nową komendę, po czym grzecznie przeskakuje w dół pomijając jej całe schowane (wcięte w prawo) ciało.
  2. W głównym kodzie nagle natrafia na rozkaz wywołania, np. zrob_hamburgera().
  3. Wtedy komputer jak na sprężynie teleportuje się z powrotem w górę do odnalezionej definicji tej funkcji.
  4. Wykonuje ciało funkcji linijka po linijce, a gdy to się kończy - wraca na dół dokładnie w miejsce, z którego został zawołany!
Ważne: Musisz najpierw zdefiniować funkcję w kodzie, zanim jej użyjesz (wywołasz). Python jest ślepy. Jeśli napiszesz print(a), a potem podasz a = 10 wyrzuci błąd. To samo dotyczy definicji. Zawsze najpierw buduj maszynę, a potem każ jej działać.

📥 Parametry (Wejście do maszyny)

Funkcja zrob_hamburgera() jest dość sztywna. Zawsze daje dokładnie to samo. Prawdziwa moc maszyn ujawnia się wtedy, gdy możemy z zewnątrz, podczas samego wołania - narzucić jej, dla kogo ma pracować, wrzucając jej zmienne przez szczelinę w dachu (czyli nawiasy!).

W definicji () umieszczamy Parametry. Są to po prostu puste pudełka (zmienne), do których funkcja przechwyci wrzucone potem wartości z zewnątrz.

# Budujemy funkcję z jednym pustym miejscem o nazwie 'name'
def hello(name):
    print("Hello " + name + "!")

# Wywołujemy funkcję wstrzykując przez rurę słowo "Marta"
hello("Marta")                       # Wypisze: Hello Marta!
hello("Tomek")                       # Wypisze: Hello Tomek!

🧐 Parametr a Argument (Różnice)

Wielu programistów stosuje te dwa pojęcia zamiennie niczym synonimy, ale warto poznać techniczną różnicę między nimi!

Parametr – To "nazwa pustego pudełka", którą wymyślasz u góry w definicji, między nawiasami przy słowie def. Oczekuje na coś.

Argument – To KONKRETNA informacja/wartość (np. "Tomek" albo liczba 50), którą przekazujesz funkcji, gdy już ją fizycznie WYWOŁUJESZ.
def strzel_z_luku(obrazenia):        <- 'obrazenia' to PARAMETR
    pass

strzel_z_luku(15)                       <- liczba 15 to ARGUMENT

👥 Wiele parametrów naraz (Kolejność!)

Funkcja nie musi ograniczać się do wsysania tylko jednej rzeczy. Możesz przekazać ich wiele oddzielając je od siebie zwykłym przecinkiem.

def print_two_items(a, b):
    print("Pierwszy element to:", a)
    print("Drugi element to:", b)

# Pamiętaj o zachowaniu idealnej kolejności! Złe zmapowanie psuje kod.
print_two_items("Pomidor", "Ser")
Argumenty takie nazywa się Argumentami Pozycyjnymi (Positional arguments). Pomidor idealnie po swojej pozycji wpada do pierwszego pudełka a, podczas gdy drugi z kolei Ser rzucany jest do b. Jeśli w złą stronę przekażesz współrzędne gracza (y, x) - spadnie on pod mapę!

🎭 Argumenty nazwane (Keyword arguments)

Co zrobić, gdy funkcja pożera aż 6 parametrów, a Ty nie masz pamięci do zapamiętania, w jakiej kolejności ktoś nakazał wpisywać siłę, manę, pancerz czy charyzmę? Z rozwiązaniem przychodzą w Pythonie parametry nazwane!

def ustaw_zdrowie(hp, mana, energia):
    print(hp, mana, energia)

# Skoro wymieniłem je konkretnie używając znaku równości, kolejność ginie!
ustaw_zdrowie(mana=200, hp=100, energia=5)
Nie tylko sprawia to, że jako programista ratujesz swój czas, ale kod staje się nagle niezwykle czytelny dla innych, bo dokładnie widzą jaką wartość pod jaką cechę ustawiasz wywołując kod po wielu miesiącach!

🎯 Ćwiczenie: Sklepikikarz z Parametrami

+200 XP

Pomyłka Barmana

Spójrz na poniższą funkcję. Spróbuj przewidzieć, co DOKŁADNIE wypiszą 3 polecenia na samym dole. Czy program zwariuje i dlaczego?

def zamowienie(napoj, rozmiar, cena):
    print(f"Podaję {rozmiar} {napoj}. Cena: {cena}")

# Wywołanie 1
zamowienie("Kawa", "Duża", 15)

# Wywołanie 2
zamowienie("Średnia", "Herbata", 10)

# Wywołanie 3
zamowienie(cena=20, napoj="Sok", rozmiar="Mały")

Zastanów się, co wyjdzie w wypowiedzi barmana przed przewinięciem do odpowiedzi!

✅ Rozwiązanie: Pomyłka Barmana

  1. zamowienie("Kawa", "Duża", 15)
    Wszystko na swoim miejscu. Kolejność idealna: napoj="Kawa", rozmiar="Duża".
    Wypisze: Podaję Duża Kawa. Cena: 15

  2. zamowienie("Średnia", "Herbata", 10)
    Pomylono kolejność pozycyjną! Pierwszy wyraz wciska się na sztywno do pudełka 'napoj', drugi do 'rozmiar'.
    Wypisze komicznie: Podaję Herbata Średnia. Cena: 10

  3. zamowienie(cena=20, napoj="Sok", rozmiar="Mały")
    Kolejność została totalnie przemieszana, ALE użyto Argumentów Nazwanych. To one decydują o locie danych do odpowiednich wnęk.
    Wypisze bezbłędnie: Podaję Mały Sok. Cena: 20

🛡️ Domyślne Wartości (Default Values)

Załóżmy, że nasza wbudowana do platformy funkcja logowania przyjmuje mnóstwo parametrów z danymi użytkownika. Większość z nich nie potrzebuje logowania tajnego. Czy gracz musi w kółko przy logowaniu się wpisywać `incognito=False` setki razy na serwerze?

Odpowiedź brzmi nie! W definicji na samej górze można zastosować tak zwaną Wartość Domyślną Parametru (podając ją już za znakiem równości w samym def!).

def open_website(url, incognito=False):
    if incognito:
        print(f"Opening {url} in incognito")
    else:
        print(f"Opening {url}")

⚔️ Przykład użycia Wartości Domyślnych

Kiedy mamy zaszytą "wspomagającą" wartość domyślną z poprzedniego slajdu, spójrzmy jak łatwe staje się pisanie kodu w dole i wywoływanie strony:

# Podałem 1 argument, a potrzebuje dwóch? Funkcja nie wywali błędu!
# Skorzysta z Domyślnego zdefiniowanego incognito=False
open_website("a.com")                  # Wypisze: Opening a.com

# Tym razem jako wyjątkowy przypadek NADPISAŁEM domyślne ustawienie!
open_website("b.com", True)             # Wypisze: Opening b.com in incognito

# Bardziej profesjonalne z czytelnymi Argumentami Nazwanymi:
open_website("c.com", incognito=True)   # Wypisze: Opening c.com in incognito
Wczuj się przez chwilę w osobę, która widzi rzucone open_website("b.com", True). Nie wie z czym ma do czynienia. Nazwanie ostatniego na incognito=True czyści mroki cudzego kodu.

📤 Wyjście z maszyny (Zwracanie Wartości)

Funkcje mogą przyjmować wartości z zewnątrz do pudła, wciągając je niczym odkurzacz. Ale na koniec ciężkiej pracy mogą też wyrzucać jeden, wielki gotowy Produkt jako odpowiedź dla innej części programu! Do tego używamy magicznego słowa return.

Instrukcja return pozwala wypluć wynik operacji wprost w miejsce w kodzie, z którego krzyczano o jej wywołanie. To tak jak wrzucenie monet (argumentu) do szczeliny automatu i wypadnięcie pełnej butelki po kilku zębatkach w dół.
def dodaj(a, b):
    wynik = a + b
    return wynik               # Wypadnij na zewnątrz i zanieś 15!

# Całe 'dodaj()' dosłownie podmienia się w locie w odpowiedź (15):
sum = dodaj(10, 5)

🧐 Print() kontra Return

To absolutnie najczęstszy problem wszystkich osób na początku przygody z IT. Różnica między słowem print() a return w definicji.

Print (Pokazywacz)
Jedyne, co robi to drukuje literki w oknie konsoli, by użytkownik miał co czytać. Komputer natychmiast ZAPOMINA o wypisanym wyniku i nie możesz wrzucić go sobie do innej zmiennej by z nim pracować.
Return (Dostarczyciel)
Nic nie drukuje i nie zanieczyszcza ekranu! Oddaje fizyczną, wirtualną "paczkę" dla wywoływacza, byś użył jej we własnym celu. To najbardziej profesjonalny sposób budowania funkcji!

🛑 Return jako Przycisk Stop (Short-Circuit)

Poza wręczeniem zmiennej światu na zewnątrz, słowo return kryje w sobie rolę bezwzględnego Awaryjnego Wyłącznika prądu.

def przyznaj_exp(poziom):
    if poziom > 100:
        return "Jesteś na max levelu!"     # TELEPORT NA ZEWNĄTRZ! STOP!

    print("Liczenie expa...")                   # Dla >100 to się w ogóle nie wydrukuje
    return "Nadano 50 punktów"
Gdy Python natrafi w locie na słowo return, po wypchnięciu przez okno wartości, absolutnie zapomina o reszcie instrukcji na dole. To wspaniały sposób na łapanie dziwnych akcji użytkowników (np. if gracz_martwy == True: return False), a by cała główna, wielka logika gry poniżej była bezpieczna. Tzw. "Wczesne zwracanie".

👻 A co, jeśli zapomnę Return? (Pustka None)

Gdy stworzysz zmienną (np. `wynik_bita`), musi ona przyjąć z prawej strony równania JAKĄŚ ostateczną wartość. Jeśli wywołana przez nią funkcja nie oddaje zupełnie niczego... co wleci do pudełka gracza?

def pokaz_ekran():
    print("Renderuję mapę...")
    # Koniec kodu. Gdzie moje return?!

wynik_funkcji = pokaz_ekran()
print(wynik_funkcji)                   # Wypisze: None
Jeśli Python nie znajdzie na samym dole Twojej procedury użytego polecenia return, a gracz zmusi ją do wywołania... uśmiechnie się i dyskretnie sam doklei tam podwodne "return None". Zwróci zatem graczowi czystą i pustą bezużyteczną wartość None (Pustkę).

🌍 Scope - Przestrzenie nazw (Global vs Local)

Stworzyłeś zmienną i nagle przestała istnieć gdy chciałeś jej użyć w drugim pliku? Czas poznać zjawisko Scope (zasięgu/przestrzeni życiowej).

def sekretny_plan():
    haslo = "Banan"
    print("Ustanowiono hasło wewnątrz.")

sekretny_plan()
print(haslo)                     # WYWALI GIGANTYCZNY BŁĄD! (NameError)
Każda zmienna stworzona WEWNĄTRZ jakiejś funkcji, to zmienna Lokalna (Tymczasowa). Istnieje tylko przez ułamek uderzenia serca podczas działania tego wycinka kodu. Gdy funkcja dociera do swojego `return`, cały jej obszar działania zostaje niszczony przez maszynę by oszczędzić Twoją pamięć RAM. Zmienna umiera.

🌌 Globalna Lornetka

Zmienne budowane wolnostojąco, o których uczyliśmy się w pierwszych rozdziałach jako o Pudełkach (leżące na głównym bloku kodu), są nazywane zmiennymi Globalnymi.

globalny_lvl = 10             # Leży na samym szczycie

def podglad_statystyk():
    print(globalny_lvl)          # Wypisze: 10! Lornetka zadziałała.

podglad_statystyk()
Funkcja ma wbudowaną "lornetkę". Jeśli zażądasz od niej jakiejś wartości, najpierw patrzy po zmiennych lokalnych w swoim biurku. Jak ich nie ma - wyciąga głowę z okna i patrzy na zewnątrz do poziomu globalnego (np. widzi `globalny_lvl`). UWAGA: O ile może je odczytać, Python zabrania jej modyfikowania tych globalnych cech przez nadpisywanie wewnątrz!

👻 Zjawisko Cienia (Shadowing)

To z pozoru bardzo częsty błąd polegający na "zasłanianiu słońca". Tworzymy niechcący Zmienną Lokalną o nazwie identycznej z Globalną...

imie = "Szef"

def zmien_imie():
    imie = "Uczeń"
    print("W środku:", imie)

zmien_imie()
print("Na zewnątrz:", imie)
Wynik Konsoli:
W środku: Uczeń
Na zewnątrz: Szef

Ktoś myślał, że w linijce `imie="Uczeń"` nadpisał górnego Bossa! Ale my wiemy lepiej z poprzedniego slajdu: funkcja nie może niszczyć pudła u góry. Więc dla bezpieczeństwa założyła tuż obok NOWE pudło, a starą na chwilę "zasłoniła cieniem".

🔓 Słowo kluczowe 'global' (The Dark Side)

Jeżeli NAPRAWDĘ, przysięgając na wszystkie świętości, zdajesz sobie sprawę z ryzyka... i wewnątrz swojej funkcji usilnie potrzebujesz uciąć, uszkodzić lub nadpisać główną zmienną Globalną ukrytą nad Tobą, używamy ostrzegawczego, zakazanego słowa: global.

punkty_glowne = 0

def wygrana_runda():
    global punkty_glowne           # OTWARCIE BRAMY! Może JĄ ZMIENIAĆ!
    punkty_glowne += 100

wygrana_runda()
print(punkty_glowne)                 # Wypisze dumnie: 100
Dlaczego unikamy go w profesjonalnym świecie jak ognia? W ogromnej grze posiadającej 10 tysięcy wywołań w kodzie, a błąd powoduje naliczanie się 9,999,999 złota gracza do portfela, nie wiesz KTÓRY z tysiąca modułów użył magii `global` z ukrycia.

🎯 Ćwiczenie: Złota Definicja Wyniku

+300 XP

Piszemy Pełną Funkcję (bez wydruków!)

Napisz w głowie strukturę krótkiej funkcji oblicz_kase, która przyjmie dwa parametry z łupów (monety, premia). Jeśli ktoś zapomni i 'premia' nie będzie w ogóle wpisana w argumentach podczas jej wywołania, niech awaryjnie wyląduje tam domyślnie jako `1`. Funkcja NIE MA tego drukować, ma w 100% zwrócić ci po cichutku wynik ich sumy.

# Twoja wspaniała definicja to:
def ???????:
    return ?????

# Wywołanie (ma zwrócić cyfrę 15 bez pomyłek!):
wynik_1 = oblicz_kase(10, 5)
wynik_2 = oblicz_kase(14)     # Próba testowania domyślnej wartości dla gap!

Zastanów się nad rozwiązaniem, zanim wejdziesz z satysfakcją na slajd wyżej!

✅ Rozwiązanie: Mistrz Kodowania

def oblicz_kase(monety, premia=1):
    return monety + premia

wynik_1 = oblicz_kase(10, 5)
print(wynik_1)               # Wydrukuje 15

wynik_2 = oblicz_kase(14)
print(wynik_2)               # Wydrukuje 15 (bo premia 1 odpaliła się jako fallback)
Czysto, zwięźle i powtarzalnie na całą platformę z góry na dół. Nie niszczymy globalnej przestrzeni, unikamy podawania tysiąca Printów dla użytkownika... To czysta esencja Architektury.

🧑‍🎨 Dobre Praktyki (Zasada KISS)

Teraz, gdy potrafisz tworzyć nowe Pudełka z logiką, przyswój sobie zasady, o jakich pouczy cię każdy zawodowy inżynier IT w firmie na projekcie.

🚀 Pudełka Zdarzeń - Ukończone

Pakując kod w mniejsze moduły (Funkcje), rozwiązaliśmy największy ból na platformach serwerowych — bałagan i błędy wtórne od kopiowania.


🎓 Architekt Kodowania Osiągnięto

Posiadasz teraz potężne, wirtualne fabryki, do których uczniowie (lub ty sam) mogą wrzucać zdefiniowane w nawiasie parametry-materiały, a w mikrosekundy odbierać z dołu gotowe złączone wartości!