.:: KURS PROGRAMOWANIA W JĘZYKU PYTHON ::.




- Definiowanie funkcji


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ć:

* nagłówek funkcji obejmujący
- nazwę funkcji, która pozwoli zidentyfikować funkcję w pozostałej części programu
- listę argumentów, która funkcja otrzymuje na początku działania programu
* ciało funkcji, zawierające instrukcje, które zostaną wykonane w momencie wywołania (użycia) funkcji
- jeżeli funkcja ma zwracać jakiś rezultat, musi zawierać odpowiednią instrukcję

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



- Usuwanie i redefiniowanie funkcji


Zdefiniowaną uprzednio funkcję możemy w dowolnym miejscu usunąć, posługując się instrukcją del. A zatem:


>>> del pierw


I teraz:


>>> pierw(3)

Traceback (most recent call last):
  File "&<pyshell&#35;6>", line 1, in -toplevel-
    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



- Parametry formalne i aktualne, zmienne lokalne


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:

* dostępna jest tylko w obrębie funkcji; a zatem:


>>> pierw(7)
2.6457513110645907
>>> n

Traceback (most recent call last):
  File "&<pyshell&#35;2>", line 1, in -toplevel-
    n
NameError: name 'n' is not defined


* "przykrywa" zmienną o tej samej nazwie istniejącą poza funkcją


>>> 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).



- Wiele argumentów, wiele rezultatów


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&#35;8>", line 1, in -toplevel-
    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)



- Domyślne i nazwane wartości argumentów


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&#35;17>", line 1, in -toplevel-
    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&#35;23>", line 1, in -toplevel-
    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



- Funkcje z nieznaną liczbą parametrów


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


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:])     &#35; 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&gt;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&gt;2:
            return n
      a, b = 0, 1             &#35; 0 podstawiamy pod a, 1 pod b
      for x in range(1, n):   &#35; potrzebujemy n-1 iteracji
            a, b = b, a+b     &#35; 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



- Przykład: losowanie tekstu


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):
      &#35; 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)):            &#35; między 1 a 10 liter w wyrazie
                  tekst+=chr(randint(ord('a'),ord('z')))   &#35; litery od a do z
            tekst+=" "
      return (tekst[:-1]+".").capitalize()                 &#35; z dużej litery, a na końcu kropka


Wypróbujmy:


>>> brednie()
'Phehwbbxjb gfhcgj uygnlabrog maicfvg xwi.'
>>> brednie()
'Wjzxo xqvgimdsh mvbr gwd lorcf.'
>>> brednie(1)
'Hhmgicsqnv.'
>>> brednie(4)
'Kxz adlokyjetg bwj wyranlsbrn.'
>>> brednie(9)
'Drdoy nekg lsnqdfysh cqgj qahuvjdqdk ijxjoxqtr khdjewr cbssjwo ftkdgeyal.'



- Przykład: wyliczanie odległości między dwoma punktami na płaszczyźnie


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.


&#35; program wylicza odległość między dwoma punktami
from math import hypot
p1x,p1y = input ("Podaj współrzędne poziomą i pionową pierwszego punktu > ")
p2x,p2y = input ("Podaj współrzędne poziomą i pionową drugiego punktu > ")
print "Odległość między tymi punktami wynosi %.3f" % hypot(p1x-p2x,p1y-p2y)


Wypróbujmy:


>>> ================================ RESTART ================================
>>>
Podaj współrzędne poziomą i pionową pierwszego punktu > 0,1
Podaj współrzędne poziomą i pionową drugiego punktu > 1,0
Odległość między tymi punktami wynosi 1.414


>>> ================================ RESTART ================================
>>>
Podaj współrzędne poziomą i pionową pierwszego punktu > -1,-2
Podaj współrzędne poziomą i pionową drugiego punktu > 3,1
Odległość między tymi punktami wynosi 5.000
>>> 



- Ćwiczenia kontrolne


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).

III. Zdefiniuj funkcję "sylaby", która dla parametru będącego wyrazem w języku polskim, zwróci listę jego sylab. Funkcja nie musi działać perfekcyjnie dla każdego wyrazu (wyjątków itp.), ale im więcej przypadków obsłuży prawidłowo, tym lepiej.





-- back