Funkcje
Składnia funkcji
Możliwość zaprogramowania funkcji zwiększa uniwersalność pisanych skryptów. Można, a nawet trzeba, stosować je wtedy, gdy stosujemy ten sam schemat obliczeń, ale dla różnych parametrów. Przykładowo dane wyrażane w procentach chcemy korelować z ciężarem obiektów. Gdy dane procentowe skupiają się wokół 0 lub 100, trzeba je transformować funkcją x/(100-x) i takie transformowane dane użyć do obliczeń korelacji. Warto wtedy posługiwać się jedną funkcją T(x).
Funkcje tworzy się po słowie ‘def’ po którym występuje nazwa funkcji, nawiasy okrągłe z wymienionymi argumentami. Następnie występuje zestaw instrukcji, które służą do wyliczenia wyniku funkcji. Na końcu, po słowie ‘return’ pisze się to, co funkcja powinna pokazać jako wynik. Kod transformacji T wyglądałby następująco:
def T(x):
y=x/(100-x)
return y
|
Ponieważ ma ona sens tylko dla liczb między 0 a 100 warto takie ograniczenie wprowadzić w programie funkcji.
def T(x):
if (x<0 | x>=100) : y='wartość wykluczająca transformację'
y=x/(100-x)
return y
|
Argumentami funkcji, jak i wynikiem, mogą być liczby, znaki, teksty, listy, krotki i inne złożone wyrażenia.
Aby zobaczyć wynik działania funkcji, musimy wstawić do niej jakieś wartości.
#funkcja
def T(x):
if (x<0 | x>=100) : y='wartość wykluczająca transformację'
y=x/(100-x)
return y
#program
print(T(-1))
print(T(1.5),T(95))
print(T(100))
|
>>>
wartość wykluczająca transformację
0.005025125628140704 19.0
wartość wykluczająca transformację
|
Słowo return powinno pojawić się w funkcji tylko raz:
#funkcja
def potegi(x):
for i in range(5): return x**i
#program
print(potegi(3))
print(potegi(5.5))
|
#funkcja
def potegi(x):
return x**0
return x**1
return x**2
return x**3
return x**4
#program
print(potegi(3))
print(potegi(5.5))
|
>>>
1
1.0
|
>>>
1
1.0
|
W obu przypadkach dla obi liczb 3 i 5.5 zwrócony został tylko wynik dla pierwszego użycia return. Prawidłowy program powinien utworzyć listę wyników i nazwę tej listy pokazać jako wynik funkcji.
#funkcja
def potegi(x):
wyniki=[]
for i in range(5): wyniki.append(x**i)
return wyniki
#program
print(potegi(3))
print(potegi(5.5))
|
>>>
[1, 3, 9, 27, 81]
[1.0, 5.5, 30.25, 166.375, 915.0625]
|
Zmienne zastosowane w funkcjach są zmiennymi wyłącznie w obrębie funkcji. te same nazwy mogą mieć zmienne poza funkcją i mogą mieć zupełnie inne wartości, a nawet typy.
#funkcja
def dzielenie(a,b):
if b!=0: return a/b
else: return 'dziel.przez.0'
#program
a='Wykonujemy dzielenie 75 jabłek '
b='między 15 uczniów i każdy uczeń dostaje ich '
print(a+b,dzielenie(75,15))
|
>>>
Wykonujemy dzielenie 75 jabłek między 15 uczniów i każdy uczeń dostaje ich 5.0
|
Funkcje wieloargumentowe
Większość funkcji, które trzeba napisać w Pythonie w postaci funkcji jest wieloargumentowa. Być może dlatego, że takich funkcji jest stosunkowo mało w standardowych bibliotekach.
def nazwa(a1,a2,...): instrukcja 1.1 instrukcja 1.2 ... return(wartość)
Przykładem przydatnej funkcji może być funkcja analizująca liczbę pierwiastków rzeczywistych równania kwadratowego ax2+bx+c wyliczająca je, gdy wielkość “delta” jest nieujemna. Funkcja taka może wyglądać następująco:
def pierwiastki(a,b,c):
delta=b*b-4*a*c
if delta<0:
komunikat="Nie ma pierwiastków"
return komunikat
else:
komunikat="Ma pierwiastki"
x1=(-b-(delta)**0.5)/(2*a)
x2=(-b+(delta)**0.5)/(2*a)
return komunikat, x1, x2
|
Żeby zobaczyć efekty jej działania należy wprowadzić przykładowe parametry:
#funkcja
def pierwiastki(a,b,c):
delta=b*b-4*a*c
if delta<0:
komunikat="Nie ma rzeczywistych pierwiastków."
return komunikat
else:
komunikat="Ma pierwiastki rzeczywiste:"
x1=(-b-(delta)**0.5)/(2*a)
x2=(-b+(delta)**0.5)/(2*a)
return komunikat, x1, x2
#Program
print(pierwiastki(4,7,3))
print(pierwiastki(4,7,4))
|
>>>
('Ma pierwiastki rzeczywiste:', -1.0, -0.75)
Nie ma rzeczywistych pierwiastków
|
Aby wśród wyników programu pojawiły się wprowadzone wartości parametrów, należy trochę inaczej napisać skrypt.
def pierwiastki(a,b,c):
delta=b*b-4*a*c
if delta<0:
komunikat="Nie ma pierwiastków"
return komunikat
else:
komunikat="Ma pierwiastki"
x1=(-b-(delta)**0.5)/(2*a)
x2=(-b+(delta)**0.5)/(2*a)
return komunikat, x1, x2
a=4
b=7
c=3
print("wielomian "+str(a)+"x^2+"+str(b)+"x+"+str(c), str(pierwiastki(4,7,3)))
|
>>>
wielomian 4x^2+7x+3 ('Ma pierwiastki', -1.0, -0.75)
|
Komunikaty i wyniki zwracane przez funkcje mogą być jeszcze bardziej złożone.
def pierwiastki(a,b,c):
tresc="wielomian "+str(a)+"x^2+"+str(b)+"x+"+str(c)+" "
delta=b*b-4*a*c
if delta<0:
komunikat="nie ma pierwiastków"
return tresc+komunikat
else:
komunikat="ma pierwiastki: "
x1=(-b-(delta)**0.5)/(2*a)
x2=(-b+(delta)**0.5)/(2*a)
return tresc+komunikat+str(x1)+","+str(x2)
a=4
b=7
c=3
print(pierwiastki(a,b,c))
c=4
print(pierwiastki(a,b,c))
|
>>>
wielomian 4x^2+7x+3 ma pierwiastki: -1.0,-0.75
wielomian 4x^2+7x+4 nie ma pierwiastków
|
Funkcje mogą posiadać nie tylko dowolną ilość argumentów (łącznie z 0), a także zwracać dowolną liczbę wartości. Nazywa się je funkcjami zwracającymi wektor wartości. Zapisuje się to w matematyce jako wektor funkcji, a Pythonie najlepiej zapisywać ich wynik jako krotkę.
def dzialania(a,b):
if b!=0: dziel=a/b
else: dziel='dziel.przez.0'
return a+b, a-b, a*b, dziel
print(dzialania(3,4))
print(dzialania(5,0))
|
>>>
(7, -1, 12, 0.75)
(5, 5, 0, 'dziel.przez.0')
|
Zarówno argumentami jak i wartościami funkcji w Pythonie mogą być obiekty złożone, np. listy. Poniższa funkcja przekłada elementy list w sposób deterministyczny.
def przestaw(lista):
lis=[]
i=0
while i<len(lista):
lis=[lista[i]]+lis
i=i+1
if i<len(lista):
lis=lis+[lista[i]]
i=i+1
return lis
print(przestaw([1,2,3,4,5]))
|
def przestaw(lista):
lis=[]
i=0
while i<len(lista):
lis.insert(0,lista[i])
i=i+1
if i<len(lista):
lis.append(lista[i])
i=i+1
return lis
print(przestaw([1,2,3,4,5]))
|
>>>
[5, 3, 1, 2, 4]
|
>>>
[5, 3, 1, 2, 4]
|
Można też zdefiniować funkcję wykonująca działania na elementach list. Jest to odpowiednik funkcji posiadającej dowolną, nie określoną z góry, liczbę parametrów. Takimi funkcjami jest szereg charakterystyk rozkładu używanych przy opracowaniu danych. Można je zdefiniować używając znaku * przed nazwą listy.
def srednia(*lista):
suma=0
for x in lista: suma=suma+x
if len(lista)>=1: return suma/len(lista)
else: return 'brak'
print(srednia(1))
print(srednia(1,2,3))
print(srednia(1,1,3,3,1.5,4))
|
def wariancja(*lista):
n=len(lista)
suma=0
sumakw=0
for x in lista:
suma=suma+x
sumakw=sumakw+x**2
if len(lista)<2: return "brak"
else: return (n/(n-1))*(sumakw/n-(suma/n)**2)
print(wariancja(1))
print(wariancja(1,2,3))
print(wariancja(1,1,3,3,1.5,4))
|
>>>
1.0
2.0
2.25
|
>>>
brak
1.0000000000000004
1.575
|
Funkcje z parametrami
W matematyce bardzo często rozróżnia się argumenty funkcji od jej parametrów. W zapisie wielomianu kwadratowego ax2+bx+c argumentem jest x a litery a,b,c są parametrami za którymi mogą się kryć liczby rzeczywiste lub zespolone. Rzadko rozważa się funkcje czteroargumentowe f(x,a,b,c)=ax2+bx+c, aczkolwiek taka funkcja jest prawidłowo zdefiniowana zarówno dla matematyków jak i programów komputerowych. Różnica polega tylko na różnej interpretacji tych symboli, czego komputer sam z siebie nie robi.
W Pythonie wielomian kwadratowy jest funkcja czteroargumentową. Można go zdefiniować następująco:
def wiel_kwadrat(x,a,b,c):
return a*x**2+b*x+c
|
Jeżeli wykluczymy z grupy wielomianów kwadratowych funkcje liniowe (powstające, gdy a=0), to powinniśmy to uwzględnić w funkcji:
def wiel_kwadrat(x,a,b,c):
if a==0: return "To funkcja liniowa"
else: return a*x**2+b*x+c
|
Ponadto niektórym parametrom można nadać wartości domyślne uznając przykładowo, że podstawowy wielomian kwadratowy to y=x2.
def wiel_kwadrat(x,a=1,b=0,c=0):
if a==0: return "To funkcja liniowa"
else: return a*x**2+b*x+c
|
Spowoduje to, że bez podania wartości parametrów zostaną im przypisane wartości domyślne. Można zatem używać takiej funkcji jako pozornie jednoargumentowej albo dwu- lub trzyargumentowej.
def wiel_kwadrat(x,a=1,b=0,c=0):
if a==0: return "To funkcja liniowa"
else: return a*x**2+b*x+c
print(wiel_kwadrat(-2))
print(wiel_kwadrat(-2,a=0.5))
print(wiel_kwadrat(-2,b=2))
print(wiel_kwadrat(-2,a=0.5,b=2),wiel_kwadrat(-2,b=2,a=0.5))
print(wiel_kwadrat(-2,a=0.5,b=2,c=-1),wiel_kwadrat(-2,c=-1,a=0.5,b=2))
|
>>>
4
2.0
0
-2.0 -2.0
-3.0 -3.0
|
Jeżeli stosujemy zdefiniowane w definicji funkcji nazwy parametrów i nadajemy im wartość to nie jest ważne w jakiej kolejności je napiszemy. Ale czesto pisze się tylko wartości liczbowe. Wtedy kolejność jest ważana, bo przypisanie wartości do parametrów przebiega według ich kolejności w definicji funkcji.
def wiel_kwadrat(x,a=1,b=0,c=0):
if a==0: return "To funkcja liniowa"
else: return a*x**2+b*x+c
print(wiel_kwadrat(-2,0.5,2,-3))
print(wiel_kwadrat(-2,2,0.5,-3))
print(wiel_kwadrat(-2,0.5,-3,2))
|
>>>
-5.0
4.0
10.0
|
Funkcje rekurencyjne
Definiowanie funkcji rekurencyjnych, takich które odwołują się do samych siebie, generalnie skraca kod. Nie zawsze wiąże się to z efektywnością, czyli krótkim czasem obliczeń. Dotyczy ono funkcji definiowanych w postaci ciągu rekurencyjnego:
f(n)=F(f(n-1))
gdzie F(x) jest jakąś funkcja określoną na zbiorach wartości funkcji f(n). Definiowanie takiej funkcji można elegancko przedstawić w następującej postaci:
def f(n): if n==0: return f(0) else: return f(n)=F(f(n-1))
Przykładami takich funkcji są silnia: f(0)=1, f(n)=f(n-1)*n oraz kolejne wyrazy w n-tym rzędzie trójkąta Pascala: f(0,0)=1, f(1,0)=1, f(1,1)=1, f(n,0)=1, f(n,k)=f(n-1,k-1)+f(n-1,k) dla k≥0 i k≤n.
def silnia(n):
if n==0: return 1
else: return n*silnia(n-1)
print(silnia(1))
print(silnia(2))
print(silnia(10))
print(silnia(20))
|
def Pascal(n,k):
if ((n==0 and k==0) or (n==1 and k==0) or (n==1 and k==1)):
return 1
else:
if (n>1 and k==0): return 1
elif (n>1 and k<=n):
return Pascal(n-1,k-1)+Pascal(n-1,k)
else: return 0
print(Pascal(1,1))
print(Pascal(5,3))
print(Pascal(20,5))
print(Pascal(20,10))
|
>>>
1
2
3628800 2432902008176640000
|
>>>
1
10
15504
184756
|
Druga funkcja dla większych liczb wyraźnie zwalnia. Dzieje się to dlatego, że dla każdego n>1 uruchamia 2n-1 razy sama siebie i wielokrotnie wylicza ten sam wyraz znajdujący się na trójkącie Pascala. Przy takiej ilości obliczeń czas wyliczenia np. Pascal(100,20) musi być odczuwalny. To samo lepiej jest zrobić metodą iteracyjną:
def Pascal(n,k):
pas=[[1],[1,1]]
for j in range(2,n+1):
rzad=[pas[j-1][0]]
for i in range(1,j):
rzad.append(pas[j-1][i-1]+pas[j-1][i])
rzad.append(pas[j-1][j-1])
pas.append(rzad)
return pas[n][k]
print(Pascal(5,3))
print(Pascal(20,5))
print(Pascal(200,35))
|
>>>
10
15504
1407159943034720687164247227892896168560
|
Funkcja ta tylko pozornie wykonuje zbyt dużo obliczeń wyliczając wszystkie wartości w n-tym rzędzie trójkąta Pascala. Robi to jednak bardzo efektywnie i ostatecznie wyliczenie jej wartości dla dużych n nie trwa długo.