Funkcje

Funkcje są podstawową konstrukcją w programowaniu. Dzięki nim możemy używać fragmentów kodu wielokrotnie w programie. O użyteczności funkcji dowiedziałeś się już korzystając ze sprawdzenia długości listy czy też napisu - pomyśl o ile mniej wygodnie byłoby za każdym razem robić pętle "for" i za każdym jej przejściem do zmiennej dodawać 1.

Na potrzeby tego rozdziału załóżmy sobie, że będziemy często potrzebować policzyć wartość tej funkcji:

\(f(x) = \frac{2*x^3}{8.51}\)

W Pythonie przedstawia się to tak:

x = 15
wynik = (2 * (x**3))/8.51
print wynik

Ale kopiowanie tego i zmienianie zmiennej jest bardzo niewygodne. Możemy zdefiniować sobie funkcję, która jako argument przyjmie x i zwróci nam wartość policzonego wyrażenia:

def f(x):
    return (2 * (x**3))/8.51
 
print f(5)

aby zdefiniować funkcję używamy słowa def, po którym następuje nazwa funkcji. W nawiasach podajemy nazwy kolejnych argumentów (w szczególności, możemy nie podawać ich wcale jeśli nie chcemy). Ciało funkcji oznaczamy tak samo jak w przypadku pętli for lub instrukcji warunkowych, czyli wcięciem w kodzie.

Możemy (ale nie musimy) zwrócić jakąś wartość poprzez użycie return, dzięki czemu możemy zapisać wynik naszego działania do jakiejś zmiennej tak samo jak w przypadku kiedy sprawdzaliśmy długość listy funkcją len. Co bardzo ważne, jeśli dojdziemy do instrukcji "return" funkcja się kończy (nawet jeśli coś "za" słówkiem "return" było)! Możemy też nic nie zwracać, co spowoduje, że nasza funkcja wydawać się może mało użyteczna, ale stosuje się to często w przypadku funkcji działających przez efekty uboczne, które np. wypisują jakieś złożone dane na ekran, albo zmieniają jakiś obiekt (tak jest np. z metodami usuwającymi elementy z listy).

Co bardzo ważne, instrukcje wewnątrz ciała funkcji nie wykonają się jeśli nie wywołamy tej funcji. Zobacz jak zadziała ten kod:

def funkcja():
    print "wykonalo sie"

jak widzisz, nic kompletnie się nie stało. Dopiero gdy napiszesz "funkcja()" zostanie wyświetlony napis "wykonalo sie".

Wróćmy jednak do naszej funkcji obliczającej wyrażenie arytmetyczne. Załóżmy, że chcieli byśmy założyć, że często nie wiemy w jakim miejscu chcemy policzyć wartość funcji, więc chcieli byśmy ją liczyć dla powiedzmy zera, ot tak żeby tylko ją policzyć :)

W Pythonie jest to bardzo proste, wystaczy po nazwie argumentu napisać znak równości a po nim wartość, która ma być domyślnie używana.

def f(x=0):
    return (2 * (x**3))/8.51
 
print f()
print f(5)

Funkcje - poziom wyżej

Funkcje nie różnią się bardzo od zmiennych. Możemy naszą funkcję przypisać do jakiejś zmiennej (nie wywołując jej) w ten oto sposób, a następnie korzystać z niej w normalny sposób.
 

def f(x=0):
    return (2 * (x**3))/8.51
 
funkcja = f
 
print f()
print funkcja()

Nie jest to szczególnie przydatne ale prowadzi nas do pewnego odkrycia: skoro funkcji można użyć jako zmiennej to można też przekazać ją jako argument! A to rozwiązanie znajdzie całkiem sporo ciekawych zastosowań. Zobaczmy zatem jak to działa

def wyswietl(napis):
    print napis
 
def wez_napis_i_cos_z_nim_zrob(napis, zrob):
    zrob(napis)
 
wez_napis_i_cos_z_nim_zrob("Jakis napis", wyswietl)

To daje nam ciekawe możliwości, więc napiszmy kalkulator działający w ten sposób, który może zrobić coś z 2 liczbami. Od razu zauważ, że dzięki takiemu podjeściu będziemy mogli do naszego kalkulatora łatwo dodawać nowe funkcjonalności zupełnie nie zmieniając kodu, który napisaliśmy wcześniej.

def policz(jak, liczba1, liczba2):
    return jak(liczba1, liczba2)
 
def dodaj(x, y):
    return x+y
 
def podnies_do_potegi(x, y):
    return x**y
 
def pomnoz(x, y):
    return x*y
 
def podziel(x, y):
    #tutaj zamieniamy jedna z liczb
    #na zmiennoprzecinkowa na 
    #potrzeby Pyhona2
    return (x*1.0)/y

Możesz teraz dopisać samodzielnie funkcję, która odejmuje zupełnie nie zmieniając naszego kodu! To może być naprawdę użyteczne.

Kolejną (i już ostatnią w tym samouczku) ciekawą rzeczą dotyczącą funkcji jest rekurencja. Jest to mechanizm pozwalający na to, że funkcja może wywoałać samą siebie. Tak, to może wydać się na początku dziwne i nieintuicyjne ale zauważmy, że w matematyce jest to bardzo standardowa metoda opisu funkcji. Jako przykład pokażmy standardowo liczenie silni.

Jeśli nie wiesz co to silnia: zapisujemy ją za pomocą "!" a oznacza to mnożenie kolejnych liczb od 1 do podanej liczby. Czyli

\(5! = 1 * 2 * 3 * 4 * 5 = 120\)

(Warto nadmienić, że silnia z zera to 1)

Funkcja rekurencyjna w Pythonie wyglądać będzie tak:

def silnia(x):
    if x==1 or x==0:
        return 1
    else:
        return x*silnia(x-1)

Przeanalizujmy zatem co stanie się po wpisaniu silnia(3)

  1. Warunek wyjścia nie jest na początku spełniony, więc zostanie zwrócona 3 pomnożona przez silnię z liczby o jeden mniejszej (bo 3! to to samo co 3*2!)
  2. Zostanie więc wywołana funkcja silnia(2), warunek wyjścia znów nie zostanie spełniony, czli zostanie zwrócona 2 pomnożona przez silnię z 1
  3. Zostanie wywołana silnia(1), warunek x==1 zostanie spełniony, więc zostanie zwrócona jedynka
  4. Teraz "wracając" do wywołania silnia(2) ta jedynka jest mnożona przez 2 dzięki czemu uzyskujemy wynik silni dla 2
  5. Co za tym idzie ten wynik jest mnożony przez 3 aby uzyskać wynik silni dla 3
  6. Otrzymujemy nasz wynik, czyli 3*(2*(1)), a więc 6 :)

Na początku może wydawać się to trochę skomplikowane, więc dobrze jest to sobie samemu rozpisać lub nawet rozrysować na kartce.

Kiedy już zrozumiesz jak działa rekurencja musisz uświadomić sobie, że pisanie funckji rekurencyjnych wiąże się ze pewnym ryzykiem. Jeśli na przykład zapomnielibyśmy odejmować 1 od x, to funkcja wywoływała by się w nieskończoność! Na szczęście interpreter na to niepozwoli i zwróci nam błąd nadmiernego zagnieżdżenia pętli, ale  zawsze trzeba pamiętać, by rozważyć, czy funkcja jest skonstruowana tak, że wywołania kiedyś się zakończą.