Wstęp do programowania Wykład 7 Funkcje Janusz Szwabiński Plan wykładu: Zastosowania funkcji Funkcje wbudowane Funkcje biblioteczne Funkcje użytkownika Zastosowania funkcji Z punktu widzenia programowania funkcje to nazwane sekwencje instrukcji. Tworzymy je z następujących powodów: nadawanie nazw pewnym sekwencjom instrukcji może uczynić kod bardziej czytelnym i skrócić czas usuwania błędów dzięki funkcjom kod źródłowy programu jest z reguły krótszy, ponieważ eliminują one potrzebę powtarzania tych samych sekwencji poleceń program jest łatwiejszy w utrzymaniu ponieważ dana sekwencja wprowadzana jest tylko raz, w definicji funkcji, ewentualnych zmian dokonujemy tylko w jednym miejscu podzielenie programu na części o dobrze zdefiniowanych funkcjonalnościach powoduje, że możemy każdą część debuggować z osobna dobrze zaprojektowane funkcje mogą być używane ponownie przez inne programy Funkcje wbudowane Interpreter Pythona oferuje pewną liczbę funkcji wbudowanych, czyli zawsze dostępnych dla programisty. Część z nich stosowaliśmy już na poprzednich wykładach intuicyjnie, bez wnikania w ich naturę. Z innych będziemy korzystać niezmiernie rzadko lub wcale (przynajmniej w ramach tego wykładu). Pełną listę tych funkcji można znaleźć pod adresem https://docs.python.org/2/library/functions.html (https://docs.python.org/2/library/functions.html). Polecenia wykorzystywane do konwersji typów to właśnie przykład funkcji wbudowanych, z których już korzystaliśmy: In [1]: int('32') Out[1]: 32
In [2]: int('hello') ------------------------------------------------------------------- -------- ValueError Traceback (most recent ca ll last) <ipython-input-2-3f8bc6d8fcab> in <module>() ----> 1 int('hello') ValueError: invalid literal for int() with base 10: 'Hello' In [3]: int(-2.3) Out[3]: -2 In [4]: float(32) Out[4]: 32.0 In [5]: str(32) Out[5]: '32' Funkcje do tworzenia typów złożonych również należą do tej grupy: In [6]: dict(a=1,b=2,c=3) Out[6]: {'a': 1, 'b': 2, 'c': 3} In [7]: list('anyway') Out[7]: ['a', 'n', 'y', 'w', 'a', 'y'] Do dyspozycji mamy kilka funkcji matematycznych:
In [8]: abs(-1) Out[8]: 1 In [9]: round(2.3) Out[9]: 2 In [10]: pow(2,3) Out[10]: 8 In [11]: oct(15) Out[11]: '0o17' In [12]: bin(3) Out[12]: '0b11' In [13]: hex(255) Out[13]: '0xff' Przy pomocy funkcji wbudowanych wczytujemy również dane z klawiatury: In [14]: s = input('--->') --->python In [15]: print(s) python
In [16]: l = input('podaj liczbę: ') Podaj liczbę: 4 In [22]: l**2 ------------------------------------------------------------------- -------- TypeError Traceback (most recent ca ll last) <ipython-input-22-0c9562729bb4> in <module>() ----> 1 l**2 TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int' In [23]: type(l) Out[23]: str In [18]: int(l)**2 Out[18]: 16 Ogólnie rzec biorąc, funkcja oczekuje podania na wejściu argumentu lub listy argumentów i produkuje jakiś wynik na wyjściu (w dalszej części wykładu omówimy kilka wyjątków). Wynik ten możemy przypisać zmiennej, lub bezpośrednio wyprowadzić na ekran. Funkcje biblioteczne Instalując dystrybucję Pythona, instalujemy również jego bibilotekę standardową, która zawiera bardzo dużo modułów z funkcjami rozszerzającymi podstawowe możliwości języka. Pełna dokumentacja biblioteki znajduje się pod adresem https://docs.python.org/3/ (https://docs.python.org/3/) Aby skorzystać z funkcji zdefiniowanej w bibliotece standardowej (i nie będącej funkcją wbudowaną), musimy załadować moduł, w którym jest ona zdefiniowana. Najbezpieczniejszy sposób zaimportowania definicji (o tym więcej w dalszej części kursu) ma składnię import nazwa_modułu Na przykład, jeśli chcemy korzystać z funkcji matematycznych, importujemy moduł mathz biblioteki standardowej:
In [24]: import math W wyniku wykonania tego polecenia interpreter tworzy obiekt modułu o nazwie math In [25]: print(math) <module 'math' (built-in)> Moduł zawiera szereg definicji. Aby je przeglądnąć, możemy wywołać na nim funkcję dir:
In [26]: dir(math)
Out[26]: [' doc ', ' loader ', ' name ', ' package ', ' spec ', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'hypot', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']
Dostęp do poszczególnych funkcji i stałych odbywa się w następujący sposób: nazwa_modułu.nazwa_funkcji In [27]: angle = math.pi In [28]: math.sin(math.pi/2) Out[28]: 1.0 Argumentem funkcji (ogólnie, nie tylko matematycznej) może być wyrażenie, w skład którego wchodzą inne funkcje:
In [29]: p = '3.14' math.sin(p) ------------------------------------------------------------------- -------- TypeError Traceback (most recent ca ll last) <ipython-input-29-54671218241a> in <module>() 1 p = '3.14' ----> 2 math.sin(p) TypeError: a float is required In [30]: math.sin(float(p)) Out[30]: 0.0015926529164868282 In [31]: math.sin(math.acos(0.0)) Out[31]: 1.0 Funkcje użytkownika Do tej pory rozważaliśmy jedynie gotowe funkcje, albo wbudowane w interpreter, albo zawarte w bibliotece standardowej. Python oczywiście oferuje nam również możliwość definiowania własnych funkcji. Składnia takiej definicji jest następująca: def nazwa_funckji(arg1,arg2,arg3,...): <instrukcje> return wynik Słowo kluczowe defwprowadza definicję funkcji. Po nim następuje nazwa funkcji oraz lista parametrów formalnych otoczonych nawiasami okrągłymi. Parametry formalne to po prostu argumenty funkcji. Może ich być dowolna liczba, również 0. Pierwszy wiersz definicji kończy się dwukropkiem, o którym wiemy już, że otwiera blok zawierający sekwencję instrukcji. Wszystkie polecenia w tym bloku powinny być wcięte w stosunku do słowa kluczowego def. Kolejne słowo kluczowe, returnzwraca wynik działania funkcji i przekazuje kontrolę do części programu, z poziomu którego funkcja zostanie później wywołana. Jeżeli efektem działania funkcji ma być np. wypisanie czegoś na ekranie, zamiast wyliczenia konkretnego wyniku, wiersz zawierający returnmożemy pominąć. Funkcja zwróci wówczas wartość specjalną None.
Definiowanie funkcji Rozważmy przykład: In [32]: def print_lyrics(): print("jak człowiek wierzy w siebie,") print("to cała reszta to betka,") print("nie ma takiej rury na świecie,") print("której nie można odetkać") W wyniku wykonania definicji tworzony jest obiekt funkcji o odpowiedniej nazwie: In [33]: print(print_lyrics) <function print_lyrics at 0x7ffbd835d158> In [34]: type(print_lyrics) Out[34]: function Funkcje zdefiniowane przez użytkownika wywołujemy tak samo, jak funkcje wbudowane: In [35]: print_lyrics() Jak człowiek wierzy w siebie, to cała reszta to betka, nie ma takiej rury na świecie, której nie można odetkać In [36]: test = print_lyrics() Jak człowiek wierzy w siebie, to cała reszta to betka, nie ma takiej rury na świecie, której nie można odetkać In [37]: print(test) None Raz zdefiniowana funkcja może zostać wykorzystana w innych definicjach:
In [38]: def repeat_lyrics(n): for i in range(n): print_lyrics() print("****") Nowa funkcja oczekuje jednego argumentu, dlatego wywołanie jej bez argumentów spowoduje błąd: In [39]: repeat_lyrics() ------------------------------------------------------------------- -------- TypeError Traceback (most recent ca ll last) <ipython-input-39-cd0a4288b2d7> in <module>() ----> 1 repeat_lyrics() TypeError: repeat_lyrics() missing 1 required positional argument: 'n' Natomiast wywołanie funkcji z odpowiednim argumentem zadziała w oczekiwany sposób: In [40]: repeat_lyrics(3) Jak człowiek wierzy w siebie, to cała reszta to betka, nie ma takiej rury na świecie, której nie można odetkać **** Jak człowiek wierzy w siebie, to cała reszta to betka, nie ma takiej rury na świecie, której nie można odetkać **** Jak człowiek wierzy w siebie, to cała reszta to betka, nie ma takiej rury na świecie, której nie można odetkać **** Dzięki własnościom Pythona proste funkcje potrafią być bardzo uniwersalne. Rozważmy np. funkcję, która dodaje do siebie dwa argumenty: In [41]: def dodaj(x,y): a = x + y return a Funkcja ta zadziała dla liczb całkowitych, rzeczywistych, zespolonych, łańcuchów znaków i list:
In [42]: print(dodaj(2,7)) #całkowite print(dodaj(3.14,7.2)) #rzeczywiste print(dodaj(1+1j,2+3j)) #zespolone print(dodaj('burczy','mucha')) #napisy print(dodaj([1,2],[3,4])) #listy 9 10.34 (3+4j) burczymucha [1, 2, 3, 4] Oczywiście, wszystkiego ze sobą nie doda: In [43]: print(dodaj('abc',1)) ------------------------------------------------------------------- -------- TypeError Traceback (most recent ca ll last) <ipython-input-43-71790bce1fea> in <module>() ----> 1 print(dodaj('abc',1)) <ipython-input-41-94965a25a78b> in dodaj(x, y) 1 def dodaj(x,y): ----> 2 a = x + y 3 return a TypeError: Can't convert 'int' object to str implicitly Nazwy funkcji i zmienne Powyżej widzieliśmy już, że funkcjom zdefiniowanym w Pythonie odpowiadają obiekty określonego typu. Obiekty te możemy przypisywać innym zmiennym: In [44]: fun = dodaj print(fun) <function dodaj at 0x7ffbd835dd08> In [45]: fun(1,2) Out[45]: 3 Argumenty i parametry Wewnątrz funkcji, argumenty jej wywołania są przypisane do zmiennych nazywanych parametrami. Parametry są zmiennymi lokalnymi, tzn. istnieją tylko wewnątrz bloku definiowanego przez funkcję:
In [46]: def concat(text1,text2): txt = text1 + text2 print(txt) In [47]: concat("nad naszą wsią ","przeleciał meteoryt.") Nad naszą wsią przeleciał meteoryt. Po zakończeniu działania funkcji wszystkie zmienne, które zostały zdefiniowane w jej ciele, zostają zniszczone. Dlatego próba odwołania do nich generuje błąd: In [48]: print(txt) ------------------------------------------------------------------- -------- NameError Traceback (most recent ca ll last) <ipython-input-48-54ac9f963c34> in <module>() ----> 1 print(txt) NameError: name 'txt' is not defined Przez referencję czy przez wartość? W dokumentacji Pythona możemy przeczytać (https://docs.python.org/3/tutorial/controlflow.html#definingfunctions (https://docs.python.org/3/tutorial/controlflow.html#defining functions)), że przekazywanie argumentów odbywa się zawsze przez wartość, przy czym ta wartość jest zawsze referencją do obiektu, a nie wartością tego obiektu. W praktyce oznacza to, że w przypadku niezmiennych typów danych funkcje w Pythonie działają tak, jakby ich argumenty przekazywane były przez wartość, natomiast w przypadku typów zmiennych na ogół jak przez referencję. Jako pierwszy przykład rozważymy funkcję, która zwiększa wartość argumentu o 1: In [49]: def add_1(number): number = number + 1 print("wartość wewnątrz funkcji: ", number) return number In [50]: number = 10 print("wartość przed wykonaniem funkcji: ", number) add_1(number) print("wartość po wykonaniu funkcji: ", number) Wartość przed wykonaniem funkcji: 10 Wartość wewnątrz funkcji: 11 Wartość po wykonaniu funkcji: 10
Wynik ten zrozumiemy, jeżeli przypomnimy sobie, jak działa Python wiersz number = number + 1 nie zmienia wartości obiektu, do którego odnosi się zmienna number, a tworzy nowy obiekt o wartości number + 1i następnie przypisuje mu etykietę number. Ponieważ wewnątrz funkcji ta zmienna ma charakter lokalny, wskazuje ona na inny obiekt, niż zmienna o tej samej nazwie zdefiniowana poza ciałem funkcji. Sytuacja będzie wyglądać trochę inaczej dla funkcji, której argumentem jest lista: In [51]: def changeme(mylist): mylist.append([1,2,3,4]) print("wartość wewnątrz funkcji: ", mylist) In [52]: lista = [10,20,30] print ("Wartość przed wykonaniem funkcji: ", lista) changeme(lista) print ("Wartość po wykonaniu funkcji: ", lista) Wartość przed wykonaniem funkcji: [10, 20, 30] Wartość wewnątrz funkcji: [10, 20, 30, [1, 2, 3, 4]] Wartość po wykonaniu funkcji: [10, 20, 30, [1, 2, 3, 4]] W tym wypadku oryginał również uległ zmianie, tzn. wynik jest taki, jak w przypadku przekazywania wartości przez referencję w przypadku tradycyjnych języków programowania. Jednak również w przypadku list (ogólniej zmiennych typów danych) możliwe jest zdefiniowanie funkcji, która zachowuje się tak, jakby argument przekazywany był przez wartość: In [53]: def changeme2(mylist): mylist = mylist[:] mylist.append([1,2,3,4]) print("wartość wewnątrz funkcji: ", mylist) In [54]: lista = [10,20,30] print("wartość przed wykonaniem funkcji: ", lista) changeme2(lista) print("wartość po wykonaniu funkcji: ", lista) Wartość przed wykonaniem funkcji: [10, 20, 30] Wartość wewnątrz funkcji: [10, 20, 30, [1, 2, 3, 4]] Wartość po wykonaniu funkcji: [10, 20, 30] Różnica w porównaniu z poprzednim przykładem jest taka, że teraz wewnątrz funkcji stworzyliśmy nowy obiekt w pamięci, a następnie przypisaliśmy mu lokalną nazwę mylist. Po zakończeniu funkcji nazwa ta przestała istnieć, i Python wrócił do nazwy globalnej mylist, która wskazuje na inny obiekt w pamięci.
Domyślne wartości argumentów Python wspiera kilka mechanizmów pozwalających na wywoływanie funkcji ze zmienną liczbą argumentów. Jednym z nich jest możliwość określenia wartości jednego lub większej liczby argumentów na liście argumentów formalnych: In [55]: def wypisz(wiadomosc, powtorz=1): print(wiadomosc*powtorz) In [56]: wypisz('zbliża się') Zbliża się In [57]: wypisz('sesja ',3) Sesja Sesja Sesja Wartość domyślna ustalana jest w momencie definiowania funkcji: In [58]: i = 5 def f(arg=i): print(arg) i = 6 f() 5 Uwaga! Wartość domyślna jest ustalana tylko raz. Ma to pewne konsekwencje w sytuacji, w której argumentem domyślnym jest obiekt, który może być zmieniany. Dla przykładu, następująca funkcja zapamiętuje argumenty jej wywołań: In [59]: def f(a, L=[]): L.append(a) return L In [60]: print(f(1)) print(f(2)) print(f(3)) [1] [1, 2] [1, 2, 3] Jeżeli jest to działanie niepożądane, musimy zmodyfikować nieco definicję funkcji:
In [61]: def f(a,l=none): if L is None: L = [] L.append(a) return L In [62]: print(f(1)) print(f(2)) print(f(3)) [1] [2] [3] Argumenty kluczowe Inna możliwość to użycie argumentów kluczowych w formie klucz=wartość: In [63]: def papuga(napiecie, stan='racja', akcja='voom', typ='norwegian Blue'): print("-- Ta papuga nie zrobiłaby", akcja, ',') print("jeśli przyłożysz do niej", napiecie, "woltów.") print("-- Śliczne upierzenie, ten", typ) print("-- Tak,", stan, "!") Taką funkcję możemy wywołać na kilka sposobów: In [64]: papuga(1000) -- Ta papuga nie zrobiłaby voom, jeśli przyłożysz do niej 1000 woltów. -- Śliczne upierzenie, ten Norwegian Blue -- Tak, racja! In [65]: papuga(akcja = 'VOOOOOM', napiecie = 1000000) -- Ta papuga nie zrobiłaby VOOOOOM, jeśli przyłożysz do niej 1000000 woltów. -- Śliczne upierzenie, ten Norwegian Blue -- Tak, racja! In [66]: papuga('tysiąc', stan = 'już wącha kwiatki od spodu') -- Ta papuga nie zrobiłaby voom, jeśli przyłożysz do niej tysiąc woltów. -- Śliczne upierzenie, ten Norwegian Blue -- Tak, już wącha kwiatki od spodu!
Jednak nie wszystkie wywołania będą prawidłowe: In [67]: papuga() #brakuje wymaganego argumentu niekluczowego (napiecie) ------------------------------------------------------------------- -------- TypeError Traceback (most recent ca ll last) <ipython-input-67-75833ee02938> in <module>() ----> 1 papuga() #brakuje wymaganego argumentu niekluczowego (napie cie) TypeError: papuga() missing 1 required positional argument: 'napiec ie' In [68]: papuga(napiecie=5.0,'trup') #niekluczowy argument za kluczowym File "<ipython-input-68-5bc21b90ed2e>", line 1 papuga(napiecie=5.0,'trup') #niekluczowy argument za kluczowym ^ SyntaxError: non-keyword arg after keyword arg In [69]: papuga(110,napiecie=220) #zduplikowana wartość parametru ------------------------------------------------------------------- -------- TypeError Traceback (most recent ca ll last) <ipython-input-69-b77f6d85e7e8> in <module>() ----> 1 papuga(110,napiecie=220) #zduplikowana wartość parametru TypeError: papuga() got multiple values for argument 'napiecie' In [70]: papuga(aktor="john Cleese") #nieznany klucz ------------------------------------------------------------------- -------- TypeError Traceback (most recent ca ll last) <ipython-input-70-80b90f873433> in <module>() ----> 1 papuga(aktor="john Cleese") #nieznany klucz TypeError: papuga() got an unexpected keyword argument 'aktor' W ogólności lista parametrów wywołania musi mieć jakiś argument pozycyjny, po którym następuje jakikolwiek argument kluczowy, gdzie klucze wybrane są z listy parametrów formalnych (w definicji funkcji). Nie jest ważne, czy parametr formalny ma wartość domyślną, czy też nie. Żaden z argumentów nie może otrzymać wartości więcej niż jeden raz nazwy parametrów formalnych odpowiadające argumentom pozycyjnym w wywołaniu nie mogą być w nim użyte jako kluczowe:
In [71]: def fun(a): pass In [72]: fun(0,a=0) ------------------------------------------------------------------- -------- TypeError Traceback (most recent ca ll last) <ipython-input-72-8b4daa4ef384> in <module>() ----> 1 fun(0,a=0) TypeError: fun() got multiple values for argument 'a' Lista dowolnych argumentów Gdy na liście parametrów pojawi się argument *nazwa, w momencie wywołania stanie się on krotką zawierającą wszystkie argumenty pozycyjne (niekluczowe) wymienione w wywołaniu funkcji za parametrami formalnymi: In [73]: def calkowite_wydatki(*wydatki): return sum(wydatki) In [74]: calkowite_wydatki(1,2,3,4) Out[74]: 10 In [75]: calkowite_wydatki(1,2,3,4,5,6,7,8,9,10) Out[75]: 55 Jeszcze jeden przykład: In [76]: def sklep_z_serami(rodzaj, *argumenty): print("-- Czy macie", rodzaj, '?') print("-- Przykro mi,", rodzaj, "właśnie się skończył.") for arg in argumenty: print(arg)
In [77]: sklep_z_serami('limburger', "Jest bardzo dojrzały, proszę pana.", "Jest naprawdę bardzo, BARDZO dojrzały, proszę pana.") -- Czy macie Limburger? -- Przykro mi, Limburger właśnie się skończył. Jest bardzo dojrzały, proszę pana. Jest naprawdę bardzo, BARDZO dojrzały, proszę pana. Podobnie, jeśli na liście parametrów formalnych pojawi się **nazwa, to przy wywołaniu funkcji przypisywany jest mu słownik zawierający wszystkie klucze, które nie odpowiadają nazwom parametrów formalnych: In [78]: def slownik(**wpisy): return wpisy In [79]: tel = slownik(yoda=1111, Chewie=2222, Darth=6666) print(tel) {'Darth': 6666, 'Chewie': 2222, 'Yoda': 1111} I jeszcze jeden przykład: In [80]: def sklep_z_serami(rodzaj, *argumenty, **klucze): print("-- Czy macie", rodzaj, '?') print("-- Przykro mi,", rodzaj, "właśnie się skończył.") for arg in argumenty: print(arg) print('-'*40) for kl in klucze.keys(): print(kl, ':', klucze[kl]) In [81]: sklep_z_serami('limburger', "Jest bardzo dojrzały, proszę pana.", "Jest naprawdę bardzo, BARDZO dojrzały, proszę pana.", klient='john Cleese', wlasciciel='michael Palin', skecz='skecz ze sklepem z serami') -- Czy macie Limburger? -- Przykro mi, Limburger właśnie się skończył. Jest bardzo dojrzały, proszę pana. Jest naprawdę bardzo, BARDZO dojrzały, proszę pana. ---------------------------------------- skecz : Skecz ze sklepem z serami wlasciciel : Michael Palin klient : John Cleese
Formy lambda Formy lambda to zapożyczenie z języków funkcjonalnych. To małe, anonimowe (nienazwane) funkcje, które mogą zostać użyte we wszystkich miejscach, w których wymagane są obiekty funkcji. Składniowo ograniczone są do pojedynczego wyrażenia: In [82]: def dodawacz(n): return lambda x:x+n In [83]: f = dodawacz(42) In [84]: print(f(0)) print(f(10)) 42 52 In [85]: g = dodawacz(3.14) In [86]: print(g(1)) print(g(10)) 4.140000000000001 13.14 Inny przykład: In [87]: pary = [(1,'jeden'),(2,'dwa'),(3,'trzy'),(4,'cztery')] In [88]: pary.sort(key=lambda para: para[1]) In [89]: print(pary) [(4, 'cztery'), (2, 'dwa'), (1, 'jeden'), (3, 'trzy')] Jeszcze inny przykład:
In [90]: def sq(x): return x**2 liczby = [1,2,3,4,5,6,7,8] list(map(sq,liczby)) Out[90]: [1, 4, 9, 16, 25, 36, 49, 64] In [91]: liczby = [1,2,3,4,5,6,7,8] list(map(lambda x: x**2,liczby)) Out[91]: [1, 4, 9, 16, 25, 36, 49, 64]