Spotkania z Pythonem - Czesc 3 - wyrazenia lambda
Transkrypt
Spotkania z Pythonem - Czesc 3 - wyrazenia lambda
Spotkania z Pythonem Cz¦±¢ 3 - wyra»enia lambda, generatory, dekoratory, operacje na plikach Michaª Alichniewicz Studenckie Koªo Automatyków SKALP Gda«sk 2014 Dzi¦kuj¦ za uwag¦! Na licencji Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License. M. Alichniewicz (SKALP) Python vol. 3 2014 1 / 66 Wyra»enia lambda M. Alichniewicz (SKALP) Python vol. 3 2014 2 / 66 Wyra»enia lambda - co to jest? Zacznijmy od tego, czym jest wyra»enie lambda. Z denicji, jest to anonimowa funkcja (czyli - nie ma nazwy; ale jest równie» obiektem, wi¦c mo»emy j¡ sobie gdzie± przypisa¢), która przyjmuje zadane argumenty, a nast¦pnie u»ywa ich w tzw. wyra»eniu. Wynik wyra»enia jest warto±ci¡ która taka funkcja zwraca. Najprostsz¡ analogi¡ która mo»e uªatwi¢ zrozumienie funkcji lambda jest zwyczajna funkcja matematyczna. Przyjmuje ona jakie± argumenty, które nast¦pnie s¡ cz¦±ci¡ skªadow¡ jakiego± matematycznego wyra»enia, dla przykªadu f (x) = 5x2 + x + 0.5 − M. Alichniewicz (SKALP) Python vol. 3 1 x 2014 3 / 66 Wyra»enia lambda - co to jest? Dlaczego akurat analogia do matematyki? Otó», w takiej prostej funkcji nie wyst¦puje logika, caªa funkcja jest tylko i wyª¡cznie pojedy«czym wyra»eniem. Druga cecha która powoduje »e funkcja matematyczna jest dobra do przedstawienia jako analogia funkcji lambda jest fakt, »e w matematyce wszystkie zmienne i nazwy funkcji s¡ arbitralne. Czyli mo»emy mie¢ funkcj¦ f (x), ale równie dobrze mo»e to by¢ alfabetu. To, co jest tu niezmienne, to M. Alichniewicz (SKALP) g(a), wyra»enie. Python vol. 3 czy inne litery 2014 4 / 66 Wyra»enia lambda - co to jest? Zaªó»my, »e chcieli by±my zdeniowa¢ w Pythonie tak¡ funkcj¦ jak ta opisana wzorem z poprzednich slajdów. W klasyczny sposób, by to wygl¡daªo tak: def nasza_funkcja(x): # Oblicz i zwroc return 5*x**2 + x + 0.5 - 1/x Jak widzicie, nie ma tutaj ciaªa funkcji - wszystko co funkcja robi to zwraca warto±¢. M. Alichniewicz (SKALP) Python vol. 3 2014 5 / 66 Wyra»enia lambda - denicja Poniewa» ciaªa funkcji jako takiej nie ma, to mo»na to skróci¢ do wspomnianego wyra»enia lambda. Wyra»enia lambda deniuje si¦ nast¦puj¡co: lambda arg1, arg2, ...: wyrazenie arg1, arg2, etc. to argumenty funkcji lambda, za± wyra»enie jest naszym. . . wyra»eniem, którego wynik jest warto±ci¡ jakie nasza funkcja lambda zwraca. Oczywi±cie funkcje lambda tak jak tradycyjne funkcje w Pythonie, mog¡ przyjmowa¢ domy±lne warto±ci argumentów, mo»emy podawa¢ argumenty przez nazw¦ przy wywoªywaniu funkcji, tak samo jak mo»emy zdeniowa¢ zmienn¡ liczb¦ argumentów. M. Alichniewicz (SKALP) Python vol. 3 2014 6 / 66 Wyra»enia lambda - denicja Poniewa» sama funkcja lambda nie ma identykatora, trzeba j¡ gdzie± przypisa¢: f = lambda arg1, arg2, ...: wyrazenie Skoro i tak trzeba t¡ funkcj¦ gdzie± przypisa¢, to po co jej u»ywa¢? Czy ma to sens, skoro mo»emy sobie tak¡ funkcj¦ zdeniowa¢ tradycyjnie? A no. . . nie ma. Zastosowanie funkcji lambda ma sens dopiero z innymi funkcjami, jak dla przykªadu reduce, lter oraz map. Wszystkie wymienione funkcje przetwarzaj¡ w pewien sposób list¦ podan¡ przez u»ytkownika. A w jaki sposób? Równie» w pewien sposób zdeniowany przez u»ytkownika - jako pierwszy argument przyjmuj¡ funkcje która operuje na elementach. Funkcje mo»emy albo zdeniowa¢ normalnie - co poniek¡d za±mieca kod, bo mamy jak¡± dziwn¡ funkcj¦ nie wiadomo po co, albo wªa±nie u»y¢ wyra»enia lambda. M. Alichniewicz (SKALP) Python vol. 3 2014 7 / 66 Wyra»enia lambda - funkcja reduce Funkcja reduce przyjmuje dwa argumenty - drugi to lista, z kolei pierwszy jest funkcj¡ dwóch parametrów. Jej dziaªanie opiera si¦ na zredukowaniu listy do pojedynczego wyniku w sposób zdeniowany przez u»ytkownika, na zasadzie: y = f (f (f (f (f (x1 , x2 ) , x3 ) , x4 ) , x5 ) . . .) Co mo»na z tym zrobi¢? Np. zsumowa¢ argumenty listy (mo»na to zrobi¢ normaln¡ p¦tl¡ for, ale po co?). W takim wypadku, nasza funkcja to po prostu: f (x, y) = x + y M. Alichniewicz (SKALP) Python vol. 3 2014 8 / 66 Wyra»enia lambda - funkcja reduce A jak to wygl¡da w praktyce? Nasz¡ funkcj¦ redukuj¡c¡ mo»emy zdeniowa¢ jako wyra»enie lambda: lambda x,y: x + y Nie musimy jej nigdzie przypisywa¢ - mo»emy je poda¢ wprost jako argument funkcji. Mamy wtedy: reduce(lambda x, y: x+y, nasza_lista) Sprawd¹my wi¦c dziaªanie: # Zwroci 21 reduce(lambda x, y: x + y, [1,2,3,4,5,6]) M. Alichniewicz (SKALP) Python vol. 3 2014 9 / 66 Wyra»enia lambda - funkcja reduce Korzystaj¡c z funkcji reduce, mo»emy bardzo ªatwo policzy¢ silni¦ - jak pami¦tamy z matematyki, to iloczyn kolejnych liczb od 1 do docelowej. W takim przypadku, funkcja lambda b¦dzie wygl¡da¢ nast¦puj¡co: lambda x,y: x * y # Mnoymy ssiednie elementy W poª¡czeniu z funkcj¡ range, która deniuje nam list¦, mo»emy zdeniowa¢ ko«cow¡ funkcj¦ n!: reduce(lambda x, y: x * y, range(1,n)) Sprawd¹my wi¦c dziaªanie: # Zwroci 120 reduce(lambda x, y: x * y, range(1,6)) M. Alichniewicz (SKALP) Python vol. 3 2014 10 / 66 Wyra»enia lambda - funkcja lter Drug¡ funkcj¡ z kolekcji jest lter. Ta funkcja dla odmiany zwraca list¦, która skªada si¦ z elementów listy któr¡ podajemy jako drugi argument, dla których wynik funkcji podanej w pierwszym argumencie jest prawd¡. Do czego to mo»na wykorzysta¢? A no na przykªad do stworzenia listy kolejnych liczb parzystych. Jak to zrobi¢? Zaªó»my, »e nasza funkcja to to prostu dzielenie modulo 2. Dla liczb parzystych - otrzymamy 1; dla liczb nieparzystych, otrzymamy 0, co jest interpretowane jako faªsz. Neguj¡c wynik otrzymamy prawd¦ dla liczby parzystej i faªsz dla liczby nieparzystej. Oczywi±cie najlepiej to zobrazuje przykªad: # Zwroci [2,4,6,8] filter(lambda x: not x % 2, range(1,10)) M. Alichniewicz (SKALP) Python vol. 3 2014 11 / 66 Wyra»enia lambda - funkcja map Ostatnia funkcja z kolekcji to map. Tak jak poprzednia zwraca list¦, tylko »e dla odmiany tutaj dostajemy wynik w postaci tych samych elementów, ale po przetworzeniu przez podan¡ przez nas funkcj¦ (ilo±¢ jest ta sama, nic nam nie ubywa). Funkcja jest wywoªywana dla ka»dego elementu oddzielnie. Za pomoc¡ tego mo»emy np. podnie±¢ liczby z listy do kwadratu lub np. zamieni¢ wszystkie teksty z listy na pisane wielkimi literami. Przykªad: # Zwroci ['A', 'B', 'C', 'D', 'SKALP'] map(lambda x: x.upper(), ['a', 'b', 'c', 'd', 'Skalp']) M. Alichniewicz (SKALP) Python vol. 3 2014 12 / 66 Skrócone instrukcje warunkowe M. Alichniewicz (SKALP) Python vol. 3 2014 13 / 66 Skrócone instrukcje warunkowe Rozwa»my dwa zdania: Je»eli warunek jest speªniony, zwró¢ A, w przeciwnym razie zwró¢ B oraz Zwró¢ A je»eli warunek jest speªniony, w przeciwnym razie zwró¢ B Oba zdania maj¡ oczywi±cie logiczny sens. M. Alichniewicz (SKALP) Python vol. 3 2014 14 / 66 Skrócone instrukcje warunkowe Oba zdania reprezentuj¡ opisowo skrócon¡ instrukcj¦ warunkow¡, przy czym pierwsza jest dosªownym tªumaczeniem instrukcji znanej z C, a druga jest jej odpowiednikiem w Pythonie. Dla przypomnienia, w C to wygl¡da tak: warunek ? wartosc_1 : wartosc_2 W Pythonie, to ma nieco inn¡ posta¢: wartosc_1 if warunek else wartosc_2 Pierwsza ró»nica - zamiast nie do ko«ca czytelnych znaków ? oraz :, mamy czytelne if i else. Druga to nieco inna konstrukcja warunku najpierw podajemy warto±¢ któr¡ zwracamy je»eli warunek jest speªniony, pó¹niej instrukcj¦ warunkow¡ a dopiero na ko«cu warto±¢ dla faªszu. M. Alichniewicz (SKALP) Python vol. 3 2014 15 / 66 Generatory M. Alichniewicz (SKALP) Python vol. 3 2014 16 / 66 Generatory W Pythonie istnieje mo»liwo±¢ zdeniowania funkcji, b¡d¹ klasy, po której mo»emy iterowa¢ - czyli wykorzysta¢ je jako ¹ródªo danych dla p¦tli for. xrange. W Pythonie jest jedna taka funkcja: Je»eli sprawdzimy, co nam zwróci ta funkcja bezpo±rednio: print xrange(0,10) To co najwy»ej wy±wietli si¦ nam xrange(10). Z kolei je»eli umie±cimy t¡ instrukcj¦ w p¦tli: for item in xrange(0,10): print item, To p¦tla wy±wietli nam 0 1 2 3 4 5 6 7 8 9. Tak samo zadziaªa jakby±my podali zamiast xrange podali range. Z tym »e je»eli wy±wietlimy co zwraca druga funkcja, to dostaniemy list¦. Jaka jest ró»nica? M. Alichniewicz (SKALP) Python vol. 3 2014 17 / 66 Generatory Stwórzmy odpowiednik funkcji range we wªasnym zakresie: def my_range(start, end, step = 1): return_ = [] while start < end: # Wpisz akutalna wartosc na liste # Metoda ``append'' na liscie pozwala # na dopisanie elementu na koniec listy return_.append(start) start += step return return_ Dziaªanie tej funkcji jest dokªadnie takie jak dziaªanie funkcji range. Tworzymy list¦ z kolejnymi elementami z zakresu. M. Alichniewicz (SKALP) Python vol. 3 2014 18 / 66 Generatory W celu uªatwienia zrozumienia tego jak dziaªa generator, zróbmy sobie odpowiednik p¦tli for: for item in set_of_data: # Zrob cos z ``item'' # TUTAJ KOD DO WYKONANIA W PETLI pass Za pomoc¡ p¦tli while. M. Alichniewicz (SKALP) Python vol. 3 2014 19 / 66 Generatory Jedna z mo»liwych wariacji tego kodu, która bardzo dobrze oddaje dziaªanie p¦tli for: # Pobierz obiekt iteratora z naszego # obiektu po jakim chcemy wykonywac petle iterator = set_of_data.__iter__() # (1) # Zacznijmy petle while True: try: # Wez kolejny element # z iteratora item = iterator.next() # (2) # Zrob cos z ``item'' # TUTAJ KOD DO WYKONANIA W PETLI except StopIteration: # (3) break M. Alichniewicz (SKALP) Python vol. 3 2014 20 / 66 Generatory Jak ten kod dziaªa? Przede wszystkim trzeba zwróci¢ uwag¦ »e nie u»ywamy tutaj indeksowania elementów. Ka»dy obiekt który mo»emy poda¢ do p¦tli for, musi mie¢ zaimplementowan¡ metod¦ __iter__. Zwraca ona obiekt, po którym bezpo±rednio mo»na iterowa¢ (1) - a wymogiem takiego iterowalnego obiektu jest posiadanie zaimplementowanej metody next, która zwraca kolejny element z zestawu danych (2), albo StopIteration w przeciwnym wypadku (3). Oczywi±cie rzuca wyj¡tkim obiekt który podajemy na wej±cie p¦tli i obiekt który jest zwrócony przez jego metod¦ __iter__ mo»e by¢ tym samym obiektem - wymagane jest jednak posiadanie odpowiednich metod. M. Alichniewicz (SKALP) Python vol. 3 2014 21 / 66 Generatory Stwórzmy sobie tak¡ przykªadow¡ klas¦: class my_range_class(object): def __init__(self, start, end, step = 1): self.start = start self.end = end self.step = step def __iter__(self): return self def next(self): if self.start < self.end: return_value = self.start self.start += self.step return return_value else: raise StopIteration() M. Alichniewicz (SKALP) Python vol. 3 2014 22 / 66 Generatory Jak wida¢, w tym kodzie nigdzie nie tworzymy listy elementów! Zwracamy je na bie»¡co, je»eli p¦tla (albo my) - o to poprosi poprzez wywoªanie metody next. Ma to t¦ zalet¦, »e przede wszystkim nie generujemy dªugiej listy w pami¦ci. Je»eli generujemy liczby, to problemu nie ma - no chyba »e s¡ to bardzo dªugie liczby - ale dla innych obiektów mo»e si¦ okaza¢ »e nasz program zu»ycie pami¦ci ma niczym Firefox w czasach ±wietno±ci (z caªym szacunkiem dla przegl¡darki Mozilli). Powy»szy kod bezproblemowo zadziaªa w bloku for, i jego dziaªanie b¦dzie zgodne z oczekiwaniami. M. Alichniewicz (SKALP) Python vol. 3 2014 23 / 66 Generatory Jak ju» si¦ przekonali±my wielokrotnie, du»o dªugich instrukcji ma w Pythonie swoje krótsze odpowiedniki. Tak te» jest w przypadku generatorów - zamiast peªnej klasy mo»emy zdeniowa¢ po prostu odpowiedni¡ funkcj¦. Pisz¡c generator w postaci funkcji, informujemy Pythona »e wªa±nie zwracamy do p¦tli jak¡± warto±¢ (ale nie ko«czymy funkcji!) za pomoc¡ sªowa kluczowego yield (jest to odpowiednik return: w tym momencie wychodzimy z funkcji, ale funkcja b¦dzie wznowiona od tego miejsca w przypadku pro±by o kolejny argument). M. Alichniewicz (SKALP) Python vol. 3 2014 24 / 66 Generatory Najlepiej to zobrazuje fragment kodu (funkcje print sªu»¡ do pokazania dziaªania kodu): def my_range_generator(start, end, step = 1): print 'Wejscie do funkcji' while start < end: print 'Zwrocenie wartosci %d' % start # "Wyrzuc" nowa wartosc yield start print 'Wznowienie funkcji' start += step print 'Wyjscie z funkcji' Przeanalizujmy dziaªanie tego w praktyce. M. Alichniewicz (SKALP) Python vol. 3 2014 25 / 66 Generatory Zacznijmy od utworzenia obiektu i sprawdzenia co dostali±my: x = my_range_generator(0, 10, 2) print repr(x) # Dostaniemy na ekranie cos podobnego do # <generator object my_range_generator at ...> for musi __iter__ - sprawd¹my wi¦c czy nasza Jak ju» wiemy, ka»dy obiekt po którym mo»emy chodzi¢ p¦tl¡ mie¢ zaimplementowan¡ metod¦ funkcja tak¡ posiada, i co nam zwróci: x_iter = x.__iter__() print repr(x_iter) # Dostaniemy na ekranie cos podobnego do # <generator object my_range_generator at ...> Mo»emy zauwa»y¢, »e oba polecenia repr zwróciªy nam to samo - czyli to jest taka sama sytuacja jak dla naszej klasy zdeniowanej wcze±niej. M. Alichniewicz (SKALP) Python vol. 3 2014 26 / 66 Generatory Skoro ju» wiemy »e nasze x jest iteratorem, mo»emy go poprosi¢ o pierwszy element: item = x.next() # # # # # W ``item'' bedzie wartosc 0 - a na ekranie zobaczymy: Wejscie do funkcji Zwrocenie wartosci 0 Wy±wietlanie tych danych pozwala na wyci¡gni¦cie dwóch wa»nych wniosków: 1 dopiero przy pierwszym next, funkcja zako«czyªa swoje dziaªanie w miejscu wyst¡pienia yield. wej±cie do funkcji jest wykonywanie wywoªaniu 2 M. Alichniewicz (SKALP) Python vol. 3 2014 27 / 66 Generatory Wykonajmy wi¦c po raz kolejny ten sam kod: item = x.next() # # # # # W ``item'' bedzie wartosc 2 - a na ekranie zobaczymy: Wznowienie funkcji Zwrocenie wartosci 2 Jak wida¢, funkcja zostaªa wznowiona (a wi¦c nie mo»emy mówi¢ o wyj±ciu z funkcji, a jedynie o wstrzymaniu - warto±¢ wszystkich zmiennych jest zachowana!) od miejsca w którym wyszli±my z niej poprzez sªowo kluczowe yield, a nast¦pnie znów wstrzymana przy kolejnym wyst¡pieniu tego sªowa. M. Alichniewicz (SKALP) Python vol. 3 2014 28 / 66 Generatory Mo»emy kontynuowa¢ wywoªywanie next do uzyskania wszystkich elementów (uda sie to nam jeszcze 3 razy). Za czwartym razem wynik wywoªania b¦dzie nieco inny: item = x.next() # # # # # # # # Wartosc zmiennej ``item'' sie nie zmieni, a na ekranie zobaczymy: Wznowienie funkcji Wyjscie z funkcji Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration Wznowili±my dziaªanie, ale te» wyszli±my z p¦tli, a wi¦c nie napotkali±my na yield. Funkcja w naszym przypadku si¦ sko«czyªa, co potwierdza wypisanie na ekranie ci¡gu Wyjscie z funkcji. Wida¢ równie» »e Python rzuciª znanym ju» wyj¡tkiem M. Alichniewicz (SKALP) StopIteration. Python vol. 3 2014 29 / 66 Generatory - forma skrócona Podobnie jak dla instrukcji warunkowych, dla generatorów równie» istnieje denicja skrócona. Wygl¡da to tak: (przetworz_element for element in lista) przetworz_element to oczywi±cie wyra»enie które co± z naszym elementem (nazwanym przez nas element) robi. Jak wida¢, jest to konstrukcja b¦d¡ca odpowiednikiem zwini¦tej konstrukcji warunkowej dla p¦tli for. Przykªad takiego generatora: a = (item**2 for item in lista) print repr(a) # <generator object <genexpr> at 0x7f8e5df83c30> M. Alichniewicz (SKALP) Python vol. 3 2014 30 / 66 Generatory - forma skrócona Tak stworzony obiekt oczywi±cie ma wszystkie potrzebne generatorom metody - __iter__ i next. Problem z takim generatorem jest jeden - jest to objekt JEDNOKROTNEGO u»ytku. Czyli jak raz co± z nim zrobimy (np., u»yjemy w p¦tli for), to drugi raz ju» to nie zadziaªa. W praktyce takie generatory najcz¦±ciej dziaªaj¡ jako skrócona wersja p¦tli for, i ich celem jest stworzenie specycznej listy. Robi si¦ to umieszczaj¡c generator w parze nawiasów kwadratowych albo przekazuj¡c do konstruktora klasy list: a = [item**2 for item in list] # albo, zamiennie a = list(item**2 for item in list) M. Alichniewicz (SKALP) Python vol. 3 2014 31 / 66 Generatory - forma skrócona z instrukcj¡ warunkow¡ Póki co mo»liwo±ci generatorów s¡ w zasadzie identyczne jak funkcji map, czyli przeprowad¹ operacj¦ na ka»dym elemencie. Ciekawsze mo»liwo±ci daje poª¡czenie generatorów ze skrócon¡ instrukcj¡ warunkow¡. Skªadnia takiego generatora wygl¡da w ten sposób (mo»na to oczywi±cie zwin¡¢ w jedn¡ lini¦): (przetworz_element for element in lista\ if warunek_elementu) if. Jest to warunek dla ka»dego elementu z caªych danych wej±ciowych. Jak widzimy, na ko«cu pojawiªa si¦ instrukcja wykonywany M. Alichniewicz (SKALP) Python vol. 3 2014 32 / 66 Generatory - forma skrócona z instrukcj¡ warunkow¡ Prostym przykªadem wykorzystania jest np. znalezienie elementów wspólnych dla dwóch list: a = [1, 2, 3, 4, 5] b = [3, 4, 5, 6, 7] wspolne = [item for item in a if item in b] # [3, 4, 5] Innym praktycznym przykªadem jest konwersja ci¡gu znaków (np. odczytanych z UART'a) na ich heksadecymentaln¡ reprezentacj¦: wejscie = 'SKALP' wyjscie = ''.join('\\x%02X' % ord(char) \ for char in wejscie) print wyjscie # \x53\x4B\x41\x4C\x50 M. Alichniewicz (SKALP) Python vol. 3 2014 33 / 66 Dekoratory M. Alichniewicz (SKALP) Python vol. 3 2014 34 / 66 Dekoratory Cz¦sto, tworz¡c bardziej skomplikowany kod, tramy na przypadek gdzie np. dla du»ej cz¦±ci metod w klasie pocz¡tkowy kod jest dokªadnie ten sam - czyli dla przykªadu, jaka± inicjalizacja, sprawdzenie uprawnie«, etc. Zgodnie z reguªami KISS oraz DRY, warto to gdzie± przenie±¢ na zewn¡trz. Jednym ze sposobów jest zwyczajne zawarcie tego w jakiej± dodatkowej metodzie i wywoªywanie na pocz¡tku ka»dej funkcji. To samo mo»emy zrobi¢ z kodem wywoªywanym na ko«cu. Tak by to wygl¡daªo w ka»dym j¦zyku programowania. W Pythonie jednak mo»emy to zrobi¢ nieco inaczej - tworz¡c dekoratory funkcji. M. Alichniewicz (SKALP) Python vol. 3 2014 35 / 66 Dekoratory Przez dekorowanie funkcji rozumiemy wykonanie jakiegos kodu przed i po wykonaniu zasadniczej tre±ci funkcji. Z kolei sam dekorator jest deniowany jako specyczna funkcja: przyjmuj¡ca jako parametr funkcj¦, i zwracaj¡ca now¡, udekorowan¡ funkcj¦. Zaraz zaraz, jak to przyjmuj¡ca/zwracaj¡ca funkcj¦? Pami¦tajmy: w Pythonie wszystko jest obiektem. Tak wi¦c funkcja te», i mo»emy spokojnie j¡ przypisa¢ do jakiej± zmiennej - lub te» przekaza¢ do funcji lub z funkcji. Druga sprawa - w Pythonie mo»emy deniowa¢ funkcje lub klasy wewn¦trzne, czyli widoczne tylko wewn¡trz funkcji/klasy gdzie zostaªy zdeniowane. M. Alichniewicz (SKALP) Python vol. 3 2014 36 / 66 Dekoratory Na pocz¡tek zdeniujmy sobie prost¡ funkcj¦ któr¡ b¦dziemy chcieli udekorowa¢: def hello(): for i in range(0,10000000): # Male opoznienie nie zaszkodzi pass print "Hello world!" Funkcja nie jest specjalnie skomplikowana, ale te» dla celów pokazowych jest jak najbardziej wystarczaj¡ca. M. Alichniewicz (SKALP) Python vol. 3 2014 37 / 66 Dekoratory Zaªó»my »e np chcieliby±my przed p¦tl¡ opó¹niaj¡c¡ wy±wietli¢ tekst wejscie, a po wy±wietleniu Hello world! dorzuci¢ jeszcze tekst wyj±cie, jednocze±nie nie modykuj¡c oryginalnej funkcji (celem zachowania czytelno±ci kodu - funkcja hello ma wy±wietla¢ hello world, a nie dla przykªadu informowa¢ »e do funkcji weszli±my). Mo»emy sobie stworzy¢ now¡ funkcj¦, która wywoªa t¡ oryginaln¡: def hello_decorated(): print 'wejscie' hello() print 'wyjscie' Jest to jedno z mo»liwych rozwi¡za«, ale musimy pami¦ta¢ o wywoªaniu hello_decorated zamiast hello. M. Alichniewicz (SKALP) Python vol. 3 2014 38 / 66 Dekoratory Na etapie pisania kodu to nie problem. Ciekawiej natomiast zaczyna si¦ je»eli jest to zmiana tymczasowa - np. jako element debuggowania programu, i docelowo jej nie b¦dzie w ko«cowym programie. Trzeba b¦dzie wtedy pami¦ta¢ »eby zmieni¢ X wywoªa« z powrotem na hello. Inny ciekawy przypadek to sytuacja gdy to nie dotyczy jednej funkcji, a kilkunastu - wtedy w ogóle mo»emy si¦ pogubi¢. Teoretycznie - mo»emy zmieni¢ nazw¦ funkcji pierwotnej, np. na _hello, a jako hello zdeniowa¢ nasz¡ udekorowan¡ funkcj¦. Ponownie schody si¦ zaczn¡ dla kilkunastu funkcji. Krokiem który to uªatwi, jest stworzenie nowej funkcji, która generuje now¡, udekorowan¡ funkcj¦ (czyli, jej argumentem b¦dzie stara funkcja), a zwróci wersj¦ ze zmianami. M. Alichniewicz (SKALP) Python vol. 3 2014 39 / 66 Dekoratory Stwórzmy wi¦c tak¡ funkcj¦: def decorator(original_fcn): """ Dekoruje funkcje. Jako argument przyjmuje funkcje oryginalna. """ def decorated_fcn(*args, **kwargs): """ Funkcja udekorowana. Nie znamy argumentow, wiec *args i **kwargs. """ print 'wejscie' # Wywolanie oryginalu original_fcn(*args, **kwargs) print 'wyjscie' return decorated_fcn M. Alichniewicz (SKALP) # Zwroc nowa funkcje Python vol. 3 2014 40 / 66 Dekoratory Je»eli wywoªamy tak¡ funkcj¦ dekoruj¡c¡ jako argument podaj¡c wªa±nie nasze hello (ale bez nawiasów, czyli nie wywoªujemy jej!), w efekcie dostaniemy now¡ funkcj¦: hello_decorated = decorator(hello) print repr(hello_decorated) # Dostaniemy cos podobnego do # <function decorated_fcn at 0x7fbcf45f0938> Wywoªanie otrzymanego hello_decorated da nam oczekiwany efekt. Dalej jednak mamy problem, bo trzeba to gdzie± przypisa¢, czyli na chwil¦ obecn¡ dalej potrzebujemy sztuczki ze zmian¡ nazwy. M. Alichniewicz (SKALP) Python vol. 3 2014 41 / 66 Dekoratory Python i na to ma rozwi¡zanie. Zdeniowana przez nas funkcja decorator jest faktycznie dekoratorem, i Python posiada mechanizm który umo»liwia automatyczne dekorowanie funkcji na etapie jej denicji. Maj¡c taki dekorator, mo»emy sobie przedeniowa¢ nasz¡ funkcj¦ hello: @decorator # Podaj dekorator def hello(): for i in range(0,10000000): # Male opoznienie nie zaszkodzi pass print "Hello world!" Dekorator podaje przed denicj¡ funkcji lini¡ zaczynaj¡c¡ si¦ od znaku nazw¦ dekoratora (nie jego wywoªanie) - po @, a nast¦pnie podajemy maªpie podajemy po prostu obiekt. M. Alichniewicz (SKALP) Python vol. 3 2014 42 / 66 Dekoratory A co je»eli chcieliby±my mie¢ dekorator, który przyjmuje jaki± dodatkowy parametr, w naszym przypadku np. tekst jaki chcemy wy±wietli¢ przed wej±ciem do funkcji? Z racji tego, »e po @ Python oczekuje na obiekt który jest funkcj¡ (dekoratorem), mo»emy zastosowa¢ sztuczk¦: stworzy¢ funkcj¦, która przyjmuje jakis parametr, a nast¦pnie zwraca odpowiednio sparametryzowany dekorator (po maªpie musi by¢ obiekt - ale nigdzie nie jest powiedziane, »e ma by¢ podany bezpo±rednio.) M. Alichniewicz (SKALP) Python vol. 3 2014 43 / 66 Dekoratory Stwórzmy wi¦c tak¡ funkcj¦ generuj¡c¡ dekorator (docstring-i pomini¦te ze wzgl¦du na orgraniczone miejsce na slajdzie): def decorator_verbose(text): """ Zwraca dekorator, ktory wyswietla na wejsciu do funkcji podany tekst. """ def decorator(original_fcn): def decorated_fcn(*args, **kwargs): print text # Wywolanie oryginalu original_fcn(*args, **kwargs) print 'wyjscie' return decorated_fcn # Zwroc nowa funkcje return decorator M. Alichniewicz (SKALP) Python vol. 3 2014 44 / 66 Dekoratory Oczywi±cie warto sprawdzi¢ jej dziaªanie w praktyce: @decorator_verbose('Begin execution') def hello(): for i in range(0,10000000): # Male opoznienie nie zaszkodzi pass print "Hello world!" hello() # # # # # Wyswietli: Begin execution Hello world! wyjscie M. Alichniewicz (SKALP) Python vol. 3 2014 45 / 66 Dekoratory Dekoratory mo»na równie» ze sob¡ ª¡czy¢ - wtedy funkcja jest bezpo±rednio udekorowana przez dekorator podany jako ostatni (ten bezpo±rednio przed denicj¡ funkcji), pó¹niej to co dostaniemy jest dekorowane przez kolejny dekorator etc. Dorzu¢my wi¦c kolejny dekorator: def tictoc(fcn): def decorated(*args, **kwargs): tic = time.time() fcn(*args, **kwargs) # Policz czas wykonywania toc = time.time() print "Time elapsed: %f" % (toc - tic) return decorated M. Alichniewicz (SKALP) Python vol. 3 2014 46 / 66 Dekoratory Czas na przedeniowanie naszego hello: @tictoc @decorator_verbose('Begin execution') def hello(): for i in range(0,10000000): # Male opoznienie nie zaszkodzi pass print "Hello world!" hello() # # # # # # Wyswietli: (czas przykladowy) Begin execution Hello world! wyjscie Time elapsed: 0.380634 M. Alichniewicz (SKALP) Python vol. 3 2014 47 / 66 Manager kontekstu M. Alichniewicz (SKALP) Python vol. 3 2014 48 / 66 Uruchamianie kodu z managerem kontekstu Manager kontekstu. Brzmi dosy¢ nietypowo, a wi¦c co to jest? Cz¦±¢ kodu, nawet nie±wiadomie, uruchmiamy w pewnym kontek±cie. Mo»e to by¢ np. operowanie na pliku - wszystkie operacje wykonujemy w kontek±cie tego pliku. To, co taki blok charakteryzuje to to, »e musimy jakie± operacje wykona¢ wchodz¡c do tego kontekstu (np. otwarcie wspomnianego pliku), tak samo jak musimy wykona¢ pewne operacje wychodz¡c z tego kontekstu (tutaj - zamkni¦cie pliku). M. Alichniewicz (SKALP) Python vol. 3 2014 49 / 66 Manager kontekstu - denicja W ogólnym rozrachunku, to naszym managerem kontekstu jest - jak wszystko w Pythonie - nic innego jak specyczny obiekt, posiadaj¡cy odpowiednie metody. Dla managera kontekstu s¡ to metody dosy¢ intuicyjne - pierwsza to __enter__ (nie przyjmuj¡ca zewn¦trznych argumentów) zwracaj¡ca obiekt kontekstu (mo»na samego siebie), druga z kolei to równie intuicyjne __exit__, które z kolei przyjmuje 3 argumenty - deniuj¡ one dlaczego zostaªo zako«czone wykonanie bloku z naszym kontekstem (dla poprawnego wykonania, wszystkie trzy elementy b¦d¡ miaªy warto±¢ None, dla wyj¡tku b¦d¡ tu dane wyj¡tku). True. Je»eli wszystko poszªo OK, ta metoda powinna zwróci¢ M. Alichniewicz (SKALP) Python vol. 3 2014 50 / 66 Manager kontekstu - denicja Zróbmy wi¦c przykªadowy kod. W naszym przykªadzie manager i kontekst to b¦d¡ dwa ró»ne obiekty. class ManagerKontekstu(): """ Klasa managera kontekstu """ class Kontekst(): """ Klasa kontekstu """ def __init__(self): """ Konstruktor kontekstu """ print 'Utworzenie kontekstu' self.counter = 1 def __init__(self): """ Konstruktor managera kontekstu """ print 'Utworzenie managera' # pozostaly kod na nastepnym slajdzie M. Alichniewicz (SKALP) Python vol. 3 2014 51 / 66 Manager kontekstu - denicja def __enter__(self): """ Wejscie / utworzenie kontekstu """ print 'Wejscie do kontekstu' self.ctx = ManagerKontekstu.Kontekst() self.ctx.counter += 1 return self.ctx def __exit__(self, *args): """ Wyjscie z kontekstu (sprzatanie) """ print 'Wyjscie z kontekstu' self.ctx.counter += 1 Zmienna counter jest zdeniowana »eby pokaza¢ dziaªanie oraz rozdzielenie kontekstu i managera. M. Alichniewicz (SKALP) Python vol. 3 2014 52 / 66 Manager kontekstu - wywoªanie W Pythonie blok kontekstu jest deniowany nast¦puj¡co: with ManagerKontekstu() as kontekst: # zrob cos z kontekstem # albo wpisz pass W naszym zdeniowanym poprzednio kontek±cie mamy wtedy: if __name__ == "__main__": with ManagerKontekstu() as kontekst: print '>>> Zwiekszam kontekst' kontekst.counter += 1 print 'Counter wynosi: %d' % kontekst.counter M. Alichniewicz (SKALP) Python vol. 3 2014 53 / 66 Manager kontekstu - wywoªanie Je»eli wykonamy nasz kod - dostaniemy na ekranie Counter wynosi: 4. Zmienna counter nale»y do kontekstu, ale zwi¦kszamy j¡ równie» przy wchodzeniu do tego kontekstu przez wywoªanie odpowiedniej metody managera. Przygotujmy sobie bardziej praktyczny przykªad managera kontekstu (docsting'i pomi¦te ze wzgl¦du na oszcz¦dno±¢ miejsca): import time class TicToc(): def __enter__(self): self.tic = time.time() return self def __exit__(self, *args): self.toc = time.time() self.elapsed = self.toc - self.tic M. Alichniewicz (SKALP) Python vol. 3 2014 54 / 66 Manager kontekstu - wywoªanie Sprawd¹my wi¦c jak to dziaªa: with TicToc() as timer: # jakies akcje time.sleep(5) # przerwa na 5 sekund # jakies dalsze akcje print 'Czas wykonywania: %f' % timer.elapsed W efekcie na ekranie powinni±my zobaczy¢ ci¡g podobny do Czas wykonywania: 5.00281691551 (czas mo»e si¦ ró»ni¢ - ale powinien by¢ wi¦kszy od 5 w naszym przypadku) M. Alichniewicz (SKALP) Python vol. 3 2014 55 / 66 Operacje na plikach M. Alichniewicz (SKALP) Python vol. 3 2014 56 / 66 Operacje na plikach Python, jak zdecydowana wi¦kszo±¢ j¦zyków programowania, umo»liwia dziaªania na plikach. Do otwarcia pliku sªu»y - dosy¢ logicznie nazwana funkcja open: fp = open('nazwa_pliku', 'tryb') Generalnie, mamy 3 tryby otwarcia pliku: r - otwarcie do odczytu - ustawia wska¹nik na pocz¡tek pliku (domy±lny tryb) w - otwarcie do zapisu i skasowanie aktualnej zawarto±ci pliku - ustawia wska¹nik na pocz¡tek pliku (je»eli plik nie istnieje, to go tworzy) a - otwarcie do zapisu - ustawia wska¹nik na koniec pliku (je»eli plik nie istnieje, to go tworzy) M. Alichniewicz (SKALP) Python vol. 3 2014 57 / 66 Operacje na plikach Tryby te mo»na dodatkowo rozbudowywa¢ poprzez dodatkowe modykatory: b - otwiera plik w trybie binarnym (dla systemów gdzie jest rozró»nienie plik tekstowy / plik binarny) U - otwiera plik tekstowy w trybie universal newlines - czyli jako znak nowej linii na równi jest traktowany + \n, \r oraz \r\n (tryb domy±lny) - otwiera plik do zapisu (je»eli plik nie istnieje, to go tworzy) M. Alichniewicz (SKALP) Python vol. 3 2014 58 / 66 Operacje na plikach Funkcja open, w przypadku Pythona zwraca obiekt typu le, zamiast uchwtu o pliku przekazywanego do funkcji odpowiedzialnych za odczyt i zapis jak w przypadku wi¦kszo±ci j¦zyków programowania. Obiekt typu le ma szereg przydatnych do tego metod, zaczynaj¡c od podstawowych: close() - zamyka plik write(str) - zapisuje ci¡g znaków do pliku. Nale»y pami¦ta¢ »e wywoªanie tej metody nie zawsze powoduje natychmiastowy zapis zycznie do pliku, czasami jest to tylko zapis do bufora read(n) - odczytuje n bajtów z pliku. Je»eli rozmiar nie jest podany, odczytuje caªy plik (uwaga przy du»ych plikach!) readline(n) - je»eli nie podano n, odczytuje caª¡ lini¦. Je»eli podane n, to jest to maksymalna liczba bajtów jaka mo»e by¢ odczytana jest (w efekcie mo»emy odczyta¢ niekompletn¡ lini¦). M. Alichniewicz (SKALP) Python vol. 3 2014 59 / 66 Operacje na plikach Ko«cz¡c na tych rzadziej wykorzystywanych: ush() - wymusza od±wie»enie bufora, tj. zapis jego zawarto±ci zycznie na dysk readlines() - odczytuje wszystkie linie z pliku i tworzy z nich list¦ (odczyt zaczyna od aktualnego wska¹nika) seek(pointer) - ustawia wska¹nik wewn¡trz pliku (miejsce z którego b¦dziemy czyta¢ / do którego pisa¢) tell() - zwraca aktualn¡ warto±¢ wska¹nika writelines(list) - zapisuje do pliku wszystkie elementy z listy, b¡d¹ innego iterowalnego argumentu podanego w argumencie (czyli mo»e to np. by¢ generator). M. Alichniewicz (SKALP) Metoda nie dodaje znaków ko«ca linii! Python vol. 3 2014 60 / 66 Operacje na plikach Jak plik otworzymy, to trzeba go pó¹niej zamkn¡¢ (dajmy innym programom szans¦ na dost¦p). Poniewa» obiekt typu le ma zaimplementowane metody __enter__ oraz __exit__, jest wi¦c with, managerem kontekstu - a wi¦c mo»emy wykorzysta¢ tutaj instrukcj¦ która zamknie plik za nas: # Otworz plik i nie martw sie o zamkniecie with open('moj_plik', 'w') as fp: fp.write('Ladna dzis pogoda!\n') fp.write('Nieprawdaz?') print fp.closed # True - czyli plik zamkniety M. Alichniewicz (SKALP) Python vol. 3 2014 61 / 66 Zadanie M. Alichniewicz (SKALP) Python vol. 3 2014 62 / 66 Zadanie Napisa¢ program, który odczyta od u»ytkownika wyra»enie matematyczne (zawieraj¡ce nawiasy, jednak ograniczone do operatorów matematycznych, bez funkcji - podane w tradycyjnej notacji) oraz obliczy jego warto±¢. Zadanie trzeba wykona¢ bez wykorzystania funkcji eval! Podpowiedzi: Kluczem do rozwi¡zania jest Odwrotna Notacja Polska (w skrócie - ONP, albo RPN z angielskiego Rerverse Polish Notation) listy mog¡ sªu»y¢ zarówno jako bufor FIFO, albo jako FILO - czyli stos Program zbuduj w taki sposób, »eby mo»na byªo go wykorzystywa¢ bez modykacji w innych projektach (mile widziane obudowanie logiki w klas¦ Wbudowane w Pythona - uniwersalno±¢ tej klasy, wliczaj¡c w to sposób podania danych - mile widziane). Mo»na poczyni¢ dodatkowe zaªo»enia przy wprowadzaniu danych, jak np. biaªe znaki w tre±ci wyra»enia, jednak nale»y pami¦ta¢ »e ko«cowy u»ytkownik M. Alichniewicz (SKALP) nie ma obycia w Pythonie. Python vol. 3 2014 63 / 66 Materiaªy M. Alichniewicz (SKALP) Python vol. 3 2014 64 / 66 Materiaªy Dokumentacja j¦zyka Python https://docs.python.org/2/ Forum StackOverow http://stackoverflow.com/ Improve Your Python: 'yield' and Generators Explained http://www.jeffknupp.com/blog/2013/04/07/ improve-your-python-yield-and-generators-explained/ Understanding Python Decorators in 12 Easy Steps! http://simeonfranklin.com/blog/2012/jul/1/ python-decorators-in-12-steps/ Understanding Python's with statement http://effbot.org/zone/python-with-statement.htm M. Alichniewicz (SKALP) Python vol. 3 2014 65 / 66 Spotkania z Pythonem Cz¦±¢ 3 - wyra»enia lambda, generatory, dekoratory, operacje na plikach Michaª Alichniewicz Studenckie Koªo Automatyków SKALP Gda«sk 2014 Dzi¦kuj¦ za uwag¦! Na licencji Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License. M. Alichniewicz (SKALP) Python vol. 3 2014 66 / 66