Kontynuujemy naukę Pythona. Tym razem na naszej liście jest zagadnienie funkcji, które trzeba poznać, jeżeli na poważnie się myśli o programowaniu.
Funkcje to zdefiniowane bloki kodu, które można wywołać (użyć ich nazwy i nawiasów), często podając co nich jakiś argument albo argumenty i otrzymując pewien wynik.
Funkcje wbudowane
Poznaliśmy już kilka wbudowanych funkcji. Oto kilka przykładów:
print("Hello World")
#Hello World
print(abs(-3)) #3
print(max([3,2,1,10])) #10
print(min([3,2,1,10])) #1
print(sum([3,2,1,10])) #16
Funkcja print wypisuje podany napis. Abs – daje liczbę pozbawioną znaku -, jeżeli jest ona ujemna. Max to maksimum, min to minimum, sum to suma czyli dodanie do siebie wszystkich elementów.
Funkcje wbudowane poznaje się z czasem i ich uczy, można też zawsze sprawdzić w dokumentacji jak ta czy inna funkcja działa. Np. funkcja len() zwraca nam długość napisu/ilość elementów w liście a dla takich typów jak liczba int lub liczba float (liczba zmiennoprzecinkowa) jest niedostępna.
Metody wbudowane na obiektach
Metody są bardzo podobne do funkcji i choć zapewne już kilka poznaliśmy, nie odróżniamy ich od funkcji. Cóż, metody to te funkcje, które wywołujemy na obiekcie, jakimś typie danych, odnosząc się do nich „po kropce”.
Oto kilka funkcji, które powinniśmy pamiętać z poprzednich tutoriali:
print("anna".upper())
#ANNA
print("ANNA".lower())
#anna
print("aNNA".swapcase())
#Anna
print("anna".capitalize())
#Anna
Napis jest obiektem, zaś po kropce odnosimy się do jego metod. Upper to wielka litera, lower to mała, swapcase to zamiana wielkich na małe i vice versa zaś capitalize – podnieś do wielkiej pierwszą. Poza takimi funkcjami mamy też takie, które zwracają prawdę/fałsz:
print("1".isdigit()) #True
print("a".isalpha()) #True
print("a".islower()) #True
print("a".isupper()) #False
print("a".isalnum()) #True
print("1".isalnum()) #True
print(" ".isspace()) #True
Isdigit sprawdza, czy znak jest liczbą, isalpha czy jest literą, islower – czy jest małą literą, isupper – czy jest wielką literą, isalnum – czy jest literą bądź cyfrą, isspace – czy znak jest spacją.
Takie funkcje można użyć w bloku if. Sprawdźmy czy „1” jest cyfrą:
if "1".isdigit():
print("Hello World")
#Hello World
Jak widać jest, a zatem blok kodu nam się wykona i mamy Hello World. Mamy także metody, które zwracają nam zmienioną wersję czegoś. Taki oto przykład:
msg = "Hello World"
msg2 = msg.replace(" ", "|")
print(msg2)
#Hello|World
Metoda replace zamienia nam tutaj spację na inny znak i taki, zmieniony obiekt nam zwraca.
Są też metody „in-place” czyli takie, które zmieniają nam oryginalny obiekt.
lst = [1,2,3]
print(lst.pop()) #3
print(lst) #[1, 2]
lst.append(4)
print(lst) #[1, 2, 4]
lst.append(-1)
print(lst)
#[1, 2, 4, -1]
lst.sort()
print(lst)
#[-1, 1, 2, 4]
Pop usuwa ostatni element z listy i jednocześnie go zwraca. Append dodaje element na końcu listy. Sort – sortuje listę. Są to metody in-place czyli zmieniające oryginalny obiekt. Przykładem czegoś, co nie jest in-place może być funkcja sorted, która zwraca posortowaną kopię listy, nie modyfikując oryginalnej:
lst = [5,4,3,2,1]
print(sorted(lst))
#[1, 2, 3, 4, 5]
print(lst)
#[5, 4, 3, 2, 1]
Metody (sorted metodą nie jest, ponieważ nie wykonujemy jej na obiekcie po kropce, tutaj występuje jedynie w charakterze przykładu czegoś, co nie jest in-place, nie modyfikuje oryginalnej struktury danych) poznajemy tak samo jak funkcje i nie ma tu specjalnie co robić jakiejś listy wszystkich funkcji i metod. To się z czasem poznaje.
Metody ukryte
Z obowiązku czy w ramach jakiejś ciekawostki można o nich opowiedzieć. Są to metody w pewien sposób przed nami ukryte i poznamy je, jak się zaznajomimy z klasami. Tych metod zazwyczaj albo w ogóle nie wywołujemy albo wywołujemy je jakimś operatorem, zaś „pod spodem” Python się do nich odnosi.
Oto kilka przykładów:
print(2+2)
#4
print(int.__add__(2,2))
#4
print(2-2)
#0
print(int.__sub__(2,2))
0
I tak jak dodajemy do siebie dwa typy liczbowe (czyli int) przy użyciu znaku plusa, to Python gdzieś tam pod spodem odnosi się do typu int i jego metody __add__, która przyjmuje dwa argumenty i zwraca wynik. Podaję to raczej jako ciekawostkę i pewien wstęp do tematów, które będę opisywać w przyszłości.
Funkcje printujące
Najbardziej bezsensowny rodzaj funkcji, który jednak zawsze się na początku poznaje. Własne funkcje wypisujące jakiś tekst na ekranie. Bez sensu, bo niczego nie zwracają zaś od wypisywania na ekranie mamy funkcję print, ale przynajmniej obrazują jak się funkcję tworzy i jak się ją wywołuje:
def hello_world():
print("Hello World")
hello_world()
#Hello World
Słówko kluczowe def, nazwa funkcji (bez spacji), nawias, dwukropek, wcięcie z blokiem kodu – definicja funkcji. Nazwa funkcji z nawiasami – wywołanie funkcji.
Bez sensu, bowiem nic nie zwracają, nie da się ich zapisać do zmiennej:
def hello_world():
print("Hello World")
msg = hello_world()
#Hello World
print(msg)
#None
W momencie nieudanej próby przypisania do zmiennej msg odpala nam się print Hello World zaś próbując wypisywać msg otrzymujemy None, ponieważ ta funkcja nic nie zwraca.
Funkcje zwracające
Funkcje, które coś zwracają. Służy do tego słówko kluczowe return. Oto przykład funkcji zwracającej jakąś wartość:
def hello_world():
return "Hello World"
msg = hello_world()
print(msg)
#Hello World
Tutaj zwrócony zostaje napis Hello World, który możemy przypisać do zmiennej i wypisać. Albo od razu wypisać:
def hello_world():
return "Hello World"
print(hello_world())
#Hello World
Z argumentem/argumentami
Funkcje, które przyjmują jakiś argument. Na przykład:
def hello_name(name):
return f"Hello {name}"
print(hello_name("John"))
#Hello John
print(hello_name("Jane"))
#Hello Jane
Tutaj przyjmujemy argument name i zwracamy wartość z tym imieniem. Inny przykład, funkcji która akurat nic nie zwraca, ale argument posiada:
def hello_world(times):
for _ in range(times):
print("Hello World")
hello_world(3)
#Hello World
#Hello World
#Hello World
Ta funkcja wypisuje Hello World tyle razy, ile jej każemy. To oznacza ten dziwny może zapis for _ in range(times) – czyli „zrób to tyle razy”.
Inny przykład funkcji, która posiada aż dwa argumenty i coś z nimi robi. Nie będę szczególnie odkrywczy, będzie to zwykłe dodawanie:
def sum(a, b):
return a + b
print(sum(1,10)) #11
Z argumentem domyślnym
Argument może być domyślny, to znaczy jeżeli go nie podamy, zostanie zastosowana domyślna, określona przez nas wartość. Ta funkcja, jeżeli nie podamy jej ile razy ma się wykonać, wykona się trzy razy:
def hello_world(times=3):
for _ in range(times):
print("Hello World")
hello_world()
#Hello World
#Hello World
#Hello World
Ta funkcja, jeżeli jej nie podamy imienia, wypisze Hello Stranger czyli witaj nieznajomy:
def hello_name(name="Stranger"):
return f"Hello {name}"
print(hello_name("John"))
#Hello John
print(hello_name("Jane"))
#Hello Jane
print(hello_name())
#Hello Stranger
Z zaawansowaną logiką
Zobaczmy, jak działa funkcja abs:
print(abs(-3)) #3
print(abs(3)) #3
print(abs(0)) #0
Ona podaje odległość danej liczby na osi liczbowej od punktu zero. Dla -3 jest to 3, dla 3 również, dla zera – 0. Ma zatem jakąś logikę, która dla liczb od zera w górę zwraca tę liczbę zaś dla liczb mniejszych niż zero – pozbawia je najpierw minusa (można to osiągnąć np. mnożąc razy -1).
Spróbujmy ją odwzorować:
def my_abs(num):
if num >= 0:
return num
else:
return num * -1
print(my_abs(-3)) #3
print(my_abs(3)) #3
print(my_abs(0)) #0
Jeżeli liczba jest większa lub równa zero to zwracamy tę liczbę, jeżeli nie – zwracamy ją pomnożoną razy -1.
Warto zauważyć, że słowo return oznacza nie tylko zwrócenie wartości ale i całkowite wyjście z funkcji. A zatem to nasze „else” czyli „w przeciwnym wypadku” jest w zasadzie tylko ze względów estetycznych i związanych z czytelnością. Można tę funkcję zapisać bowiem tak:
def my_abs(num):
if num >= 0:
return num
return num * -1
print(my_abs(-3)) #3
print(my_abs(3)) #3
print(my_abs(0)) #0
Jeżeli num będzie większy lub równy zeru, to funkcja w ten blok wejdzie i zwróci num. W przeciwnym wypadku będzie kontynuować, a dalej jest zwrócenie num razy -1. Czy używamy else czy nie to już kwestia estetyki i naszych upodobań.
Zwracające prawdę/fałsz
Możemy tworzyć funkcje, które zwracają prawdę lub fałsz. Oto funkcja sprawdzająca, czy dana liczba jest parzysta:
def is_even(num):
if num % 2 == 0:
return True
else:
return False
print(is_even(2))
#True
print(is_even(3))
#False
Jeżeli reszta z dzielenia num przez 2 jest równa zero – zwróć prawdę. Jeżeli nie – zwróć fałsz. Takie funkcje mogą być używane w blokach warunkowych:
def is_even(num):
if num % 2 == 0:
return True
else:
return False
n = 10
if is_even(n):
print("n is even")
else:
print("n is not even")
#n is even
Patrząc na tę funkcję mogliśmy wyłapać, że bloki warunkowe wewnątrz funkcji, te z użyciem słówka return różnią się od bloków poza funkcją, bowiem return oznacza wyjście. A zatem (wewnątrz funkcji tylko) możemy się pozbyć tego else:
def is_even(num):
if num % 2 == 0:
return True
return False
n = 10
if is_even(n):
print("n is even")
else:
print("n is not even")
#n is even
Jeżeli ta reszta z dzielenia wynosi zero to wejdziemy do bloku, gdzie już mamy return czyli zwróć wartość i wyjdź. Jeżeli nie to w przeciwnym wypadku wywoła się zwróć fałsz.
Ale to można jeszcze bardziej uprościć. Napiszmy coś takiego:
n = 10
print(n % 2 == 0)
#True
n = 11
print(n % 2 == 0)
#False
Aha. Samo takie postawienie sprawy (to jest napisanie n, reszta z dzielenia przez 2 równe 0) zwraca nam prawdę lub fałsz. A zatem, możemy dać coś takiego:
def is_even(num):
return num % 2 == 0
n = 10
if is_even(n):
print("n is even")
else:
print("n is not even")
#n is even
Tutaj funkcja is_even nie bawi się w żadne warunki. Po prostu sprawdza, czy reszta z dzielenia przez 2 jest równa 0, co da albo fałsz albo prawdę. I ten wynik nam zwraca.
Może i skomplikowane, ale tak to działa. Nikt nam nie zabrania, zwłaszcza na początkowym etapie, używać bardziej długich i czytelniejszych zapisów.
Z dowolną ilością argumentów
Pytanie: czy te funkcje przyjmują wiele argumentów?
print(sum([1,2,3])) #6
print(max([1,2,3])) #3
Odpowiedź – nie, przyjmują jeden, listę z wieloma argumentami. I jest to jakiś sposób na przyjęcie wielu argumentów – lista jako argument.
Napiszemy sobie taką funkcję sum:
def my_sum(lst):
num = 0
for n in lst:
num += n
return num
print(my_sum([1,2,3])) #6
Odtwarzamy tutaj klasyczną funkcję sum. Przekazujemy do niej listę (z dowolną liczbą elementów). Num ustawiamy na 0, następnie lecimy po każdym elemencie naszej listy i dodajemy go do num, które na końcu zwracamy.
Wywołując funkcję, przekazujemy jeden argument – listę, z dowolną ilością elementów. Jest to swego rodzaju obejście, bo funkcję z dowolną liczbą argumentów, tak prawdziwie, bez żadnych sztuczek, też uzyskać możemy.
Robi się to tak:
def my_sum(*args):
num = 0
for n in args:
num += n
return num
print(my_sum(1,2,3)) #6
print(my_sum(1,2,3,4)) #10
Działa nam w ten sam sposób, natomiast przekazujemy tam już dowolną ilość argumentów.
Funkcje lambda
Są to specyficzne funkcje. Zaczynamy od słówka lambda, potem podajemy argumenty, jakie ma funkcja przyjąć a potem jedną instrukcję, co z tymi argumentami zrobić.
Zrozumieć to lepiej na przykładzie. Oto tradycyjna funkcja dodająca dwie liczby do siebie:
def sum(a, b):
return a + b
print(sum(2,2)) #4
Ta sama funkcja, ale w wersji lambda:
sum = lambda a,b : a + b
print(sum(2,2)) #4
Do czego to się może przydać? Do innych funkcji przyjmujących funkcję jako argument. Takim czymś jest na przykład map. Pokażę przykład map, które bierze listę cyfr i zwraca inną listę z cyframi zamienionymi na liczby:
lst1 = ["1", "11", "2", "4"]
lst2 = list(map(int, lst1))
print(lst2)
#[1, 11, 2, 4]
Tak to wygląda, chcemy listę, która mapuje inną listę na funkcję int. Int to nic innego jak funkcja int() i możemy tego użyć w list comprehension, nie bawić się w map w ogóle:
lst1 = ["1", "11", "2", "4"]
lst2 = [int(n) for n in lst1]
print(lst2)
#[1, 11, 2, 4]
Natomiast celem moim jest pokazanie funkcji lambda, więc wyobraźmy sobie, że chcemy listę liczb zamienić na ich wartość razy dwa. Tradycyjnie zrobimy to tak:
lst1 = [1,2,3,4]
def timest_two(n):
return n * 2
lst2 = list(map(timest_two, lst1))
print(lst2)
#[2, 4, 6, 8]
Tutaj może przydać nam się lambda:
lst1 = [1,2,3,4]
lst2 = list(map(lambda n: n * 2, lst1))
print(lst2)
#[2, 4, 6, 8]
Choć po prawdzie takie rzeczy jak map czy filter są w Pythonie obecne ze względu na inne języki programowania. Python posiada list comprehension, a zatem możemy to zrobić tak:
lst1 = [1,2,3,4]
lst2 = [n * 2 for n in lst1]
print(lst2)
#[2, 4, 6, 8]
Tak jest chyba najbardziej czytelnie. Skoro już zahaczyliśmy o map oraz funkcje lambda to znak, że wypłynęliśmy na naprawdę głębokie wody i czas kończyć ten wpis. Niech to będzie zachęta na przyszłość bardziej niż coś, co już teraz musimy rozumieć.
Naukę Pythona będziemy kontynuować. Jest jeszcze wiele rzeczy do opisania. Tym niemniej – zrobiliśmy naprawdę dużo.