JEZYK PROGRAMOWANIA PYTHON: FUNKCJE, SŁOWNIKI E. Dyguda-Kazimierowicz 1 Definiowanie i korzystanie z funkcji Funkcje pozwalają na wyodrębnienie często powtarzanych działań, dzięki czemu struktura kodu staje się bardziej czytelna, a całość zdecydowanie krótsza. Możliwość wielokrotnego wywoływania funkcji z różnymi argumentami podnosi uniwersalność takiego rozwiązania. Funkcje mogą ponadto znaleźć się poza programem głównym, np. zebranie w osobnych plikach funkcji o pokrewnym zastosowaniu ułatwia zarządzanie kodem dużych programów. Własne moduły z definicjami funkcji mogą być załadowane w dowolnym momencie pracy przez wiele programów. Instrukcja def, umożliwiająca tworzenie funkcji, jest instrukcją złożoną: zawiera zakończony dwukropkiem wiersz nagłówkowy (nadający nazwę i tworzący obiekt funkcji) i wcięty blok instrukcji, które wykonują określone zadanie. Funkcje w Pythonie stanowią kolejny typ obiektowy, zatem instrukcję def traktować należy jak instrukcję przypisania. Z kolei samo przypisanie nazwy do funkcji nie oznacza jej uruchomienia to tylko opakowanie bloku instrukcji, których działanie ma miejsce dopiero po wywołaniu funkcji w dalszej części kodu. W Pythonie nie ma potrzeby wcześniejszego deklarowania ani typu pobieranych argumentów, ani typu zwracanych wartości. W rzeczywistości dana funkcja może działać na argumentach o różnych typach, np. dodawać liczby lub łańcuchy (zależnie od tego, z czym zostanie wywołana). Sama definicja funkcji może znaleźć się w kodzie w dowolnym miejscu poprzedzającym jej wykonanie, ale dla czytelności programu warto umieszczać wszystkie definicje funkcji na początku. Najprostsza funkcja nie pobiera żadnych argumentów i nie zwraca wartości (w rzeczywistości zwracany jest obiekt pusty typu None): >>> def komunikat(): print Wykonanie zakonczone sukcesem >>> komunikat() Wykonanie zakonczone sukcesem 1
1 DEFINIOWANIE I KORZYSTANIE Z FUNKCJI >>> test = komunikat() Wykonanie zakonczone sukcesem >>> print test None Nawiasy po nazwie funkcji oznaczają jej wywołanie podanie samej nazwy powoduje tylko wyświetlenie informacji o obiekcie. Ponieważ funkcja jest obiektem, można ją przypisać innej zmiennej. W powyższym przykładzie zmiennej test przypisywany był wynik działania funkcji komunikat(), czyli obiekt None; poniżej zmiennej x przypisany zostaje obiekt funkcji: >>> komunikat <function komunikat at 0x4021aa74> >>> x=komunikat >>> x <function komunikat at 0x4021aa74> >>> x() Wykonanie zakonczone sukcesem Wartość zwracana funkcji, czyli wynik jej działania, przekazywany następnie obiektowi wywołującemu, określa się za pomocą instrukcji return. Niezależnie od miejsca wystąpienia return w bloku instrukcji funkcji, instrukcja ta powoduje zakończenie działania funkcji i powrót sterowania do programu wywołującego: >>> def komunikat_2(): return wykonano >>> def komunikat_3(): return WYKONANO print To sie juz nie wyswietli >>> test_2 = komunikat_2() >>> test_3 = komunikat_3() >>> print test_2 wykonano >>> print test_3 WYKONANO 2
1 DEFINIOWANIE I KORZYSTANIE Z FUNKCJI Jeżeli wynikiem działania funkcji ma być kilka wartości, najlepiej zwracać je w postaci krotki. Rozpakowanie krotki pozwala na jednoczesne przypisanie wartości poszczególnych pozycji osobnym zmiennym: >>> def komunikat_4(): return Wykonanie, zakonczone, sukscesem >>> A, B, C = komunikat_4() >>> print B zakonczone Użyteczność funkcji wynika w dużej mierze z możliwości przekazywania im w momencie wywołania określonych argumentów. Wartości tych argumentów (a dokładniej odwołania do nich) przypisywane są odpowiednim parametrom zdefiniowanym w nawiasach po nazwie funkcji: >>> def komunikat_5(imie): #imie -> parametr funkcji return Co slychac, +imie+? >>> test_5 = komunikat_5( Ala ) # Ala -> argument funkcji >>> print test_5 Co slychac, Ala? Poniższa funkcja oblicza odległość na płaszczyźnie na podstawie współrzędnych 2 punktów. W przypadku wielu argumentów, ich wartości przypisywane są na podstawie odpowiadających im pozycji parametrów w definicji funkcji: >>> def odleglosc(x1,y1,x2,y2): from math import sqrt return sqrt((x1 - x2)**2 + (y1 - y2)**2) >>> odleglosc(1, 1, 7, 1) 6.0 Przy okazji funkcji warto wprowadzić pojęcie zasięgu zmiennych. Zasięg zmiennej to obszar programu, w którym jest ona widoczna, czyli reprezentuje pewną wartość. Wszystkie tworzone do tej pory zmienne widoczne były z każdego miejsca w programie (oczywiście od momentu przypisania im wartości). To samo dotyczy utworzonych powyżej nazw funkcji, ale już nie ich parametrów czy zmiennych pojawiających się w bloku instrukcji. Moduł wywołujący funkcję nie widzi zdefiniowanych wewnątrz niej zmiennych, które istnieją tylko lokalnie i tylko przez czas działania funkcji: 3
1 DEFINIOWANIE I KORZYSTANIE Z FUNKCJI >>> def ustaw_wartosc(x): nowa_zmienna = x print Wartosc nowej zmiennej ustawiono na:, x >>> ustaw_wartosc(100) Wartosc nowej zmiennej ustawiono na: 100 >>> nowa_zmienna Traceback (most recent call last): File "<stdin>", line 1, in? NameError: name nowa_zmienna is not defined W każdym momencie działania programu (lub pracy interaktywnej z interpreterem) istnieją trzy zakresy nazw, określane mianem przestrzeni nazw, w których zmienne są tworzone, modyfikowane i wyszukiwane. Zmienna w momencie przypisania jej wartości trafia do jednej z tych przestrzeni nazw zgodnie z następującymi regułami (dotyczącymi nazw niekwalifikowanych, czyli tych niezwiązanych z określonymi obiektami): Moduł definiuje zakres globalny przestrzeń nazw wypełniana jest nazwami zmiennych i funkcji tworzonych w czasie działania kodu. Każdorazowe wywołanie funkcji tworzy do jej dyspozycji nowy zakres lokalny (przestrzeń nazw zmiennych utworzonych wewnątrz funkcji), który nie jest dostępny z poziomu modułu. Wbudowana przestrzeń nazw powstaje w chwili uruchomienia interpretera (wywołania skryptu) i zawiera wszystkie nazwy wstępnie zdefiniowane (np. wbudowanych funkcji). Próba użycia nazwy zmiennej powoduje jej wyszukiwanie kolejno w zakresie lokalnym (na zewnątrz funkcji zakres lokalny jest równoznaczny z globalnym), globalnym i wbudowanym. Wyszukiwanie zatrzymuje się w momencie znalezienia zmiennej, np. jeśli zmienne o tych samych nazwach istnieją jednocześnie w zakresie lokalnym funkcji i globalnym modułu, w obrębie funkcji używane są te pierwsze. Zapobiega to konfliktowi z nazwami już istniejącymi zarówno w module, jak i w innych funkcjach. Funkcja może korzystać ze zmiennych globalnych w sensie pobierania ich wartości (brak określonej zmiennej w zakresie lokalnym spowoduje jej poszukiwanie wyżej, czyli w globalnej i wreszcie wbudowanej przestrzeni nazw), ale nie może nadawać im nowych wartości (z wyjątkiem przeprowadzonych w miejscu modyfikacji obiektów zmiennych, np. list). 4
1 DEFINIOWANIE I KORZYSTANIE Z FUNKCJI Wbudowana przestrzeń nazw przeszukiwana jest na końcu, zatem utworzenie zmiennej nazwanej tak samo, jak np. wbudowana funkcja, przysłoni tę ostatnią (zdecydowanie odradzane). W poniższym przykładzie zmienne a, b i zakresy to zmienne globalne, natomiast parametr funkcji x i zdefiniowana w funkcji zmienna a mają charakter lokalny (a w funkcji przysłania a z zakresu globalnego). Zmiennej b w obrębie funkcji nie została nadana żadna wartość, zatem jej wyszukiwanie kontynuowane jest w przestrzeni nazw modułu. Moduł nie widzi wartości a nadanej w obrębie funkcji, gdyż zmienna a ma tam charakter lokalny: >>> a, b = 1, 11 >>> def zakresy(x): a = 7 print a, b, x >>> zakresy(a) 7 11 1 >>> print a, b 1 11 Na korzystanie w obrębie funkcji ze zmiennych globalnych (ich tworzenie i modyfikowanie) pozwala jedyna w Pythonie deklaracja instrukcja global. Zadeklarowanie zmiennej przy pomocy global sprawi, że zostanie ona dołączona do globalnej przestrzeni nazw modułu: >>> def zakresy_2(): global a, b, x print a, b #wyswietl poczatkowe wartosci a, b a, b, x = 11, 22, 33 #zmien a, b oraz utworz x >>> zakresy_2() 1 11 #poczatkowe wartosci a, b >>> print a, b, x #zmienna x zostala utworzona w funkcji! 11 22 33 #a, b zostaly zmienione podczas wywolania funkcji! Mimo opisanej powyżej możliwości działania na zmiennych globalnych, zaleca się takie konstruowanie funkcji, aby komunikacja funkcja moduł wywołujący ograniczona była do przekazywanych argumentów i zwracanych przez funkcję wartości. Przekazanie do funkcji argumentu oznacza nadanie określonemu parametrowi danej wartości, co sprowadza się do utworzenia kopii odwołania. Obowiązują tu zatem takie same reguły, jak przy two- 5
1 DEFINIOWANIE I KORZYSTANIE Z FUNKCJI rzeniu aliasów zmiennych (czyli zmiennych współużytkujących odwołania do tego samego obiektu). Argumenty będące obiektami niezmiennymi nie mogą być zmienione również w wyniku działania funkcji (przypisanie nowej wartości w funkcji spowoduje utworzenie lokalnej kopii), natomiast argumenty zmienne są wrażliwe na modyfikacje przeprowadzane w miejscu: >>> napis, lista1, lista2 = brak zmian, [ a, b ], [ c ] >>> def zmien(s, L1, L2): S = S +??? for litera in L1: if litera == b : L1[L1.index(litera)] = litera.upper() L2 = [ b, b ] for litera in L2: if litera == b : L2[L2.index(litera)] = litera.upper() print S, L1, L2 >>> zmien(napis, lista1, lista2) brak zmian??? [ a, B ] [ B, B ] >>> print napis, lista1, lista2 brak zmian [ a, B ] [ c ] >>> liczby = [2, 3, 4] >>> def kwadraty(l): for i in range(len(l)): L[i] = L[i]**2 >>> kwadraty(liczby) >>> liczby [4, 9, 16] >>> liczby = [0, 1, 2] >>> kwadraty(liczby[:]) #wywolanie funkcji z kopia listy >>> liczby #-> brak zmian! [0, 1, 2] 6
2 SŁOWNIKI 2 Słowniki Słowniki są kolejnym po listach i krotkach złożonym typem obiektów, służącym do przechowywania kolekcji dowolnych obiektów. W przypadku słowników kolekcje te mają postać par klucz:wartość wymienianych w nawiasach klamrowych. Rola klucza słownikowego jest analogiczna do roli indeksu w sekwencjach i polega na jednoznacznym identyfikowaniu każdej pozycji obiektu złożonego. Indeksy sekwencji przypisywane są niejako automatycznie i użytkownik czy program nie ma nad nimi kontroli w takim sensie, że nie można np. stworzyć sekwencji opisywanej wyłącznie parzystymi indeksami czy przypisać wartości pozycji o indeksie 100, gdy sekwencja kończy się na indeksie 10. Z kolei kluczem słownika może być dowolny obiekt niezmienny (liczba, łańcuch, krotka), ponadto w jednym słowniku klucze nie muszą być tego samego typu: >>> slownik = { imie : Ala, 100:[0, 1, 2], ( a, b ):(314, 0.0)} >>> print slownik { imie : Ala, ( a, b ): (314, 0.0), 100: [0, 1, 2]} Kolejność wpisów słownika slownik, wyświetlonego przy pomocy print, jest inna niż podana podczas jego tworzenia. Ilustruje to podstawową cechę słowników, jaką jest nieuporzadkowanie. Słowniki nie są sekwencjami, czyli nie mają zdefiniowanej kolejności elementów. Tym samym wszelkie operacje zastrzeżone dla sekwencji (indeksowanie, łączenie i powielanie, pętla for) nie działają na słownikach. Wpisy w słowniku zapamiętywane są w kolejności ułatwiającej ich przeszukiwanie i nie ma możliwości jej kontrolowania, co w rzeczywistości jest bez znaczenia, gdyż poszczególne wartości pobierane są na podstawie klucza, a nie pozycji: >>> slownik[ imie ] Ala >>> slownik[100] # 100 to klucz, a nie indeks! [0, 1, 2] >>> slownik[( a, b )][1] #1 to indeks wartosci identyfikowanej 0.0 #za pomoca klucza bedacego krotka Słowniki są obiektami zmiennymi (podobnie jak listy), czyli możliwa jest ich modyfikacja bez tworzenia kopii: 7
2 SŁOWNIKI >>> slownik[ zwierzak ] = kot #dodanie wpisu >>> slownik[ imie ] = Ola #zmiana istniejacego wpisu >>> del slownik[( a, b )] #usuniecie wpisu >>> print slownik { imie : Ola, zwierzak : kot, 100: [0, 1, 2]} W przeciwieństwie do list, gdzie dodanie wartości do nieistniejącego indeksu jest błędem, przypisanie do nowego klucza wykorzystywane jest do powiększania słowników. Różny sposób rozrastania się list i słowników wynika z faktu, że pierwsze są sekwencjami, podczas gdy słowniki należą do odwzorowań nieuporządkowanych zbiorów obiektów. Przydatne operacje i metody działające na słownikach pokazane będą na przykładzie poniższego słownika, odwzorowującego jednoliterowe oznaczenia aminokwasów białkowych na ich trzyliterowe odpowiedniki: >>> aa_1 = { A : Ala, C : Cys, D : Asp, E : Glu, F : Phe, G : Gly, H : His, I : Ile } Wielkość słownika (ilość wpisów, czyli par klucz:wartość) sprawdzić można za pomocą funkcji len(). Funkcja konwersji na typ łańcuchowy str() także działa na słownikach: >>> len(aa_1) 8 >>> str(aa_1) "{ A : Ala, C : Cys, E : Glu, D : Asp, G : Gly, F : Phe, I : Ile, H : His }" Najczęstszy sposób pracy ze słownikiem polega na pobieraniu kolejnych wartości za pomocą klucza, co wymaga dostępu do wszystkich kluczy w danym słowniku. Metoda słownikowa keys() zwraca listę kluczy, które można potem użyć np. w pętli for. Listę wszystkich wartości i wreszcie listę dwupozycyjnych krotek poszczególnych wpisów słownika zwracają metody values() i items(). Ponieważ próba pobrania wartości na podstawie nieistniejącego klucza kończy się komunikatem o błędzie, a przypisanie wartości już istniejącemu kluczowi nadpisuje jego aktualną wartość, do sprawdzenia czy dany słownik zawiera określony klucz, służy metoda has_key(): 8
2 SŁOWNIKI >>> aa_1.keys() [ A, C, E, D, G, F, I, H ] >>> aa_1.has_key( F ) True >>> aa_1.values() [ Ala, Cys, Glu, Asp, Gly, Phe, Ile, His ] >>> aa_1.items() [( A, Ala ), ( C, Cys ), ( E, Glu ), ( D, Asp ), ( G, Gly ), ( F, Phe ), ( I, Ile ), ( H, His )] Łączenie dwóch słowników przy pomocy operatora dodawania jest niemożliwe słowniki nie są sekwencjami! Metoda update() pozwala dołączyć wpisy z innego słownika (np. aa_2) do bieżącego (aa_1): >>> aa_2 = { K : Lys, L : Leu, M : Met, N : Asn } >>> aa_1.update(aa_2) >>> print aa_1 { A : Ala, C : Cys, E : Glu, D : Asp, G : Gly, F : Phe, I : Ile, H : His, K : Lys, M : Met, L : Leu, N : Asn } >>> print aa_2 { K : Lys, M : Met, L : Leu, N : Asn } W powyższym przykładzie modyfikacji w miejscu ulega słownik, dla którego metoda update() jest wywoływana, czyli aa_1. Drugi słownik pozostaje niezmieniony, natomiast sama metoda zwraca obiekt pusty None. Kopiowanie słowników umożliwia metoda copy(). Zwracana jest kopia słownika, dla którego metoda została wywołana: >>> aa_2_kopia = aa_2.copy() >>> print aa_2_kopia { K : Lys, M : Met, L : Leu, N : Asn } Usuwanie wpisu ze słownika za pomocą polecenia del zademonstrowano wcześniej. Metoda popitem() zwraca przypadkowo pobraną parę klucz:wartosc jako dwupozycyjną krotkę i jednocześnie usuwa ją ze słownika (modyfikacja w miejscu!). Można również usunąć wszystkie wpisy ze słownika wywołanie metody clear() pozostawia pusty słownik: 9
2 SŁOWNIKI >>> aa_2.popitem() ( K, Lys ) >>> aa_2.popitem() ( M, Met ) >>> print aa_2 { L : Leu, N : Asn } >>> aa_2.clear() >>> print aa_2 {} Słowniki wyświetlone za pomocą polecenia print nie są zbyt czytelne. Poniższy kod definiuje przykładową funkcję wyświetlającą zawartość słownika posortowaną według kluczy: >>> def wyswietl_slownik(slownik): klucze = slownik.keys() klucze.sort() for klucz in klucze: print %3s:%5s % (klucz, slownik[klucz]) >>> wyswietl_slownik(aa_1) A: Ala C: Cys D: Asp F: Phe G: Gly H: His E: Glu #itd 10