Funkcje
Po co nam funkcje?
Programy, które będziemy pisać, z czasem zaczną robić się coraz większe, będą miały nieraz setki linii kodu albo nawet wiele plików, z których każdy będzie bardzo długi. Czasami będziemy też ten sam kod chcieli umieścić w wielu miejscach w programie - pisanie go wiele razy jest złym rozwiązaniem, bo jeśli znajdziemy w tym fragmencie kodu jakiś błąd, to będziemy musieli go poprawić w wielu miejscach i łatwo o kolejną pomyłkę. Konstrukcją, która rozwiązuje oba te problemy są funkcje.
Co to są funkcje?
Funkcje to nic innego jak nazwane kawałki kodu, które możemy wykonać z dowolnego miejsca w naszym programie. Funkcje mają dwie bardzo ważne cechy:
- przyjmują argumenty - funkcje nie mają dostępu do zmiennych zdefiniowanych poza nimi (czyli w szczególności w miejscu, w którym je wołamy), ale argumenty pozwalają nam przekazać odpowiednie wartości do funkcji jako nowe zmienne,
- zwracają wynik - funkcje rzadko będą modyfikowały zmienne zdefiniowane poza nimi (tzw. zmienne globalne), ale będzie nas interesował wynik działania funkcji, dlatego funkcje mogą zwracać dowolną wartość, którą potem przypiszemy na zmienną.
Powyższe funkcjonalności najłatwiej będzie zrozumieć na poniższych przykładach.
Przykłady funkcji
Do tej pory korzystaliśmy już w tym kursie z różnych funkcji, przyjrzyjmy się teraz trzem z nich bliżej.
-
Funkcja
print()
jest dostępna z dowolnego miejsca w naszych programach i przyjmuje jeden argument - napis, albo napis formatowany. Ta funkcja nie zwraca żadnej wartości, co możemy sprawdzić poniższym programem:ret = print("Test")
print(f"Wartość zwrócona przez print to: {ret}")Interpreter powinien wypisać nam:
Test
Wartość zwrócona przez print to: NoneNone
to nic innego jak po prostu brak wartości. -
Nieco bardziej interesująca jest funkcja
len()
, której mogliśmy używać do sprawdzenia długości listy lub napisu. Podobnie jak poprzednia funkcja, przyjmuje ona jeden argument - napis, listę, zbiór, słownik i inne, ale tym razem zwraca wartość - liczbę całkowitą oznaczającą długość (wielkość) podanego argumentu:napis: str = "Ala ma kota"
dlugosc: int = len(napis)
print(f"Wartość zwrócona przez len to: {dlugosc}")Jeśli wszystko zadziałało poprawnie, to interpreter powinien na ekranie wypisać długość napisu.
-
Ostatnim typem funkcji, który zostanie tu przedstawiony, są tak zwane funkcje klasowe, czyli takie związane z jakimś obiektem. Obiektem są na przykład lista, napis czy zbiór (więcej o programowaniu obiektowym i obiektach tutaj). Funkcje klasowe wymagają odpowiedniego obiektu, na którym wołana jest funkcja. Zobaczmy to na przykładzie sortowania listy:
lista: list[int] = [4, 2, 7, 1, 9]
ret = lista.sort()
print(f"Wartość zwrócona przez funkcję sort to: {ret}")
print(f"Po wywołaniu funkcji lista to: {lista}")Po wykonaniu tego programu widzimy, że funkcja klasowa
sort()
nie zwróciła żadnej nowej wartości, ale zmodyfikowała istniejącą listę. Zwróćmy uwagę na fakt, że funkcja klasowa jest wywoływana inaczej niż zwykłe funkcje - z racji tego, że jest związana z konkretnym obiektem (w pewnym sensie zdefiniowana wewnątrz tego obiektu).
Definiowanie własnych funkcji
Biblioteka standardowa Pythona ma zdefiniowanych bardzo wiele przydatnych
funkcji, ale trudno się spodziewać, że zawiera funkcję rozwiązujący każdy
problem, który pojawi się w programie. To budzi potrzebę definiowania własnych
funkcji, które będą dostosowane do naszych potrzeb. Możemy to zrobić przy
pomocy słowa kluczowego def
.
def suma(a: int, b: int) -> int :
return a + b
print(suma(12, 13)) # Wynik: 25
W powyższym przykładzie dzieje się wiele rzeczy, więc zacznijmy od deklaracji funkcji - deklaracja to inaczej nazwanie funkcji i określenie argumentów.
Deklaracja składa się z czterech części:
def
- słowo kluczowe rozpoczynające deklarację- nazwy funkcji wraz z nawiasami okrągłymi
- deklaracji argumentów w nawiasach okrągłych (podawanie typów jest oczywiście opcjonalne, ale wskazane)
- opcjonalnej deklaracji zwracanego typu
Lista argumentów funkcji (punkt 3.) może oczywiście być pusta, jeśli funkcja nie
przyjmuje żadnych argumentów. Funkcja nie musi też nic zwracać i wtedy jako
zwracany typ (punkt 4.) możemy podać wspomniany wcześniej typ None
.
Kolejnym ważnym elementem w powyższej funkcji jest instrukcja return
(ang.
zwróć) będąca częścią definicji funkcji (czyli jej kodu). Instrukcja ta
natychmiast kończy wykonanie funkcji i zwraca wyrażenie przy niej zapisane.
Wyrażeniem tym może być nazwa zmiennej, wyrażenie arytmetyczne lub logiczne albo
stała (np. 5
).
Poniżej znajdują się trzy równoważne definicje funkcji sprawdzającej parzystość liczby:
def czyParzysta1(x: int) -> bool:
if x % 2 == 0:
return True
else:
return False
def czyParzysta2(x: int) -> bool:
if x % 2 == 0:
return True
return False
def czyParzysta3(x: int) -> bool:
return x % 2 == 0
Zachęcam do zapoznania się z powyższymi przykładami i przeanalizowanie, dlaczego każdy z nich jest poprawny. Warto też dodać, że w tym przypadku preferowaną wersją byłaby wersja trzecia, ponieważ jest najkrótsza.
We wszystkich powyższych przykładach, funkcja zawsze zwracała tylko jedną lub
zero wartości i faktycznie tak jest - funkcja nie może zwracać więcej niż jednej
wartości. Jeżeli chcemy, żeby funkcja zwracała więcej niż jedną wartość, to
musimy zwrócić krotkę (tuple
), na przykład tak:
# Ta funkcja dostaje listę i wartość, którą chcemy znaleźć. Jeśli ją znajdziemy
# to zwracamy parę (indeks, wartość). Jeśli jej nie znajdziemy, to zwracamy
# parę (-1, wartość)
def findWithIndex(lista: list[int], wartosc: int) -> tuple[int, int]:
for (idx, el) in enumerate(lista):
if el == wartosc:
return (idx, wartosc)
return (-1, wartosc)
Jak dobrze dzielić kod na funkcje?
Umiejętny podział kodu na funkcje, jest trudny i jest czymś, czego programista uczy się całe życie. Z czasem nabiera się swego rodzaju wyczucia, co powinno być funkcją, a co lepiej zostawić w kodzie bez jej tworzenia. Na początku warto trzymać się kilku prostych zasad:
- Jeśli taka sama logika występuje w kilku miejscach, to powinniśmy napisać funkcję i wywołać ją w tych właśnie miejscach
- Jeśli nasz program staje się bardzo zagnieżdżony (ma 5-6 wcięć), to wewnętrzne części powinniśmy wydzielić jako funkcję
- Jeżeli funkcja lub treść głównego programu robi się bardzo długa (np. więcej niż 80-100 linii), to powinniśmy podzielić kod na funkcje
Przy pisaniu funkcji warto pamiętać o dwóch regułach:
- Nazwy funkcji powinny być znaczące i informować o tym, co dana funkcja robi
- Funkcje nie powinny być zbyt długie (max. 80-100 linii)