Poznaliśmy dotąd szereg funkcji Pythona, zarówno wbudowanych, jak i dostępnych z poziomu dołączanych modułów.
Jak w każdym języku umożliwiającym programowanie strukturalne, Python pozwala również tworzyć programiście własne funkcje.
Definicja funkcji musi zawierać:
W języku Python składnia definicji funkcji jest następująca:
def nazwa_funkcji ( lista_parametrów):
instrukcje_do_wykonania
Przejdźmy do trybu interaktywnego Pythona.
Zdefiniujemy przykładową funkcję pierw, która wyliczać będzie pierwiastek kwadratowy liczby rzeczywistej podanej jako argument.
>>> def pierw(n):
return n**0.5
Jak widać funkcja jest bardzo prosta, a jej ciało składa się tylko z jednej linii zawierającej instrukcję return. Instrukcja return służy do przekazywania rezultatu na zewnątrz funkcji. Wyrażenie po niej zostanie wyliczone, a jego wartość zwrócona jako rezultat funkcji.
Wypróbujmy działanie funkcji:
>>> pierw(2)
1.4142135623730951
>>> pierw(9)
3.0
Zdefiniowaną uprzednio funkcję możemy w dowolnym miejscu
usunąć, posługując się instrukcją del. A zatem:
>>>
I teraz:
>>> pierw(3)
Traceback (most recent call
last):
File
"<pyshell#6>", line
pierw(3)
NameError: name 'pierw' is not defined
>>>
Co oznacza, że funkcję pierw udało nam się usunąć i nie możemy dalej jej używać.
Nie martwmy się jednak tą stratą. Zaraz zdefiniujemy nową, lepszą funkcję pierw.
Uwzględni ona, że pierwiastek kwadratowy możemy obliczać tylko dla nieujemnych liczb rzeczywistych.
>>> def pierw(n):
if
n>=0: return n**0.5
Dla liczb nieujemnych funkcja działa prawidłowo:
>>> pierw(3)
1.7320508075688772
A dla ujemnych nie powoduje komunikatu o błędzie
>>> pierw(-3)
>>>
Aby zmienić funkcję, nie musimy jej wpierw kasować. Wystarczy, że od nowa ją zdefiniujemy.
Poprawmy naszą funkcję tak, aby wyliczała pierwiastek również dla ujemnych liczb rzeczywistych:
>>> def pierw(n):
if
n>=0: return n**0.5
else:
return (-n)**0.5*1j
Sprawdźmy:
>>> pierw(4)
2.0
>>> pierw(-4)
2j
Występujący w nagłówku funkcji identyfikator n nazywamy parametrem formalnym. Jest to nazwa, pod którą przekazana do funkcji wartość widziana jest wewnątrz ciała funkcji.
Parametr formalny jest szczególnym rodzajem zmiennej lokalnej (szczególnym, bo inicjalizowanym wartością podaną przy wywołaniu w nawiasach). Zmienna lokalna to każda zmienna inicjowana w obrębie funkcji. Zmienna lokalna:
>>> pierw(7)
2.6457513110645907
>>> n
Traceback (most recent call
last):
File
"<pyshell#2>", line
n
NameError: name 'n' is not
defined
>>> n=8
>>> pierw(7)
2.6457513110645907
>>> n
8
Jak widać modyfikacja zmiennej lokalnej n (na 7), nie zmienia wartości zmiennej globalnej n (nadal 8).
Występująca w wywołaniu funkcji wartość 7 to parametr aktualny. Parametry aktualne to faktyczne wartości przekazane do funkcji.
W momencie wywołania funkcji wszystkie operacje przewidziane do wykonania na parametrze formalnym, wykonywane są na parametrze aktualnym (czyli w tym przypadku na liczbie 7).
Funkcja może przyjmować więcej niż jeden argument i zwracać więcej niż jeden rezultat.
Poniżej mamy przykładową funkcję rs, która dla dwóch liczb zwraca ich sumę oraz różnicę.
>>> def rs(a,b):
return a+b,a-b
Wypróbujmy:
>>>
rs(1,3)
(4,
-2)
Jak widać, rezultat wywołania funkcji, która zwraca więcej niż jedną wartość, jest krotką.
Możemy to wykorzystać w iteracji:
>>>
for n in rs(3,4): print n
7
-1
Lub skonwertować wynik na listę:
>>>
list(rs(2,7))
[9,
-5]
Jeżeli wartości, które mają zostać przekazane jako argumenty funkcji zawarte są w sekwencji, np.:
>>>
l=[2,3]
nie da się bezpośrednio przekazać takiej sekwencji jako listy argumentów (gdyż traktowana jest ona jako pojedynczy argument):
>>> rs(l)
Traceback (most recent call
last):
File "<pyshell#8>", line
rs(l)
TypeError: rs()
takes exactly 2 arguments (1 given)
o ile nie "rozpakujemy" elementów sekwencji przy użyciu gwiazdki:
>>>
rs(*l)
(5,
-1)
Zdefiniujmy następującą funkcję:
>>>
def kto(imie,wiek,jaki):
print
imie,"mimo swych",wiek,"lat
jest bardzo",jaki+"m człowiekiem"
Wypróbujmy ją:
>>> kto("Adam",12,"mądry")
Adam
mimo swych 12 lat jest bardzo mądrym człowiekiem
>>> kto("Ola",16,"rozsądny")
Ola
mimo swych 16 lat jest bardzo rozsądnym człowiekiem
Jeżeli jednak nie podamy wartości dla wszystkich parametrów formalnych, wystąpi błąd:
>>> kto("Zdziś")
Traceback (most recent call
last):
File "<pyshell#17>", line
kto("Zdziś")
TypeError: kto()
takes exactly 3 arguments (1 given)
Możemy tego uniknąć, podając domyślne wartości argumentów. Zdefiniujmy funkcję od nowa:
>>>
def kto(imie,wiek=18,jaki="oczytany"):
print
imie,"mimo swych",wiek,"lat
jest bardzo",jaki+"m człowiekiem"
Teraz wystarczy jednak podać imię, by funkcja zadziałała:
>>> kto("Zdziś")
Zdziś
mimo swych 18 lat jest bardzo oczytanym człowiekiem
>>> kto("Zdziś",44)
Zdziś
mimo swych 44 lat jest bardzo oczytanym człowiekiem
>>> kto("Zdziś",22,"krnąbrny")
Zdziś
mimo swych 22 lat jest bardzo krnąbrnym człowiekiem
Jedynym parametrem wymaganym pozostaje imię, gdyż nie podaliśmy dla niego wartości domyślnej:
>>> kto()
Traceback (most recent call
last):
File "<pyshell#23>", line
kto()
TypeError: kto()
takes at least 1 argument (0 given)
Parametry do funkcji, możemy przekazywać albo podając ich wartości w kolejności podanej w nagłówku definicji funkcji, albo w dowolnej kolejności, wykorzystując ich nazwy:
>>> kto(imie="Jan",wiek=4,jaki="dostojny")
Jan
mimo swych 4 lat jest bardzo dostojnym człowiekiem
>>> kto(jaki="dostojny",wiek=4,imie="Jan")
Jan
mimo swych 4 lat jest bardzo dostojnym człowiekiem
Jeżeli parametry posiadają wartości domyślne, możemy przekazywać tylko wybrane z nich:
>>> kto(jaki="dostojny",imie="Jan")
Jan
mimo swych 18 lat jest bardzo dostojnym człowiekiem
Parametry znajdujące się na początku listy i na właściwych sobie pozycjach, nie muszą mieć podanej nazwy:
>>> kto("Jan",jaki="dostojny")
Jan
mimo swych 18 lat jest bardzo dostojnym człowiekiem
Parametry znajdujące się po parametrach wymienionych z nazwy, nawet na właściwych sobie pozycjach, muszą mieć podaną nazwę:
>>> kto(imie="Jan",4,"dostojny")
SyntaxError: non-keyword arg after keyword
arg
Jeżeli w momencie definiowania funkcji nie jesteśmy w stanie określić liczby argumentów, które będą do niej przekazywane, poprzedzamy nazwę parametru formalnego oznaczającego wszystkie pozostałe argumenty funkcji gwiazdką:
>>> def suma(*arg):
s=0
for x in arg:
s+=x
return s
Teraz funkcja zadziała dla dowolnej liczby argumentów:
>>>
suma()
0
>>>
suma(1)
1
>>>
suma(1,2)
3
>>>
suma(1,2,3)
6
>>>
suma(*range(10))
45
Funkcje rekurencyjne to funkcje, które odwołują się do samych siebie. Dobrym przykładem funkcji rekurencyjnej jest silnia:
>>>
def silnia(n):
if n>1:
return n*silnia(n-1)
else:
return 1
>>> silnia(1)
1
>>> silnia(2)
2
>>> silnia(5)
120
Wykorzystujemy tu fakt, że n!=(n-1)!*n.
Każda funkcja oparta na iteracji, może zostać przedstawiona w postaci rekurencyjnej. Np. suma:
>>> def suma(*n):
if n:
return n[0]+suma(*list(n)[1:]) # konwersja na listę, gdyż krotka nie może
być przycięta
else:
return
0
>>> suma(1)
1
>>> suma(1,2,3)
6
>>> suma(*range(100))
4950
Nie zawsze jednak funkcja w postaci rekurencyjnej jest jednak użyteczna. Przykładem nieefektywności rekursji jest funkcja wyliczająca n-ty element ciągu Fibonacciego (0,1,1,2,3,5,8,13,21,...).
>>> def fib(n):
if
n<2:
return
n
else:
return
fib(n-1)+fib(n-2)
Wypróbujmy:
>>> fib(1)
1
>>> fib(3)
2
>>> fib(8)
21
>>> fib(20)
6765
Na razie działa dobrze. Spróbujmy większej liczby n:
>>> fib(50)
Czekamy, czekamy, a wyniku jak nie było, tak nie ma. Najsensowniej będzie przerwać działanie funkcji wciskając kombinację klawiszy CTRL+C.
Dlaczego funkcja liczy tak powoli? Każde wywołanie funkcji powoduje jej ponowne dwukrotne wywołanie dla n>=2. A zatem, dla n=50, liczba wywołań funkcji wyniesie około 249 razy. Nawet jeśli pojedyncze wywołanie funkcji zabiera tylko jedną dziesięciomilionową sekundy, to wykonanie 249 wywołań zajmie komputerowi prawie dwa lata.
Tę samą funkcję da się przedstawić w szybkiej wersji iteracyjnej:
>>> def fib(n):
if n<2:
return n
a, b = 0, 1 # 0 podstawiamy pod a, 1 pod b
for x in range(1, n): # potrzebujemy
n-1 iteracji
a, b = b, a+b # b podstawiamy pod a, sumę pod b
return b
Sprawdźmy:
>>> fib(0)
0
>>> fib(3)
2
>>> fib(8)
21
>>> fib(20)
6765
>>> fib(50)
12586269025L
Spróbujemy teraz napisać funkcję, która wygeneruje losowe zdanie zawierające podaną liczbę (domyślnie 5) losowo wygenerowanych wyrazów.
>>> def brednie(wyrazy=5):
# funkcja generuje losowe zdanie o zadanej liczbie wyrazów
from random import seed,randint
seed()
tekst=""
for wyraz in range(wyrazy):
for
litera in range(randint(1,10)): # między
tekst+=chr(randint(ord('a'),ord('z'))) # litery od a do z
tekst+=" "
return (tekst[:-1]+".").capitalize() # z dużej litery, a na końcu
kropka
Wypróbujmy:
Spróbujemy teraz napisać program do wyliczania odległości między dwoma punktami na płaszczyźnie. Aby przejść do edycji nowego programu należy z menu File wybrać polecenie New Window. Otworzy się nowe okno, przeznaczone do edycji programu. Wybierzmy z menu File polecenie Save As. Wybieramy folder Moje dokumenty, następnie wpisujemy nazwę punkty.py.
I. Zdefiniuj funkcję "geo", która dla podanych trzech parametrów: n=numer elementu ciągu, a1=wartość pierwszego elementu ciągu (domyślnie 1), q=wartość iloczynu ciągu geometrycznego (domyślnie 2) zwróci n-ty element ciągu geometrycznego.
II. Zdefiniuj funkcję "avg", która dla dowolnej liczby parametrów zwróci ich średnią arytmetyczną (lub 0 dla 0 parametrów).