TI/Funkcje
Spis treści
Wprowadzenie
Funkcje to fragmenty programu, które można używać wielokrotnie. Możemy nadać nazwę jakiejś grupie instrukcji i uruchamiać ją używając tej nazwy w dowolnym miejscu programu tyle razy ile chcemy. Operację taką nazywamy wywołaniem funkcji. Tak naprawdę używaliśmy już wielu wbudowanych funkcji takich jak len czy range. Funkcje są prawdopodobnie najważniejszym składnikiem budującym każdy program (w dowolnym języku programowania), musimy więc poznać je dokładniej.
Funkcje definiujemy w programie używając słowa kluczowego def. Po tym słowie następuje nazwa funkcji oraz para nawiasów, które mogą zawierać nazwy zmiennych (parametrów funkcji), a linia kończy się dwukropkiem. Parametry służą do przekazywania funkcji różnych wartości tzw. wejściowych i uzyskiwania zależnych od nich wyników. Potem następuje blok linii programu, które tworzą funkcję. Poniżej jest przedstawiony przykład definicji i wywołania funkcji.
Przykład
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Nazwa pliku: function1.py
# początek definicji funkcji
def przywitajSie():
print 'Sie ma!' # instrukcje tworzące funkcję
# koniec definicji funkcji
przywitajSie() # wywołanie (użycie) funkcji
przywitajSie() # ponowne wywołanie funkcji
- Rezultat:
Sie ma!
Sie ma!
- Jak to działa?
Definiujemy funkcję o nazwie przywitajSie w sposób opisany powyżej. Ponieważ funkcja ta nie używa żadnych parametrów, nie ma więc żadnych zmiennych wewnątrz nawiasów. Zauważ, że możemy wywołać tę samą funkcję dwukrotnie co oznacza, że nie musimy dwa razy pisać tego fragmentu programu.
Parametry funkcji
Jak wiemy, funkcja może mieć parametry wywołania. Są to wartości, które dostarczamy funkcji, i których możemy użyć wewnątrz funkcji. Parametry zachowują się tak samo jak zmienne z tą różnicą, że ich wartości są nadawane w momencie wywołania funkcji.
Parametry wpisuje się w nawiasach podczas definiowania funkcji oddzielając je przecinkami. Kiedy wywołujemy funkcję, ich wartości podajemy w taki sam sposób. Uwaga: nazwy, które podaliśmy podczas definiowania funkcji nazywamy parametrami, ale wartości podane przy wywołaniu funkcji nazywamy argumentami.
Przykład
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Nazwa pliku: func_param.py
def printMax(a, b):
if a > b:
print a, 'jest większe'
elif a == b:
print a, 'jest równe', b
else:
print b, 'jest większe'
printMax(3, 4) # bezpośrednio podane wartości
x = 5
y = 7
printMax(x, y) # zmienne podane jako argumenty
- Rezultat:
4 jest większe
7 jest większe
- Jak to działa?
Definiujemy tutaj funkcję o nazwie printMax, która używa dwóch parametrów o nazwach a i b. Funkcja ma znaleźć większą z liczb używając instrukcji if..else a następnie wypisać, która liczba jest większa. Podczas pierwszego użycia funkcji printMax podaliśmy bezpośrednio liczby jako argumenty. Przy drugim wywołaniu jako argumentów użyliśmy zmiennych. Instrukcja printMax(x,y) przypisuje wartość pierwszego argumentu (zmiennej x) do parametru a oraz wartość drugiego argumentu (zmiennej y) do parametru b. Samo wyznaczenie większej liczby i jej wypisanie odbywa się wewnątrz funkcji tak samo za każdym razem.
Zmienne lokalne
Jeśli wewnątrz funkcji zadeklarujemy zmienne, nie mają one żadnego związku ze zmiennymi poza funkcją, nawet jeśli mają takie same nazwy. Mówimy, że nazwy zmiennych wewnątrz funkcji są lokalne dla tej funkcji. Obszar, w którym możemy danej zmiennej używać nazywamy zasięgiem zmiennej. Każda zmienna zdefiniowana w funkcji ma zasięg poczynając od miejsca, w którym po raz pierwszy przypiszemy jej wartość, aż do końca funkcji.
Przykład
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Nazwa pliku: func_local.py
x = 50
def func(x):
print 'x wynosi', x
x = 2
print 'Zmieniono lokalne x na', x
func(x)
print 'x ciągle wynosi', x
- Rezultat:
x wynosi 50
Zmieniono lokalne x na 2
x ciągle wynosi 50
- Jak to działa?
Kiedy po raz pierwszy wypisujemy wartość zmiennej x, w pierwszej linii funkcji func, użyta zostaje wartość parametru zadeklarowanego w bloku głównym, powyżej definicji funkcji.
Następnie przypisujemy zmiennej x wartość 2. Nazwa x jest lokalna dla funkcji, tak więc gdy zmieniamy wartość x wewnątrz funkcji, zmienna x zdefiniowana w bloku głównym pozostaje niezmieniona.
W momencie ostatniego wywołania polecenia print wypisujemy wartość zmiennej x zdefiniowanej w bloku głównym potwierdzając, że nie została ona zmieniona przez przypisanie nowej wartości lokalnej zmiennej x wewnątrz poprzednio wywołanej funkcji.
Dostęp do zmiennych globalnych
Wewnątrz funkcji, odczytywać zmienne zdefiniowane na wyższym poziomie można bez problemu. Jeśli natomiast chcemy przypisywać wartość do takiej zmiennej, zdefiniowanej poza zakresem danej funkcji, to trzeba tą zmienną określić jako globalną. Służy do tego deklaracja global. Bez użycia tej deklaracji nie jest możliwe przypisanie wartości zmiennej zdefiniowanej na zewnątrz funkcji — jedyny efekt takiego przypisania to utworzenie lokalnej zmiennej o tej nazwie, która zasłania zmienną globalną.
Należy powiedzieć precyzyjnie, co tutaj znaczy słowo globalna. W Pythonie zmienną globalną nazywa się zmienną zdefiniowaną bezpośrednio w jakimś module, a nie wewnątrz funkcji czy klasy w tym module. Takie znaczenie jest zupełnie inne niż np. w C, gdzie zmienna globalna o danej nazwie w całym programie może być tylko jedna.
Przykład
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Nazwa pliku: func_global.py
x = 50
def func():
global x
print 'x wynosi', x
x = 2
print 'Zmieniono zewnętrzny x na', x
func()
print 'x wynosi teraz', x
- Rezultat:
x wynosi 50
Zmieniono zewnętrzny x na 2
x wynosi teraz 2
- Jak to działa?
Deklaracja global została użyta do poinformowania, że chodzi nam o globalną, zewnętrzną zmienną x. Jeśli teraz przypisujemy wartość zmiennej x wewnątrz funkcji, zmieniamy wartość x z głównego bloku.
Można określić więcej niż jedną zmienną globalną w tej samej deklaracji global, na przykład:
global x, y, z.
Wyskakując na moment do rozdziału o modułach, należy wspomnieć, że do do zmiennych zdefiniowanych „gdzie indziej“ można się odwoływać podając nazwę modułu który je zawiera. Na przykład zmienna argv zdefiniowana w module sys jest z poza tego modułu dostępna jako sys.argv. Dlatego atrybut global służy do dostępu do zmiennych zdefiniowanych na poziomie modułu, ale tylko z wewnątrz tego modułu.
Domyślne wartości argumentów
W przypadku pewnych funkcji nie zawsze chcemy podawać argumenty odpowiadające wszystkim parametrom określonych w definicji funkcji. Parametry mogą być „opcjonalne“. Nie musimy wtedy ich podawać w wyłaniu funkcji, i jeśli tego nie zrobimy zostaną użyte wartości domyślne. Wartości domyślne definiujemy dopisując w definicji funkcji po nazwie parametru znak = i wartość domyślną.
Przykład
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Nazwa pliku: func_default.py
def napisz(wiadomosc, razy=1):
print wiadomosc * razy
napisz('Dzień')
napisz('dobry', 5)
- Rezultat:
Dzień
dobrydobrydobrydobrydobry
- Jak to działa?
Funkcja o nazwie napisz ma za zadanie wypisać tekst tyle razy ile podano. Jeśli nie podamy ile razy, wtedy domyślnie tekst wypisywany jest jeden raz. Osiągamy to podając wartość domyślną argumentu dla parametru razy równą 1.
Podczas pierwszego wywołania napisz podajemy tylko tekst i wypisywany jest on jeden raz. Przy drugim wywołaniu napisz podajemy zarówno tekst jak i argument 5 mówiący, że chcemy napisać tekst 5 razy.
- Uwaga
- tylko parametry z końca listy parametrów mogą mieć wartości domyślne. Nie można mieć parametru z wartością domyślną przed parametrem bez wartości domyślnej w kolejności deklaracji na liście parametrów funkcji. Dzieje się tak dlatego, że wartości są przypisywane parametrom zgodnie z ich pozycją na liście. Na przykład deklaracja def func(a, b=5) jest prawidłowa, ale deklaracja def func(a=5, b) nie jest, bo wówczas w wywołaniu func(11) nie byłoby jasne, czy podany argument tyczy się a czy b.
Zaawansowane sposoby przekazywania argumentów
Argumenty nazwane
Jeśli chcemy skorzystać z funkcji o dużej liczbie parametrów, wygodne może być jawne użycie nazw parametrów. Działa to tak, że w wywołaniu funkcji podajemy zarówno nazwę parametru, jak i, tak jak poprzednio, właściwy argument. Podanie wartości danego parametru poprzez taki nazwany argument (po angielsku argumenty takie nazywa się keyword arguments), powoduje, że jego wartość w wywołaniu funkcji jest ustalona i zostaje on wyłączony z normalnego przypisywania argumentów do parametrów według kolejności.
Rozpoznawanie argumentów po nazwie, a nie kolejności, ma dwie zalety:
- użycie funkcji jest prostsze, bo nie musimy się martwić o kolejność argumentów
- możemy podać wartości tylko tych argumentów, których chcemy (pod warunkiem, że parametry które pomieliśmy mają określone wartości domyślne).
Przykład
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Nazwa pliku: func_key.py
def func(a, b=5, c=10):
print 'a wynosi', a, 'oraz b wynosi', b, 'oraz c wynosi', c
func(3, 7)
func(25, c=24)
func(c=50, a=100)
- Rezultat:
a wynosi 3 oraz b wynosi 7 oraz c wynosi 10
a wynosi 25 oraz b wynosi 5 oraz c wynosi 24
a wynosi 100 oraz b wynosi 5 oraz c wynosi 50
- Jak to działa?
Funkcja o nazwie func ma jeden parametr bez wartości domyślnej, oraz dwa parametry posiadające wartości domyślne.
- Przy pierwszym użyciu, func(3, 7), parametr a dostaje wartość 3, parametr b dostaje wartość 7, a parametr c dostaje wartość domyślną 10.
- Przy drugim użyciu, func(25, c=24), zmienna a dostaje wartość 25 ze względu na pozycję na liście argumentów. Parametr c dostaje wartość 24 ze względu na użycie jego nazwy. Zmienna b dostaje wartość domyślną 5.
- Przy trzecim użyciu, func(c=50, a=100), używamy wyłącznie nazw jako argumentów do podania wartości. Zauważ, że podajemy wartość parametru c przed parametrem a, mimo że a jest podany przed c w definicji funkcji.
Użycie argumentu nazwanego nazwą która nie odpowiada żadnemu parametrowi jest błędem.
Zmienna liczba parametrów
Czasami wygodnie byłoby zdefiniować funkcję, która przyjmowałaby dowolną liczbę parametrów, tak jak polecenie print. Można to uzyskać dodając na końcu listy parametrów zmienną, do której zostanie wpisana lista „nadmiarowych“ argumentów. Ten specjalny parametr jest oznaczony przez gwiazdkę przed jego nazwą.
Przykład
>>> def powypisuj(rozdzielacz, pierwsze, *reszta)
... """Wypisuje podane słowa używając pierwszego argumentu
... jako rozdzielacza."""
... print pierwsze
... for slowo in reszta:
... print rozdzielacz, slowo,
...
...
>>> powypisuj('-x-', 'ala', 'ma', 'kota')
ala-x-ma-x-kota
>>> powypisuj('-x-', 'ala')
ala
>>> powypisuj('-x-')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: powypisuj() takes at least 2 arguments (1 given)
W jaki sposób argumenty zostają przyporządkowane?
Argumenty nazwane nie mogą występować tylko na końcu listy argumentów, lub patrząc od drugiej strony, argumenty nienazwane muszą znajdować się na początku. Podane argumenty są przyporządkowywane parametrom w ten sposób, że najpierw argumenty bez zadanej nazwy parametru zostają przydzielone do odpowiednich parametrów w kolejności, a potem argumenty nazwane zostają przydzielone do odpowiednich parametrów według nazwy. Tak więc, jeśli argumentów jest więcej niż parametrów „bez gwiazki“, to trafią one do zmiennej „z gwiazdką“.
Błędem jest jeśli jakiemuś parametrowi zostaną przyporządkowane dwa argumenty &mdash na przykład jeden zwykły, a drugi nazwany.
Zmienna liczba argumentów nazwanych
Istnieje jeszcze jeden sposób przekazywania zmiennej liczby parametrów, w formie słownika. Ponieważ słowniki będą omówione dopiero w rozdziale o strukturach danych, to reszta tego podrozdziału może być niezrozumiała. Jeśli tak, to nie przejmuj się i najpierw poczytaj o słownikach.
Funkcja może mieć jeszcze jeden specjalny parametr — poprzedzony dwoma gwiazkami, zwyczajowo nazywający się **kwargs. W wywołaniu takiej funkcji można użyć argumentów nazwanych, których nazwy nie odpowiadają żadnym parametrom funkcji. Te argumenty zostają wstawione do słownika, który zostaje przekazany jako parametr kwargs do funkcji. W tym słowniku, nazwy argumentów są kluczami, a ich wartości wartościami przypisanymi do tych kluczy.
Przykład
>>> def f(*args, **kwargs):
... print 'args =', args
... print 'kwargs =', kwargs
...
>>> f(1, 2, 3, a=4, b=5, c=6)
args = (1, 2, 3)
kwargs = {'a': 4, 'b': 5, 'c': 6}
Polecenie return
Dotychczas rozważane funkcje wykonywały pewne polecenia ale nie oczekiwaliśmy od nich aby zwracały jakieś wartości (tzn. nie potrzebowaliśmy aby wartości wyliczone albo obiekty wytworzone w funkcji były dostępne w miejscu, z którego wywołaliśmy funkcję).
Polecenie return jest używane do powrotu z funkcji czyli natychmiastowego przerwania jej wykonania i zwrócenia wartości.
Przykład
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Nazwa pliku: func_return.py
def maksimum(x, y):
if x > y:
return x
else:
return y
print maksimum(2, 3)
- Rezultat:
3
- Jak to działa?
Funkcja maksimum zwraca większy ze swoich parametrów, czyli liczb przekazanych do funkcji. Do znalezienia większej z liczb użyta jest instrukcja if..else, po znalezieniu ta wartość jest zwracana.
- Uwaga
- instrukcja return bez podanej wartości jest równoważna instrukcji return None. None jest obiektem języka Python reprezentującym brak wartości. Jest on używany na przykład do zaznaczenia, że jakaś zmienna nie ma wartości.
Każda funkcja domyślnie zawiera instrukcję return None na końcu. Można to zaobserwować wywołując print jakasFunkcja(), gdzie funkcja jakasFunkcja nie używa instrukcji return, na przykład:
Przykład
>>> def jakasFunkcja():
... pass
...
>>> print jakasFunkcja()
None
Instrukcja pass jest w języku Python używana do oznaczenia pustego bloku instrukcji.
Uwaga:
Istnieje wbudowana funkcja max, która wyszukuje większą z liczb. Jeśli tylko to możliwe należy używać funkcji wbudowanych, a nie pisać własne.
Opisy wbudowane w funkcje
W Pythonie istnieje wygodna możliwość uzupełniania programu o objaśnienia–dokumentację. W ogólności programista może umieszczać swoje uwagi jako komentarze (wykorzystując znak #), które są po prostu ignorowane w trakcie wykonywania programu, lub jako napisy wstawione na początku definicji funkcji (wykorzystując znaki normalnie używane do ograniczania napisów, czyli ' i "). Ta druga forma ma tę przewagę, że mimo iż w trakcie normalnego wykonywania programu jest pomijana podobnie jak zwykłe komentarze, to te napisy zostają także zachowane od specjalnej zmiennej i można je obejrzeć. W trybie interaktywnej pracy z Pythonem służy do tego funkcja help.
Linie tekstu z dokumentacją nazywają się po angielsku documentation strings, co skraca się do docstrings. Przez docstring rozumie się tylko taki napis umiejscowiony na początku funkcji (lub klasy czy modułu, mechanizm jest ten sam) stanowiący dokumentację.
Przykład
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Nazwa pliku: func_doc.py
def printMax(x, y):
'''Konwertuje obydwa obiekty na int i wypisuje większą z dwóch liczb.
Niepowodzenie konwersji powoduje rzucenie wyjątku ValueError.
'''
x = int(x) # konwersja do liczby całkowitej (integer), jeśli możliwa
y = int(y)
if x > y:
print x, 'jest większa'
else:
print y, 'jest większa'
printMax(3, 5)
print(printMax.__doc__)
- Rezultat:
5 jest większa
Konwertuje obydwa obiekty na int i wypisuje większą z dwóch liczb.
Niepowodzenie konwersji powoduje rzucenie wyjątku ValueError.
Używanie DocString
Napis w pierwszym logicznym wierszu funkcji staje się docstring-iem dla tej funkcji. Dodajmy, że DocStrings również stosują się do modułów i klas, o których będzie mowa później.
Konwencją stosowaną dla dokumentacji jest napis w kilku wierszach. Pierwszy wiersz zaczyna się wielką literą i kończy kropką. Następny wiersz jest pusty, a w trzecim wierszu rozpoczynamy dokładniejsze objaśnienia. Jest bardzo wskazane, aby stosować się do tej konwencji opisując napisane przez siebie funkcje.
Możemy odczytać dosctring jakiejś funkcji (na przyklad printMax) używając jej atrybutu (czyli nazwy należącej do funkcji) __doc__ (zauważ użycie po dwóch znaków podkreślenia). Atrybuty są częścią obiektów, którymi są też funkcje. O obiektach będzie mowa w następnych rozdziałach.
Używając pomocy help() w Pythonie robimy użytek z docstringow. Funkcja help() pobiera z funkcji jej atrybut __doc__ i wypisuje go. Możesz to wypróbować na powyższej funkcji — napisz w swoim programie help(printMax). Pamiętaj, żeby nacisnąć klawisz q, żeby zakończyć pomoc help.
Automatyczne narzędzia programistyczne pobierają dokumentację z twoich programów właśnie w taki sposób. Dlatego dobrze jest używać docstrings do każdej napisanej funkcji. Komenda pydoc, dostępna w dystrybucji języka Python działa podobnie do funkcji help() używając docstrings.
Co powinna zawierać dokumentacja funkcji
Nawet krótka dokumentacja funkcji może być bardzo pomocna. Co powinno się w niej znaleźć?
- Jednolinijkowy opis działania wykonywanego przez funkcję: „Mnoży wektorowo argumenty i zwraca ślad wyniku.”
- Nie ma potrzeby pisać, czym jest opisywany obiekt (w tym przypadku funkcją), ani duplikować listy parametrów, bo użytkownik i tak to wie z opisu wygenerowanego automatycznie:
„To jest funkcja która ma parametry a, b, c.” - Dobrze jest napisać co funkcja zwraca, jeśli coś zwraca.
- Jeśli funkcja ma być wykorzystywana przez innych jako funkcja biblioteczna, to opis musi być znacznie dłuższy — powinien zawierać głębsze wyjaśnienie tego co funkcja robi, jeśli to jest istotne jakie ma ograniczenia i warunki działania.
- W przypadku funcji złożonych obliczeniowo, dobrze jest napisać jak czas wykonywania funkcji rośnie wraz ze wzrostem rozmiaru argumentów wejściowych.
- Dobrze jest podać przykład użycia funkcji i oczekiwanego wyniku. Zob. http://docs.python.org/library/doctest.html .
Docstrings to ważne narzędzie, którego warto używać, bo dzięki dokumentacji program jest łatwiejszy do zrozumienia.
Ćwiczenia
Proszę napisać następujące funkcje:
- Która wypisuje sumę dwóch argumentów.
- Która zwraca sumę dwóch argumentów.
- Wypisującą wynik dzielenia zmiennoprzecinkowego dla dwóch dowolnych argumentów będących liczbami.
- Obliczającą i zwracającą pierwiastek zadanego stopnia — w liście parametrów podajemy liczbę podpierwiastkową i stopień pierwiastka, domyślnie niech wynosi on dwa.
- Wypisującą równanie prostej przechodzącej przez współrzędne dwóch zadanych punktów.
- Wypisującą jedno lub dwa rozwiązania równania kwadratowego [math]a x^2+b x +c = 0[/math], lub informującą, że nie ma rozwiązań rzeczywistych. Parametrami funkcji niech będą [math]a[/math], [math]b[/math], [math]c[/math] — współczynniki równania kwadratowego.
- Zwracającą silnię argumentu. Zadanie proszę rozwiązać na dwa sposoby — raz z użyciem pętli, a drugi raz rekurencyjnie.
- Wypisującą żądaną liczbę wyrazów ciągu Fibbonacciego. ([math]F_0=0[/math], [math]F_1=1[/math], [math]F_n=F_{n-1}+F_{n-2}[/math])
- Służącą do obliczania [math]n[/math]-tego elementu ciągu arytmetycznego o zadanym wyrazie początkowym i różnicy. Funkcja powinna dawać możliwość wypisywania wyników pośrednich (elementów ciągu od pierwszego do przedostatniego obliczonego).
- Służącą do obliczania [math]n[/math]-tego elementu ciągu geometrycznego o zadanym wyrazie początkowym i ilorazie. Funkcja powinna dawać możliwość wypisywania wyników pośrednich (elementów ciągu od pierwszego do przedostatniego obliczonego).
- Służącą do obliczania [math]n[/math]-tej sumy częściowej ciągu arytmetycznego o zadanym wyrazie początkowym i różnicy. Funkcja powinna dawać możliwość wypisywania wyników pośrednich (kolejno obliczanych elementów ciągu i sum).
- Służącą do obliczania [math]n[/math]-tej sumy częściowej ciągu geometrycznego o zadanym wyrazie początkowym i ilorazie. Funkcja powinna dawać możliwość wypisywania wyników pośrednich (kolejno obliczanych elementów ciągu i sum).
- Służącą do obliczania [math]n[/math]-tej sumy częściowej ciągu [math]1/n^m[/math], gdzie [math]n[/math] i [math]m[/math] są parametrami. Funkcja powinna dawać możliwość wypisywania wyników pośrednich (kolejno obliczanych elementów ciągu i sum).
- Funkcję obliczającą długość wektora jedno-, dwu- lub trójwymiarowego.
- Funkcję, która zwróci napis będący reprezentacją podanej liczby całkowitej. Tę „konwersję na napis” wykonaj cyfra po cyfrze.