PPy3/Funkcje
Funkcje
Gdy raz wymyślimy jakiś sprytny algorytm, to nie ma co go za każdym razem odkrywać na nowo. Żeby było łatwiej pisać raz, a korzystać wielokrotnie - wymyślono funkcje.
Definiowanie funkcji
def funkcja(x, y):
BLOK INSTRUKCJI
- Definicja funkcji to instrukcja złożona, podobna w strukturze do tych, które już znamy - instrukcji warunkowej i pętli.
- Wykonanie definicji funkcji nie wiąże się z natychmiastowym wykonaniem zawartego w niej bloku.
- W definicji funkcji (przykładowej) x i y to parametry formalne; nie mają one w tym momencie określonych wartości, są miejscami do wypełnienia konkretnymi wartościami, gdy funkcję postanowimy użyć. Może ich być (niemal) dowolnie wiele, w tym — zero. W tym ostatnim przypadku piszemy pustą parę nawiasów.
- Wykonanie definicji funkcji polega na nadaniu znaczenia jej nazwie, która odtąd będzie oznaczała ciąg instrukcji stanowiących wewnętrzny blok.
- Użycie czyli wywołanie funkcji to wyrażenie postaci
funkcja(x, y)
gdzie x i y mają już konkretne wartości (w tym miejscu mogą stać również wyrażenia złożone), które będą użyte w instrukcjach stanowiących definicję.
- W definicji na ogół występuje (raz lub więcej razy) słowo (instrukcja) return. Oznacza ono, że w tym miejscu wykonanie funkcji się kończy - funkcja powraca. Wartość wyrażenia po słowie return stanowi wynik zwracany przez funkcję - czyli wartość wyrażenia, będącego wywołaniem funkcji.
- Jeśli return nie ma, albo nie ma po nim wartości zwracanej, albo wykonanie funkcji kończy się ,,wypadnięciem" przez koniec bloku, wartością zwracaną jest None — która w zasadzie do niczego się specjalnie nie nadaje, poza sprawdzeniem, czy mamy do czynienia z właśnie tą wartością.
- funkcja w Pythonie (i większości języków programowania) to coś nieco podobnego do funkcji (odwzorowania) w matematyce, ale to nie jest to samo pojęcie:
- w Pythonie wywołanie funkcji może mieć skutki uboczne;
- wynik funkcji, w tym skutki uboczne, może zależeć nie tylko od argumentów wywołania.
Skutkami ubocznymi wywołania nazywamy jakiekolwiek efekty tej instrukcji, które nie sprowadzają się do wartości zwracanej. A więc, zmianę wartości innych zmiennych, czy np. wysłanie jakichś danych siecią, lub wypisanie czegoś na ekran.
- Funkcja może być wartością zmiennej; inaczej mówiąc, funkcja może być znana pod dodatkowymi nazwami (aliasami), może być włożona do listy na którąś z jej pozycji itp.
ff = funkcja
...
wynik = ff(x)
- ,,Oryginalna" nazwa funkcji, czyli występująca w jej definicji, jest jednak w pewnym sensie uprzywilejowana.
- Zmienne (nazwy) powołane do życia wewnątrz funkcji są lokalne; ich wartości ,,żyją" tylko póki wykonuje się funkcja.
- Zmienne lokalne powielające nazwy ,,zewnętrzne" względem funkcji przesłaniają te zewnętrzne. Chyba, że użyjemy deklaracji global:
x = 1
def podwojx(): # to nie zadziała:
x = x * 2
podwojx()
UnboundLocalError Traceback (most recent call last)
<ipython-input-3-6edd575e5cda> in <module>()
----> 1 podwojx()
<ipython-input-1-1da635f42ffa> in podwojx()
1 def podwojx():
----> 2 x *= 2
3
UnboundLocalError: local variable 'x' referenced before assignment
Jak widzimy, komunikat o błędzie pojawił się dopiero w wyniku wywołania błędnej funkcji.
x = 1
def podwojx(): # to zadziała - co nie znaczy, że jest mądre...
global x
x = x * 2
podwojx()
print x
→ 2
Należy unikać takich sztuczek, jak powyżej; jeśli już, to:
def podwoj(x):
return 2 * x
liczba = 1
print podwoj(liczba)
→ 2
Wskazówka: każde użycie deklaracji global w programie powinno być dobrze uzasadnione. Jeżeli używamy jej więcej niż sporadycznie, to coś jest nie tak.
Funkcja może być
- zeroargumentowa — wówczas zarówno w definicji, jak i w wywołaniu po nazwie występuje pusta para nawiasów,
- jednoargumentowa (jak powyższa podwoj),
- dwuargumentowa (jak przykładowa),
- trzy-, cztero-, ... itd.
- o zmiennej liczbie argumentów, z wartościami domyślnymi dla brakujących:
def dodaj(x, y=1):
return x + y
dodaj(2, 3)
→ 5
dodaj(2)
→ 3
Parametry z wartościami domyślnymi muszą występować na końcu listy parametrów.
- o zmiennej liczbie argumentów, ale bez wartości domyślnych, np.:
def ile(*args):
return len(args)
Działa to tak: '*' postawiona przed nazwą argumentu oznacza, że wszystkie (ew. dalsze) argumenty zostaną zebrane w jedną listę, która dostępna jest wewnątrz funkcji pod nazwą, jaką postawiliśmy po gwiazdce. W powyższym przykładzie, funkcja ile zwraca jako swój wynik po prostu liczbę argumentów, z jakimi funkcja została wywołana. Operator '*' nie ma tu wiele wspólnego z mnożeniem. Odróżniamy go od mnożenia po tym, że po jego lewej stronie nie stoi żaden czynnik. Ten sam operator może wystąpić również w roli poniekąd odwrotnej, np.:
lista = [*range(5)]
print(*lista)
→ 0 1 2 3 4
W pierwszej linii, gwiazdka niejako ,,rozwija" sekwencję range(5) w ciąg wartości (kolejnych liczb) wstawionych pod operator [...]; w linii drugiej, lista jest również ,,rozpakowana" w ciąg oddzielnych argumentów wywołania funkcji print — sprawdź, że wynik print(lista) wyglądałby nieco inaczej.
Ćwiczenia
1. Napisz funkcję pierwiastki(a, b, c), która wypisuje na ekran (za pomocą wywołania print) pierwiastki równania kwadratowego o wspołczynnikach a, b i c: a * x**2 + b * x + c == 0. W przypadku, gdy pierwiastków nie ma, wypisuje komunikat Równanie nie ma pierwiastków. W przypadku, gdy a=0, wypisuje komunikat Błędne dane: a==0 i kończy działanie.
2. Napisz analogiczną funkcję, która zamiast bezpośrednio wypisywać rozwiązania, zwraca je jako wynik swojego działania. Wypisaniem ich na ekran zajmuje się odrębna instrukcja. W przypadku, gdy pierwiastków nie ma, zwraca wartość None. Nie sprawdzaj warunku a!=0 - co się dzieje, gdy nie jest spełniony przez argumenty wywołania funkcji?
3. Napisz funkcję silnia(n), która oblicza i zwraca wartość silni: n! := 1 * 2 * 3 * ... (n - 1) * n. Uwzględnij, że przyjmuje się że 0! = 1, natomiast dla liczb ujemnych silnia nie jest określona - co można zasygnalizować zwracając jako wynik None. Użyj pętli for i funkcji range.
4. Funkcję silnia można zdefiniować w sposób rekurencyjny - za pomocą następujących warunków:
n! == None dla n < 0
0! == 1
n! == n * (n - 1)!
Napisz kod funkcji silnia(n) realizujący powyższą definicję. Porównaj działanie tego kodu z realizacją tej funkcji z poprzedniego przykładu dla dużych wartości n.