Você está na página 1de 425

Zanurkuj w Pythonie

Stworzone na Wikibooks, bibliotece wolnych podrcznikw.

Wydanie I z dnia 17 lutego 2008 Copyright c 2005-2008 uytkownicy Wikibooks. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled GNU Free Documentation License. Udziela si zezwolenia na kopiowanie, rozpowszechnianie i/lub modykacj treci artykuw polskich Wikibooks zgodnie z zasadami Licencji GNU Wolnej Dokumentacji (GNU Free Documentation License) w wersji 1.2 lub dowolnej pniejszej opublikowanej przez Free Software Foundation; bez Sekcji Niezmiennych, Tekstu na Przedniej Okadce i bez Tekstu na Tylnej Okadce. Kopia tekstu licencji znajduje si w czci zatytuowanej GNU Free Documentation License. Dodatkowe objanienia s podane w dodatku Dalsze wykorzystanie tej ksiki. Wikibooks nie udziela adnych gwarancji, zapewnie ani obietnic dotyczcych poprawnoci publikowanych treci. Nie udziela te adnych innych gwarancji, zarwno jednoznacznych, jak i dorozumianych.

Spis treci
1 Wstp 1.1 O podrczniku . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Instalacja 2.1 Ktry Python jest dla ciebie najlepszy? 2.2 Python w systemie Windows . . . . . . 2.3 Python w systemie Mac OS . . . . . . . 2.4 Python w systemach Linux . . . . . . . 2.5 Instalacja ze rde . . . . . . . . . . . . 2.6 Interaktywna powoka . . . . . . . . . . 2.7 Podsumowanie . . . . . . . . . . . . . . 3 Pierwszy program 3.1 Nurkujemy . . . . . . . 3.2 Deklarowanie funkcji . . 3.3 Dokumentowanie funkcji 3.4 Wszystko jest obiektem 3.5 Wcicia kodu . . . . . . 3.6 Testowanie moduw . . 1 2 3 4 5 7 10 13 14 15 17 18 20 22 23 26 28 29 30 32 35 41 43 46 48 50 52 56 59 61 62 64 66 68

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

4 Wbudowane typy danych 4.1 acuchy znakw i unikod . . . . . . . . . 4.2 Sowniki . . . . . . . . . . . . . . . . . . . 4.3 Listy . . . . . . . . . . . . . . . . . . . . . 4.4 Krotki . . . . . . . . . . . . . . . . . . . . 4.5 Deklarowanie zmiennych . . . . . . . . . . 4.6 Formatowanie acucha znakw . . . . . . 4.7 Odwzorowywanie listy . . . . . . . . . . . 4.8 czenie list i dzielenie acuchw znakw 4.9 Kodowanie znakw . . . . . . . . . . . . . 4.10 Praca z unikodem . . . . . . . . . . . . . 4.11 Podsumowanie . . . . . . . . . . . . . . . 5 Potga introspekcji 5.1 Nurkujemy . . . . . . . . . . . . . . . . 5.2 Argumenty opcjonalne i nazwane . . . . 5.3 Dwa sposoby importowania moduw . . 5.4 type, str, dir i inne wbudowane funkcje . i

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

5.5 5.6 5.7 5.8 5.9 5.10

Funkcja getattr . . . . . . . . Filtrowanie listy . . . . . . . Operatory and i or . . . . . . Wyraenia lambda . . . . . . Potga introspekcji - wszystko Podsumowanie . . . . . . . .

. . . . . . . . . . . . . . . . razem . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

73 76 78 81 84 87 89 90 93 97 99 102 105 107 109 111 113 114 118 123 126 129 133 135 137 138 139 142 146 150 152 157

6 Obiekty i klasy 6.1 Nurkujemy . . . . . . . . . . . . 6.2 Deniowanie klas . . . . . . . . . 6.3 Tworzenie instancji klasy . . . . 6.4 Klasa opakowujca UserDict . . . 6.5 Metody specjalne . . . . . . . . . 6.6 Zaawansowane metody specjalne 6.7 Atrybuty klas . . . . . . . . . . . 6.8 Funkcje prywatne . . . . . . . . . 6.9 Podsumowanie . . . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

7 Wyjtki i operacje na plikach 7.1 Obsuga wyjtkw . . . . . . . . . . . . . . . . 7.2 Praca na plikach . . . . . . . . . . . . . . . . . 7.3 Ptla for . . . . . . . . . . . . . . . . . . . . . . 7.4 Korzystanie z sys.modules . . . . . . . . . . . . 7.5 Praca z katalogami . . . . . . . . . . . . . . . . 7.6 Wyjtki i operacje na plikach - wszystko razem 7.7 Wyjtki i operacje na plikach - podsumowanie . 8 Wyraenia regularne 8.1 Nurkujemy . . . . . . . . . . . . . . . . . . 8.2 Analiza przypadku: Adresy ulic . . . . . . . 8.3 Analiza przypadku: Liczby rzymskie . . . . 8.4 Skadnia ?n, m? . . . . . . . . . . . . . . . . 8.5 Rozwleke wyraenia regularne . . . . . . . 8.6 Analiza przypadku: Przetwarzanie numerw 8.7 Podsumowanie . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . telefonw . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

9 Przetwarzanie HTML-a 9.1 Nurkujemy . . . . . . . . . . . . . . . . . . . . 9.2 Wprowadzenie do sgmllib.py . . . . . . . . . . 9.3 Wyciganie danych z dokumentu HTML . . . . 9.4 Wprowadzenie do BaseHTMLProcessor.py . . . 9.5 locals i globals . . . . . . . . . . . . . . . . . . 9.6 Formatowanie napisw w oparciu o sowniki . . 9.7 Dodawanie cudzysoww do wartoci atrybutw 9.8 Wprowadzenie do dialect.py . . . . . . . . . . . 9.9 Przetwarzanie HTML-a - wszystko razem . . . 9.10 Podsumowanie . . . . . . . . . . . . . . . . . . ii

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

159 . 160 . 166 . 169 . 172 . 175 . 179 . 181 . 183 . 187 . 190

10 Przetwarzanie XML-a 10.1 Nurkowanie . . . . . . . . . . . . 10.2 Pakiety . . . . . . . . . . . . . . 10.3 Parsowanie XML-a . . . . . . . . 10.4 Wyszukiwanie elementw . . . . 10.5 Dostp do atrybutw elementw 10.6 Podsumowanie . . . . . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

191 192 200 203 207 209 211 213 214 219 224 226 227 230 234 236 237 238 241 242 245 247 249 253 258 261 264

11 Skrypty i strumienie 11.1 Abstrakcyjne rda wejcia . . . . . . . . . . . . . . . . . . 11.2 Standardowy strumie wejcia, wyjcia i bdw . . . . . . 11.3 Buforowanie odszukanego wza . . . . . . . . . . . . . . . . 11.4 Wyszukanie bezporednich elementw potomnych . . . . . . 11.5 Tworzenie oddzielnych funkcji obsugi wzgldem typu wza 11.6 Obsuga argumentw linii polece . . . . . . . . . . . . . . 11.7 Skrypty i strumienie - wszystko razem . . . . . . . . . . . . 11.8 Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . 12 HTTP 12.1 Nurkujemy . . . . . . . . . . . . . . . . 12.2 Jak nie pobiera danych poprzez HTTP 12.3 Waciwoci HTTP . . . . . . . . . . . . 12.4 Debugowanie serwisw HTTP . . . . . . 12.5 Ustawianie User-Agent . . . . . . . . . . 12.6 Korzystanie z Last-Modied i ETag . . 12.7 Obsuga przekierowa . . . . . . . . . . 12.8 Obsuga skompresowanych danych . . . 12.9 HTTP - wszystko razem . . . . . . . . . 12.10Podsumowanie . . . . . . . . . . . . . . 13 SOAP 13.1 Nurkujemy . . . . . . . . . . . . . . . 13.2 Instalowanie odpowiednich bibliotek . 13.3 Pierwsze kroki z SOAP . . . . . . . . 13.4 Debugowanie serwisu sieciowego SOAP 13.5 Wprowadzenie do WSDL . . . . . . . 13.6 Introspekcja SOAP z uyciem WSDL . 13.7 Wyszukiwanie w Google . . . . . . . . 13.8 Rozwizywanie problemw . . . . . . . 13.9 Podsumowanie . . . . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

265 . 266 . 268 . 270 . 271 . 273 . 274 . 277 . 280 . 284 285 286 288 289 293 296 299

14 Testowanie jednostkowe 14.1 Wprowadzenie do liczb rzymskich . . . . 14.2 Nurkujemy . . . . . . . . . . . . . . . . 14.3 Wprowadzenie do romantest.py . . . . . 14.4 Testowanie poprawnych przypadkw . . 14.5 Testowanie niepoprawnych przypadkw 14.6 Testowanie zdroworozsdkowe . . . . . . iii

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

15 Testowanie 2 15.1 roman.py, 15.2 roman.py, 15.3 roman.py, 15.4 roman.py, 15.5 roman.py,

etap etap etap etap etap

1 2 3 4 5

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

303 304 309 314 318 321 325 326 329 337 342 345 346 349 352 355 357 359

16 Refaktoryzacja 16.1 Obsuga bdw . . . . . . . . . . . . . . . . 16.2 Obsuga zmieniajcych si wymaga . . . . 16.3 Refaktoryzacja . . . . . . . . . . . . . . . . 16.4 Postscript . . . . . . . . . . . . . . . . . . . 16.5 Podsumowanie . . . . . . . . . . . . . . . . 16.6 Nurkujemy . . . . . . . . . . . . . . . . . . 16.7 Znajdowanie cieki . . . . . . . . . . . . . 16.8 Filtrowanie listy . . . . . . . . . . . . . . . 16.9 Odwzorowywanie listy . . . . . . . . . . . . 16.10Programowanie koncentrujce si na danych 16.11Dynamiczne importowanie moduw . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

17 Programowanie funkcyjne 361 17.1 Programowanie funkcyjne - wszystko razem . . . . . . . . . . . . . . . . 362 17.2 Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366 18 Funkcje dynamiczne 18.1 Nurkujemy . . . . 18.2 plural.py, etap 1 . 18.3 plural.py, etap 2 . 18.4 plural.py, etap 3 . 18.5 plural.py, etap 4 . 18.6 plural.py, etap 5 . 18.7 plural.py, etap 6 . 18.8 Podsumowanie . . 367 368 369 372 374 376 379 381 385 387 388 391 393 397 401 404 406 407 407 407 407 407

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

19 Optymalizacja szybkoci 19.1 Nurkujemy . . . . . . . . . . . . . . . . 19.2 Korzystanie z moduu timeit . . . . . . 19.3 Optymalizacja wyrae regularnych . . 19.4 Optymalizacja przeszukiwania sownika 19.5 Optymalizacja operacji na listach . . . . 19.6 Optymalizacja operacji na napisach . . . 19.7 Podsumowanie . . . . . . . . . . . . . . A Informacje o pliku A.1 Historia . . . . . . . . . . . . . . A.2 Informacje o pliku PDF i historia A.3 Autorzy . . . . . . . . . . . . . . A.4 Graki . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

iv

B Dalsze wykorzystanie tej ksiki 409 B.1 Wstp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409 B.2 Status prawny . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409 B.3 Wykorzystywanie materiaw z Wikibooks . . . . . . . . . . . . . . . . . 409 C GNU Free Documentation License 411

Rozdzia 1

Wstp

ROZDZIA 1. WSTP

1.1

O podrczniku

Podrcznik ten powstaje na podstawie ksiki Dive into Python (w wikszoci jest to tumaczenie), ktrej autorem jest Mark Pilgrim, a udostpnionej na licencji GNU Free Documentation License.

Autorzy i tumacze
Mark Pilgrim (autor ksiki Dive into Python) Warszk Piotr Kie Roman Froow Andrzej Saski Adam Kubiczek

Rozdzia 2

Instalacja

ROZDZIA 2. INSTALACJA

2.1

Ktry Python jest dla ciebie najlepszy?

Witamy w Pythonie. W tym rozdziale zajmiemy si instalacj Pythona.

Ktry Python jest dla ciebie najlepszy?


Aby mc korzysta z Pythona, najpierw naley go zainstalowa. A moe ju go mamy? Jeeli posiadasz konto na jakimkolwiek serwerze, istnieje due prawdopodobiestwo, e Python jest tam ju zainstalowany. Wiele popularnych dystrybucji Linuksa standardowo instaluje ten jzyk programowania. Systemy Mac OS X 10.2 i nowsze posiadaj dosy okrojon wersj Pythona dostpnego jedynie z poziomu linii polece. Zapewne bdziesz chcia zainstalowa wersj, ktra da Ci wicej moliwoci. Windows domylnie nie zawiera adnej wersji Pythona, ale nie zaamuj si! Istnieje wiele sposobw, by w atwy sposb zainstalowa Pythona w tym systemie operacyjnym. Jak widzisz, wersje Pythona s dostpne na wiele platform i systemw operacyjnych. Moemy zdoby Pythona zarwno dla Windowsa, Mac OS, Mac OS X, wszystkich wariantw Uniksa, w tym Linuksa czy Solarisa, jak i dla Amigi, OS/2, BeOSa, czy te innych systemw, o ktrych najprawdopodobniej nawet nie syszae. Co najwaniejsze, program napisany w Pythonie na jednej platformie, przy zachowaniu niewielkiej dozy ostronoci, zadziaa na jakiejkolwiek innej. Moesz na przykad rozwija swj program pod Windowsem, a nastpnie przenie go do Linuksa. Wracajc do pytania rozpoczynajcego sekcj, Ktry Python jest dla ciebie najlepszy?. Odpowied jest jedna: jakikolwiek, ktry moesz zainstalowa na posiadanym komputerze.

2.2. PYTHON W SYSTEMIE WINDOWS

2.2

Python w systemie Windows

Python w Windowsie
W Windowsie mamy par sposobw zainstalowania Pythona. Firma ActiveState tworzy instalator Pythona zwany ActivePython. Zawiera on kompletn wersje Pythona, IDE z bardzo dobrym edytorem kodu oraz kilka rozszerze dla Windowsa, ktre zapewniaj dostp do specycznych dla Windowsa usug, API oraz rejestru. ActivePython mona pobra nieodpatnie, ale nie jest produktem Open Source. Wydawany jest kilka miesicy po wersji oryginalnej. Drug opcj jest instalacja ocjalnej wersji Pythona, rozprowadzanej przez ludzi, ktrzy rozwijaj ten jzyk. Jest to wersja oglnodostpna, Open Source i zawsze najnowsza. Instalacja ActivePythona Oto procedura instalacji ActivePythona: 1. cigamy ActivePythona ze strony http://www.activestate.com/Products/ ActivePython/. 2. Jeeli uywamy Windows 95/98/ME/NT4/2000, bdziemy musieli najpierw zainstalowa Windows Installer 2.0 dla Windowsa 95/98/Me lub Windows Installer 2.0 dla Windowsa NT4/2000 . 3. Klikamy dwukrotnie na cignity plik ActivePython-(pobrana wersja)-win32-ix86.msi 4. Przechodzimy wszystkie kroki instalatora. 5. Po zakoczeniu instalacji wybieramy Start->Programy->ActiveState ActivePython 2.2->PythonWin IDE. Zobaczymy wtedy ekran z napisem podobnym do poniszego: PythonWin 2.2.2 (#37, Nov 26 2002, 10:24:37) [MSC 32 bit (Intel)] on win32. Portions Copyright 1994-2001 Mark Hammond (mhammond@skippinet.com.au) see Help/About PythonWin for further copyright information. >>> Instalacja Pythona z Python.org 1. Pobieramy z http://www.python.org/ftp/python/ najnowsz wersj instalatora dla Windowsa, ktry oczywicie bdzie mia rozszerzenie .exe. 2. Klikamy dwukrotnie na instalatorze Python-2.xxx.yyy.msi. Nazwa zalee bdzie od cignitej wersji Pytona. 3. Jeeli uywamy Windows 95/98/ME/NT4/2000, bdziemy musieli najpierw zainstalowa Windows Installer 2.0 dla Windowsa 95/98/Me lub Windows Installer 2.0 dla Windowsa NT4/2000 . 4. Przechodzimy przez wszystkie kroki instalatora. 5. Jeeli nie mamy uprawnie administratora, moemy wybra Advanced Options, a nastpnie Non-Admin Install.

ROZDZIA 2. INSTALACJA 6. Po zakoczeniu instalacji, wybieramy Start->Programy->Python 2.x->IDLE (Python GUI). Zobaczymy ekran z napisem podobnym do poniszego:

Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)] on win32 Type "copyright", "credits" or "license()" for more information. **************************************************************** Personal firewall software may warn about the connection IDLE makes to its subprocess using this computers internal loopback interface. This connection is not visible on any external interface and no data is sent to or received from the Internet. **************************************************************** IDLE 1.2.1 >>>

2.3. PYTHON W SYSTEMIE MAC OS

2.3

Python w systemie Mac OS

Python w Mac OS X
W Mac OS X moemy mie Pythona na dwa sposoby: instalujc go lub nie robic tego. Zapewne bdziesz chcia go zainstalowa. Mac OS X 10.2 i nowsze domylnie instaluj okrojon wersj Pythona dostpnego jedynie z linii polece. Jeeli nie przeszkadza Ci praca w linii polece, to pocztkowo taka wersja moe Tobie wystarczy. Jednak nie posiada ona parsera XML, wic jeli dojdziesz do rozdziau mwicego na ten temat i tak bdziesz musia zainstalowa pen wersj. Zamiast wic uywa domylnie zainstalowanej wersji, lepiej bdzie od razu zainstalowa najnowsz, a ktra te dostarczy nam wygodn, graczn powok. Uruchamianie wersji domylnie zainstalowanej z systemem 1. Otwieramy katalog /Applications 2. Otwieramy katalog Utilities 3. Klikamy dwukrotnie na Terminal, by otworzy okienko terminala, ktre zapewni nam dostp do linii polece. 4. Wpisujemy polecenie python. Powinnimy otrzyma mniej wicej takie co: <span>Welcome to Darwin! [localhost:~] you\% python Python 2.2 (\#1, 07/14/02, 23:25:09) [GCC Apple cpp-precomp 6.14] on darwin Type "help", "copyright", "credits", or "license" for more information. >>> [press Ctrl+D to get back to the command prompt] [localhost:~] you\%</span> Instalacja najnowszej wersji Pythona Aby to zrobi postpujemy wedug poniszych krokw: 1. cigamy obraz dysku MacPython-OSX z http://homepages.cwi.nl/~jack/ macpython/download.html. 2. Jeeli pobrany program nie zostanie uruchomiony przez przegldark, klikamy dwukrotnie na MacPython-OSX-(pobrana wersja).dmg by zamontowa obraz dysku w systemie. 3. Klikamy dwukrotnie na instalator MacPython-OSX.pkg. 4. Instalator poprosi o login i haso uytkownika z prawami administratora. 5. Przechodzimy wszystkie kroki instalatora. 6. Po zakoczonej instalacji otwieramy katalog /Applications. 7. Otwieramy katalog MacPython-2.x.

ROZDZIA 2. INSTALACJA 8. Klikamy dwukrotnie na PythonIDE by uruchomi Pythona.

MacPython IDE wywietli ekran powitalny, a nastpnie interaktywn powok. Jeeli jednak powoka si nie pojawi, wybieramy Window->Python Interactive (Cmd-0). Otwarte okienko powinno wyglda podobnie do tego: Python 2.3 (#2, Jul 30 2003, 11:45:28) [GCC 3.1 20020420 (prerelease)] Type "copyright", "credits" or "license" for more information. MacPython IDE 1.0.1 >>> Po instalacji najnowszej wersji, domylnie zainstalowana wersja Pythona nadal pozostanie w systemie. Podczas uruchamiania skryptw zwr uwag z jakiej wersji korzystasz. Dwie wersje Pythona w Mac OS X <span>[localhost:~] you\% python Python 2.2 (\#1, 07/14/02, 23:25:09) [GCC Apple cpp-precomp 6.14] on darwin Type "help", "copyright", "credits", or "license" for more information. >>> [press Ctrl+D to get back to the command prompt] [localhost:~] you\% /usr/local/bin/python Python 2.3 (\#2, Jul 30 2003, 11:45:28) [GCC 3.1 20020420 (prerelease)] on darwin Type "help", "copyright", "credits", or "license" for more information. >>> [press Ctrl+D to get back to the command prompt] [localhost:~] you\%</span> Instalacja Pythona z MacPortw Ta metoda jest najlepsza. Naley wpierw pobra i zainstalowa MacPorts (http: //www.macports.org/). Nastpnie naley odwiey porty sudo port selfupdate Potem moemy wyszukiwa interesujce nasz pakiety. Np. znalezienie wszystkich pakietw do Pythona 2.5.x: port search py25 Waciwa instalacja Pythona: sudo port install python25 Wszystkie programy instalowane t metod s skadowane w /opt/local. Warto wic doda do cieki PATH /opt/local/bin. Dobrze jest te doinstalowa setuptools, ktry daje dostp do pythonowego instalatora pakietw, skryptu easy install. sudo port install py25-setuptools

2.3. PYTHON W SYSTEMIE MAC OS

Przydaje si, gdy nie ma w portach pakietu dla naszej wersji Pythona, np. IPythona. Cz bibliotek mona instalowa MacPortami, a reszt za pomoc easy setup. Na przykad IPythona doinstalujemy za pomoc: sudo easy_install ipython Mona te aktualizowa pakiety: sudo easy_install -U Pylons Due i mae znaki w nazwach pakietw, w wypadku uycia easy install, nie maj znaczenia. Python w Mac OS 9 Mac OS 9 nie posiada domylnie adnej wersji Pythona, ale samodzielna instalacja jest bardzo prosta. 1. cigamy plik MacPython23full.bin z http://homepages.cwi.nl/~jack/macpython/ download.html. 2. Jeeli plik nie zostanie automatycznie rozpakowany przez przegldark, klikamy dwukrotnie na MacPython23full.bin by to zrobi. 3. Klikamy dwukrotnie instalator MacPython23full. 4. Przechodzimy wszystkie kroki instalatora. 5. Po zakoczonej instalacji otwieramy katalog /Applications. 6. Otwieramy katalog MacPython-OS9 2.x. 7. Kliknij dwukrotnie na Python IDE by uruchomi Pythona. MacPython IDE wywietli ekran powitalny, a nastpnie interaktywn powok. Jeeli jednak powoka si nie pojawi, wybieramy Window->Python Interactive (Cmd-0). Otwarte okienko powinno wyglda podobnie do tego: Python 2.3 (#2, Jul 30 2003, 11:45:28) [GCC 3.1 20020420 (prerelease)] Type "copyright", "credits" or "license" for more information. MacPython IDE 1.0.1 >>>

10

ROZDZIA 2. INSTALACJA

2.4

Python w systemach Linux

Python w dystrybucjach Linuksa


Instalacja z gotowych pakietw binarnych dla konkretnej dystrybucji Linuksa jest stosunkowo prosta. Wikszo dystrybucji posiada ju zainstalowan wersj Pythona. Moesz take pokusi si o instalacj ze rde. Wiele dystrybucji Linuksa zawiera graczne narzdzia suce do instalacji oprogramowania. My jednak opiszemy, jak to zrobi w konsoli w wybranych dystrybucjach Linuksa. Python w dystrybucji Red Hat Linux Moemy zainstalowa Pythona wykorzystujc polecenie rpm: localhost:~\$ su Password: [wpisz haso roota] [root@localhost root]# wget http://python.org/ftp/python/2.3/rpms/redhat-9/ python2.3-2.3-5pydotorg.i386.rpm Resolving python.org... done. Connecting to python.org[194.109.137.226]:80... connected. HTTP request sent, awaiting response... 200 OK Length: 7,495,111 [application/octet-stream] ... [root@localhost root]# rpm -Uvh python2.3-2.3-5pydotorg.i386.rpm Preparing... ############################################ [100\%] 1:python2.3 ############################################ [100\%] [root@localhost root]# python #(1) Python 2.2.2 (\#1, Feb 24 2003, 19:13:11) [GCC 3.2.2 20030222 (Red Hat Linux 3.2.2-4)] on linux2 Type "help", "copyright", "credits", or "license" for more information. >>> [wcinij Ctrl+D, eby wyj z programu] [root@localhost root]# python2.3 #(2) Python 2.3 (\#1, Sep 12 2003, 10:53:56) [GCC 3.2.2 20030222 (Red Hat Linux 3.2.2-5)] on linux2 Type "help", "copyright", "credits", or "license" for more information. >>> [wcinij Ctrl+D, eby wyj z programu] [root@localhost root]# which python2.3 #(3) /usr/bin/python2.3 1. Wpisujc polecenie python zostaje uruchomiony Python. Jednak jest to starsza jego wersja, domylnie zainstalowana wraz z systemem. To nie jest to, czego chcemy. 2. Podczas pisania tej ksiki najnowsz wersj by Python 2.3. Za pomoc polecenia python2.3 uruchomimy nowsz, wanie zainstalowan wersje. 3. Jest to pena cieka do nowszej wersji Pythona, ktr dopiero co zainstalowalimy.

2.4. PYTHON W SYSTEMACH LINUX Python w dystrybucji Debian Pythona zainstalujemy wykorzystujc polecenie apt-get.

11

localhost:~$ su Password: [wpisz haso roota] localhost:~# apt-get install python Reading Package Lists... Done Building Dependency Tree... Done The following extra packages will be installed: python2.3 Suggested packages: python-tk python2.3-doc The following NEW packages will be installed: python python2.3 0 upgraded, 2 newly installed, 0 to remove and 3 not upgraded. Need to get 0B/2880kB of archives. After unpacking 9351kB of additional disk space will be used. Do you want to continue? [Y/n] Y Selecting previously deselected package python2.3. (Reading database ... 22848 files and directories currently installed.) Unpacking python2.3 (from .../python2.3_2.3.1-1_i386.deb) ... Selecting previously deselected package python. Unpacking python (from .../python_2.3.1-1_all.deb) ... Setting up python (2.3.1-1) ... Setting up python2.3 (2.3.1-1) ... Compiling python modules in /usr/lib/python2.3 ... Compiling optimized python modules in /usr/lib/python2.3 ... localhost:~# exit logout localhost:~$ python Python 2.3.1 (#2, Sep 24 2003, 11:39:14) [GCC 3.3.2 20030908 (Debian prerelease)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> [wcinij Ctrl+D, eby wyj z programu] Python w dystrybucji Mandriva W konsoli z uytkownika root wpisujemy polecenie: $ su Password: [wpisz haso roota] # urpmi python Python w dystrybucji Fedora/Fedora Core Aby zainstalowa Pythona w dystrybucji Fedora/Fedora Core naley w konsoli wpisa: $ su Password: [wpisz haso roota] # yum install python

12

ROZDZIA 2. INSTALACJA

Mona te zainstalowa Pythona przy instalacji systemu, wybierajc pakiety programistyczne. Python w dystrybucji Gentoo GNU/Linux W Gentoo do instalacji Pythona moemy uy programu emerge: $ su Password: [wpisz haso roota] # emerge python

2.5. INSTALACJA ZE RDE

13

2.5

Instalacja ze rde

Instalacja ze rde
Jeeli wolimy zainstalowa Pythona ze rde, bdziemy musieli pobra kod rdowy z http://www.python.org/ftp/python/. Wybieramy najnowsz wersj (najwyszy numer) i cigamy plik .tgz, a nastpnie wykonujemy standardowe komendy instalacyjne (./configure, make, make install). localhost:~\$ su Password: [wpisz haso roota] localhost:~# wget http://www.python.org/ftp/python/2.3/Python-2.3.tgz Resolving www.python.org... done. Connecting to www.python.org[194.109.137.226]:80... connected. HTTP request sent, awaiting response... 200 OK Length: 8,436,880 [application/x-tar] ... localhost:~# tar xfz Python-2.3.tgz localhost:~# cd Python-2.3 localhost:~/Python-2.3# ./configure checking MACHDEP... linux2 checking EXTRAPLATDIR... checking for --without-gcc... no ... localhost:~/Python-2.3# make gcc -pthread -c -fno-strict-aliasing -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -I. \ -I./Include -DPy\_BUILD\_CORE -o Modules/python.o Modules/python.c gcc -pthread -c -fno-strict-aliasing -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -I. \ -I./Include -DPy\_BUILD\_CORE -o Parser/acceler.o Parser/acceler.c gcc -pthread -c -fno-strict-aliasing -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -I. \ -I./Include -DPy\_BUILD\_CORE -o Parser/grammar1.o Parser/grammar1.c ... localhost:~/Python-2.3# make install /usr/bin/install -c python /usr/local/bin/python2.3 ... localhost:~/Python-2.3# exit logout localhost:~$ which python /usr/local/bin/python localhost:~$ python Python 2.3.1 (#2, Sep 24 2003, 11:39:14) [GCC 3.3.2 20030908 (Debian prerelease)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> [wcinij Ctrl+D, eby wyj z programu] localhost:~$

14

ROZDZIA 2. INSTALACJA

2.6

Interaktywna powoka

Interaktywna powoka
Teraz kiedy ju mamy zainstalowanego Pythona, pewnie si zastanawiamy, co to jest ta interaktywna powoka (interactive shell ), ktr uruchomilimy? Powiedzmy tak: Python umoliwia prac na dwa sposoby. Jest interpreterem skryptw, ktre moemy uruchomi z linii polece lub, jak inne aplikacje, dwukrotnie klikajc na ikonce skryptu, a take jest interaktywn powok, dziki ktrej moemy debugowa, sprawdza dziaanie potrzebnych funkcji czy moliwoci Pythona. Moesz nawet potraktowa powok jako kalkulator! Uruchom teraz powok Pythona w odpowiedni sposb dla twojego systemu i sprawdmy co ona potra: Przykad 1.5 Pierwsze kroki w interaktywnej powoce >>> 1 + 1 2 >>> print hello world hello world >>> x = 1 >>> y = 2 >>> x + y 3 #(1) #(2) #(3)

1. Interaktywna powoka Pythona moe wylicza dowolne wyraenia matematyczne. 2. Potra wykonywa dowolne polecenia Pythona. 3. Moemy przypisa pewne wartoci do zmiennych, ktre s pamitane tak dugo, jak dugo jest uruchomiona nasza powoka (ale nie duej).

2.7. PODSUMOWANIE

15

2.7

Instalacja - podsumowanie

Podsumowanie
W tym momencie powinnimy ju mie zainstalowanego Pythona. W zalenoci od platformy moesz mie zainstalowan wicej ni jedn wersj Pythona. Jeeli tak wanie jest to musisz uwaa na cieki dostpu do programu. Piszc tylko python w linii polece, moe si okaza, e nie uruchamiasz wersji, ktrej akurat potrzebujesz. By moe bdziesz musia podawa pen ciek dostpu lub odpowiedni nazw (python2.2, python2.4 itp.) Gratulujemy i witamy w Pythonie!

16

ROZDZIA 2. INSTALACJA

Rozdzia 3

Pierwszy program

17

18

ROZDZIA 3. PIERWSZY PROGRAM

3.1

Pierwszy program

Czy dostrzeglimy, e wikszo ksiek najpierw przedstawia elementarne zasady programowania, a potem opisuje, jak korzystajc z nich stworzy kompletny i dziaajcy program? My zrobimy inaczej...

Nurkujemy
Oto kompletny, dziaajcy program w Pythonie. Prawdopodobnie jest on dla Ciebie cakowicie niezrozumiay, ale nie przejmuj si tym, poniewa zaraz przeanalizujemy go dokadnie, linia po linii. Przeczytaj go i sprawd, czy co jeste w stanie z niego zrozumie. Przykad 2.1 odbchelper.py #-*- coding: utf-8 -*def buildConnectionString(params): u"""Tworzy acuch znakw na podstawie sownika parametrw. Zwraca acuch znakw. """ return ";".join(["%s=%s" % (k, v) for k, v in params.items()]) if __name__ == "__main__": myParams = {"server":"mpilgrim", \ "database":"master", \ "uid":"sa", \ "pwd":"secret" } print buildConnectionString(myParams) Teraz uruchommy ten program i zobaczmy, co si stanie. W IDE ActivePythona w systemie Windows moemy uruchomi edytowany program wybierajc File->Run... (Ctrl-R). Wynik wywietlany jest w interaktywnym oknie.

W IDE Pythona w systemie Mac OS uruchomimy program wybierajc Python->Run window... (Cmd-R), jednak wczeniej musimy ustawi pewn wan opcj. W tym celu otwieramy plik .py w IDE, wywoujemy menu podrczne klikajc czarny trjkt w prawym grnym rogu okna i upewniamy si, e opcja Run as main jest zaznaczona.

W systemach Unix (take w Mac OS X ) moesz uruchomi program z linii polece poleceniem python odbchelper.py W wyniku uruchomienia programu otrzymujemy:

3.1. NURKUJEMY pwd=secret;database=master;uid=sa;server=mpilgrim

19

20

ROZDZIA 3. PIERWSZY PROGRAM

3.2

Deklarowanie funkcji

Deklarowanie funkcji
Python posiada funkcje, podobnie jak wiele innych jzykw programowania, lecz nie deniujemy ich w oddzielnych plikach nagwkowych (jak np. w C++), czy te nie dzielimy na sekcje interfejsu i implementacji jak w Pascalu. Jeli potrzebujemy jakiej funkcji, po prostu j deklarujemy, na przykad: def buildConnectionString(params): Sowo kluczowe def rozpoczyna deklaracj funkcji, nastpnie podajemy nazw funkcji, a potem w nawiasach parametry. Wiksz liczb parametrw podajemy po przecinkach. Jak wida funkcja nie deniuje zwracanego typu. Podczas deklarowania Pythonowych funkcji nie okrelamy, czy maj one zwraca jak warto, a nawet czy maj cokolwiek zwraca. W rzeczywistoci kada funkcja zwraca pewn warto. Jeeli w funkcji znajduje si instrukcja return, funkcja zwrci okrelon warto, wskazan za pomoc tej instrukcji. W przeciwnym wypadku, gdy dana funkcja nie posiada instrukcji return, zostanie zwrcona warto None, czyli tak zwana warto pusta, a w innych jzykach czsto okrelana jako null lub nil. W Visual Basicu funkcje (te, ktre zwracaj warto) rozpoczynaj si sowem kluczowym function, a procedury (z ang. subroutines, nie zwracaj wartoci) deklarujemy za pomoc sowa sub. W Pythonie nie ma czego takiego jak procedury. S tylko funkcje, a kada z nich zwraca warto (nawet jeeli jest to None) i wszystkie rozpoczynaj si sowem kluczowym def. Argument params nie ma okrelonego typu. W Pythonie typy zmiennych nie okrelamy w sposb jawny. Interpreter sam automatycznie rozpoznaje i ledzi typ zmiennej. W Javie, C++ i innych jzykach z typami statycznymi musimy okreli typ danych zwracany przez funkcj, a take typ kadego argumentu. W Pythonie nigdzie tego nie robimy. Bazujc na wartoci jaka zostaa przypisana zmiennej, Python sam okrela jej typ.

Typy danych w Pythonie a inne jzyki programowania Jeli chodzi o nadawanie typw, jzyki programowania mona podzieli na: statycznie typowane S to jzyki, w ktrych typy s nadawane podczas kompilacji. Wiele tego typu jzykw programowania wymaga deklarowania wszystkich zmiennych przed ich uyciem, przez podanie ich typu. Przykadami takich jzykw jest Java, czy te C. dynamicznie typowane S to jzyki, w ktrych typy zmiennych s nadawane podczas dziaania programu. VBScript i Python s jzykami dynamicznie typowanymi, poniewa nadaj one typ zmiennej podczas przypisania do niej wartoci.

3.2. DEKLAROWANIE FUNKCJI

21

silnie typowane S to jzyki, w ktrych midzy rnymi typami wida wyran granic. Jeli mamy pewn liczb cakowit, to nie moemy jej traktowa jak jaki napis bez wczeniejszego przekonwertowania jej na acuch znakw. sabo typowane S to jzyki, w ktrych moemy nie zwraca uwagi na typ zmiennej. Do takich jzykw zaliczymy VBScript. W tym jzyku moemy, przy tym nie wykonujc adnej wyranej konwersji, poczy acuch znakw 12 z liczb cakowit 3 otrzymujc acuch 123, a nastpnie potraktowa go jako liczb cakowit 123. Konwersja jest wykonywana automatycznie. Python jest jzykiem zarwno dynamicznie typowanym (poniewa nie wymaga wyranej deklaracji typu), jak i silnie typowanym (poniewa zmienne posiadaj wyranie ustalone typy, ktre nie podlegaj automatycznej konwersji).

22

ROZDZIA 3. PIERWSZY PROGRAM

3.3

Dokumentowanie funkcji

Dokumentowanie funkcji
Funkcje mona dokumentowa wstawiajc notk dokumentacyjn (ang. doc string). Przykad 2.2 Deniowanie notki dokumentacyjnej w funkcji buildConnectionString def buildConnectionString(params): u"""Tworzy acuch znakw na podstawie sownika parametrw. Zwraca acuch znakw. """ return ";".join(["%s=%s" % (k, v) for k, v in params.items()]) Trzy nastpujce po sobie cudzysowy wykorzystuje si do tworzenia cigu znakw, ktry moe zajmowa wiele linii. Wszystko co si znajduje pomidzy nimi tworzy pojedynczy acuch znakw; dotyczy to take znakw powrotu karetki i cudzysowu. Potrjne cudzysowy moemy uywa wszdzie, ale najczciej wykorzystuje si je do tworzenia notek dokumentacyjnych. Za pomoc potrjnych cudzysoww ("""...""") moemy w atwy sposb zdeniowa acuch znakw zawierajcy pojedyncze jak i podwjne cudzysowy, podobnie jak w przypadku qq/../ w Perlu. Dziki przedrostkowi u Python zapamita ten acuch znakw w taki sposb, e bdzie potra poprawnie zinterpretowa polskie litery. Wicej szczegw na ten temat dowiemy si w dalszej czci tej ksiki. W powyszym przykadzie wszystko pomidzy potrjnymi cudzysowami jest notk dokumentacyjn funkcji, czyli opisem do czego dana funkcja suy i jak j uywa. Notka dokumentacyjna, o ile istnieje, musi znale si pierwsza w denicji funkcji (czyli zaraz po dwukropku). Python nie wymaga, aby funkcja posiadaa notk dokumentacyjn, lecz powinnimy j zawsze tworzy. Uatwia ona nam, a take innym, zorientowa si w programie. Warto zaznaczy, e notka dokumentacyjna jest dostpna jako atrybut funkcji nawet w trakcie wykonywania programu. Wiele Pythonowych IDE wykorzystuje notki dokumentacyjne do inteligentnej pomocy, czyli sugerowania nazwy funkcji przy jednoczesnym wywietlaniu informacji o niej (wzitej z notki). Moe to stanowi dla nas bardzo dobr pomoc, oczywicie pod warunkiem, e notki dokumentacyjne zostay przez nas zdeniowane...

Materiay dodatkowe Materiay co prawda w jzyku angielskim, ale na pewno warto je chocia przejrze: PEP 257, na temat konwencji notek dokumentacyjnych, Python Style Guide, o tym, jak napisa dobr notk dokumentacyjn,

3.4. WSZYSTKO JEST OBIEKTEM

23

3.4

Wszystko jest obiektem

Wszystko jest obiektem


Wspomnielimy ju wczeniej, e funkcje w Pythonie posiadaj atrybuty, a one s dostpne podczas pracy programu. Funkcje, podobnie jak wszystko inne w Pythonie, s obiektami. Otwrzmy swj ulubiony IDE Pythona i wprowad nastpujcy kod: Przykad 2.3 Odwoywanie do napisu dokumentacyjnego funkcji buildConnectionString >>> import odbchelper #(1) >>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"} >>> print odbchelper.buildConnectionString(params) #(2) pwd=secret;database=master;uid=sa;server=mpilgrim >>> print odbchelper.buildConnectionString.__doc__ #(3) Tworzy acuch znakw na podstawie sownika parametrw. Zwraca acuch znakw.

1. Pierwsza linia importuje program odbchelper jako modu kawaek kodu, ktry moemy uywa interaktywnie. (W Rozdziale 4 zobaczymy przykady programw podzielonych na wiele moduw.) Kiedy ju zaimportujemy modu, moemy odwoa si do jego wszystkich publicznych funkcji, klas oraz atrybutw. Moduy take mog odwoywa si do jeszcze innych moduw. 2. Aby wykorzysta jak funkcj zdeniowan w zaimportowanym module, musimy przed nazw funkcji doczy nazw moduu. Nie moemy napisa buildConnectionString, lecz zamiast tego moemy da odbchelper.buildConnectionString. Jeli kiedykolwiek korzystalimy z klas w Javie, powinnimy zauway pewne podobiestwa. 3. Tym razem zamiast wywoywa funkcj, zapytalimy si o jeden z atrybutw funkcji atrybut doc . W nim Python przechowuje notk dokumentacyjn. import w Pythonie dziaa podobnie jak require w Perlu. Kiedy zaimportujemy jaki modu, odwoujemy si do jego funkcji poprzez modul.funkcja. W Perlu wyglda to troszk inaczej, piszemy modul::funkcja.

cieka przeszukiwania moduw Zanim przejdziemy dalej, naley wspomnie o ciece przeszukiwania moduw. W Pythonie przegldanych jest kilka miejsc w poszukiwaniu importowanego moduu. Generalnie przeszukiwane s wszystkie katalogi zdeniowane w sys.path. Jest to lista, ktr moemy w atwy sposb przeglda i modykowa w podobny sposb jak inne listy (jak to robi dowiemy si w kolejnych rozdziaach).

24

ROZDZIA 3. PIERWSZY PROGRAM

Przykad 2.4 cieka przeszukiwania moduw >>> import sys #(1) >>> sys.path #(2) [, /usr/local/lib/python2.2, /usr/local/lib/python2.2/plat-linux2, /usr/local/lib/python2.2/lib-dynload, /usr/local/lib/python2.2/site-packages, /usr/local/lib/python2.2/site-packages/PIL, /usr/local/lib/python2.2/ site-packages/piddle] >>> sys #(3) <module sys (built-in)> >>> sys.path.append(/my/new/path) #(4) 1. Zaimportowanie moduu sys spowoduje, e wszystkie jego funkcje i atrybuty staj si dostpne. 2. sys.path to lista nazw katalogw, ktre s obecnie przeszukiwane podczas importowania moduu. (Zawarto listy zalena jest od systemu operacyjnego, wersji Pythona i pooenia jego instalacji, wic na twoim komputerze moe wyglda nieco inaczej.) Python przeszuka te katalogi (w zadanej kolejnoci) w poszukiwaniu pliku .py, ktry nazywa si tak samo jak importowany modu. 3. Waciwie to troch rozminlimy si z prawd. Sytuacja jest bardziej skomplikowana, poniewa nie wszystkie moduy wystpuj jako pliki z rozszerzeniem .py. Niektre, tak jak sys s wbudowane w samego Pythona. Wbudowane moduy zachowuj si w ten sam sposb co pozostae, ale nie mamy bezporedniego dostpu do ich kodu rdowego, poniewa nie s napisane w Pythonie (modu sys napisany jest w C). 4. Kiedy dodamy nowy katalog do cieki przeszukiwania, Python przy nastpnych importach przejrzy dodatkowo dodany katalog w poszukiwaniu moduu z rozszerzeniem .py. Nowy katalog bdzie znajdowa si w ciekach szukania tak dugo, jak dugo uruchomiony bdzie interpreter. (Dowiesz si wicej o metodzie append i innych metodach list w kolejnym rozdziale). Co to jest obiekt W Pythonie wszystko jest obiektem i prawie wszystko posiada metody i atrybuty. Kada funkcja posiada wbudowany atrybut doc , ktry zwraca napis dokumentacyjny zdeniowany w kodzie funkcji. Modu sys jest obiektem, ktry posiada midzy innymi atrybut path. W dalszym cigu nie wyjanilimy jeszcze, co to jest obiekt. Kady jzyk programowania deniuje obiekt w inny sposb. W niektrych jzykach obiekt musi posiada atrybuty i metody, a w innych wszystkie obiekty mog dzieli si na rne podklasy. W Pythonie jest inaczej, niektre obiekty nie posiadaj ani atrybutw ani metod (wicej o tym w kolejnym rozdziale) i nie wszystkie obiekty dziel si na podklasy (wicej o tym w rozdziale 5). Wszystko jest obiektem w tym sensie, e moe by przypisane do zmiennej albo stanowi argument funkcji (wicej o tym w rozdziale 4). Poniewa jest to bardzo wane, wic powtrzmy to jeszcze raz: wszystko w Pythonie jest obiektem. acuchy znakw to obiekty, listy to obiekty, funkcje to obiekty, a nawet moduy to obiekty...

3.4. WSZYSTKO JEST OBIEKTEM Materiay dodatkowe

25

Python Reference Manual dokadnie opisuje, co to znaczy, e wszystko w Pythonie jest obiektem e-bot podsumowuje obiekty Pythona

26

ROZDZIA 3. PIERWSZY PROGRAM

3.5

Wcicia kodu

Wcicia kodu
Funkcje w Pythonie nie posiadaj sprecyzowanych pocztkw i kocw oraz adnych nawiasw sucych do zaznaczania, gdzie funkcja si zaczyna, a gdzie koczy. Jedynym separatorem jest dwukropek (:) i wcicia kodu. Przykad 2.5 Wcicia w funkcji buildConnectionString def buildConnectionString(params): u"""Tworzy acuch znakw na podstawie sownika parametrw. Zwraca acuch znakw. """ return ";".join(["%s=%s" % (k, v) for k, v in params.items()]) Bloki kodu deniujemy poprzez wcicia. Przez blok kodu rozumiemy funkcje, instrukcje if, ptle for i while i tak dalej. Wstawiajc wcicie zaczynamy blok, a koczymy go przestajc wstawia wcicia danej wielkoci. Nie ma adnych nawiasw, klamer czy sw kluczowych. Oznacza to, e biae znaki (spacje itp.) maj znaczenie i ich stosowanie musi by konsekwentne. W powyszym przykadzie kod funkcji (wczajc w to notk dokumentacyjn) zosta wcity czterema spacjami. Nie musimy stosowa konkretnie czterech spacji, jednak musimy by konsekwentni (tzn. jeli pierwsze wcicie w funkcji miao 3 spacje, to kolejne wcicia take musz mie 3 spacje). Linia bez wcicia znajdowa si bdzie poza funkcj. Przykad 2.6 Fragment kodu z wciciem po instrukcji if def fib(n): print n =, n if n > 1: return n * fib(n - 1) else: print koniec return 1 #(1) #(2) #(3) #(4)

1. Powysza funkcja, nazwana fib przyjmuje jeden argument: n. Cay kod wewntrz funkcji jest wcity. 2. Wypisywanie danych (na standardowe wyjcie) jest bardzo proste, wystarczy uy sowa kluczowego print. Wyraenie print moe przyj kady typ danych, na przykad acuchy znakw, liczby cakowite i inne wbudowane typy danych jak sowniki i listy, o ktrych dowiemy si w nastpnym rozdziale. Moemy nawet drukowa na ekran rne wartoci w jednej linii. W tym celu podajemy cig wartoci, ktre chcemy wywietli, oddzielajc je przecinkiem. Kada warto jest wtedy wywietlana w tej samej linii i oddzielona spacj (znak przecinka nie jest drukowany). Tak wic, kiedy funkcj fib wywoamy z argumentem 5, na ekranie zobaczymy . 3. Do bloku kodu zaliczamy take instrukcje if. Jeeli wyraenie za instrukcj if bdzie prawdziwe, to zostanie wykonany wcity kod znajdujcy si zaraz pod instrukcj if. W przeciwnym wypadku wykonywany jest blok else.

3.5. WCICIA KODU

27

4. Oczywicie bloki if oraz else mog skada si z wikszej iloci linii, o ile linie te maj wcicia z rwn iloci spacji. Tutaj blok else ma dwie linie. Python nie wymaga adnej specjalnej skadni dla blokw skadajcych si z wielu linii. Po prostu robimy wcicia o rwnej liczbie spacji. Po pocztkowych problemach i nietraonych porwnaniach do Fortrana, pogodzisz si z tym i zobaczysz pozytywne cechy wci. Jedn z gwnych zalet jest to, e wszystkie programy w Pythonie wygldaj podobnie, poniewa wcicia kodu s wymagane przez sam jzyk i nie zale od stylu pisania. Dziki temu jakikolwiek kod jest prostszy do czytania i zrozumienia. Python uywa znaku powrotu karetki (ang. carriage return), czyli znaku koca linii, by oddzieli instrukcje. Natomiast dwukropek i wcicia su, aby oddzieli bloki kodu. C++ oraz Java uywaj rednikw do oddzielania instrukcji, a klamry do separacji blokw kodu.

Materiay dodatkowe Python Reference Manual omawia niektre problemy zwizane z wciciami kodu. Python Style Guide mwi na temat dobrego stylu tworzenia wci.

28

ROZDZIA 3. PIERWSZY PROGRAM

3.6

Testowanie moduw

Testowanie moduw
Moduy Pythona to obiekty, ktre posiadaj kilka przydatnych atrybutw. Moemy ich uy do atwego testowania wasnych moduw. Oto przykad zastosowania triku if name : if __name__ == "__main__": Zwrmy uwag na dwie wane sprawy: pierwsza, e instrukcja warunkowa if nie wymaga nawiasw, a druga, e if koczy si dwukropkiem, a w nastpnych liniach wstawiamy wcity kod. Podobnie jak w jzyku C, Python uywa == dla porwnania i = dla przypisania. W przeciwiestwie do C, w Pythonie nie moemy dokonywa przypisania w dowolnych miejscach kodu, w tym w instrukcji warunkowej (np. if x = 10 nie jest poprawny). Nie ma szansy, aby przypadkowo przypisa warto do zmiennej, zamiast zastosowa porwnanie. Na czym polega zastosowany trik? Moduy to obiekty, a kady z nich posiada wbudowany atrybut name . name zaley od tego, w jaki sposb korzystamy z danego moduu. Jeeli importujemy modu, wtedy name jest nazw pliku moduu bez cieki do katalogu, czy rozszerzenia pliku. Moemy take uruchomi bezporednio modu jako samodzielny program, a wtedy name przyjmie domyln warto main . >>> import odbchelper >>> odbchelper.__name__ odbchelper Wiedzc o tym, moemy zaprojektowa test wewntrz swojego moduu, wykorzystujc do tego instrukcj if. Jeli uruchomisz bezporednio dany modu, atrybut name jest rwny main , a wic test zostanie wykonany. Podczas importu tego moduu, name przyjmie inn warto, wic test si nie uruchomi. Pomoe nam to rozwija i wyszukiwa bdy w programie (czyli debugowa), zanim dany modu zintegrujemy z wikszym programem. Aby w MacPythonie mona byo skorzysta z tego triku, naley klikn w czarny trjkt w prawym grnym rogu okna i upewni si, e zaznaczona jest opcja Run as main .

Materiay dodatkowe Python Reference Manual omawia szczegy dotyczce importowania moduw, istnieje take polskie tumaczenie.

Rozdzia 4

Wbudowane typy danych

29

30

ROZDZIA 4. WBUDOWANE TYPY DANYCH

4.1

acuchy znakw i unikod

Za moment powrcimy do naszego pierwszego programu, jednak najpierw musimy zrozumie, czym s sowniki, krotki i listy. Jeli cho troch umiesz programowa w Perlu, to pewnie masz ju pewn wiedzy na temat sownikw, czy list, ale pewnie nie wiesz, czym s krotki. Najpierw zajmiemy si jednym z najczciej uywanych typw danych, czyli acuchami znakw.

acuchy znakw i unikod


acuchy znakw su do przechowywania napisu lub pewnych danych bajtowych. W Pythonie, podobnie jak w wikszoci innych jzykw programowania tworzymy acuchy znakw 1 (ang. string) poprzez umieszczenie danego tekstu w cudzysowach. Przykad 3.1 Deniowanie acucha znakw >>> text = "Nie za dugi tekst" #(1) >>> text Nie za d\xc5\x82ugi tekst #(2) >> print text Nie za dugi tekst #(3) >>> text2 = Kolejny napis, ale bez polskich liter #(4) >>> text2 Kolejny napis, ale bez polskich liter >>> text3 = Dugi tekst,\nktry po przecinku znajdzie si w nastpnej linii #(5) >>> print text3 Dugi tekst, ktry po przecinku znajdzie si w nastpnej linii >>> text4 = rTutaj znaki specjalne np.\n \t, czy te \x26 nie zostan zinterpretowane >>> print text4 Tutaj znaki specjalne np.\n \t, czy te \x26 nie zostan zinterpretowane 1. W ten sposb stworzylimy acuch znakw Nie za dugi tekst, ktry z kolei przypisalimy do zmiennej text. Zauwamy, e wewntrz acucha mog si znajdowa polskie litery. 2. Otrzymany w tym miejscu wynik na wyjciu moe si nieco rni na rnych systemach. Jest to zalene od kodowania znakw w systemie operacyjnym, z ktrego korzystamy. Komputer uruchomiony przez autorw korzysta z kodowania UTF-8. Zauwamy, e litera w systemie UTF-8 to dwa bajty "\xc5\x82". 3. Wszystko zostao adnie wypisane. Tam gdzie jest widzimy , a nie jakie krzaki. 4. acuch znakw nie musi by deklarowany w podwjnym cudzysowie, ale moe by te to pojedynczy cudzysw. 5. Znaki specjalne wstawiamy dodajc odwrotny ukonik (tzw. backslash) np. \n.
1W

tym podrczniku acuchy znakw bdziemy czasami nazywali napisami.

4.1. ACUCHY ZNAKW I UNIKOD

31

6. Moemy take w stworzy acuch znakw w tzw. sposb surowy. Aby to uczyni, poprzedzamy acuch znakw liter r. Wewntrz surowego acucha znakw odwrotny ukonik nie jest interpretowany. Mona powiedzie, e znaki specjalne w takim acuchu nie s znakami specjalnymi. To co napiszemy, to bdziemy mieli. Unikod Poniewa mwimy w jzyku polskim, piszemy w tym jzyku, a ponadto czytamy w tym jzyku, zapewne chcielibymy tworzy programy, ktre dobrze sobie daj rad ze znakami tego jzyka. Doskonaym do tego rozwizaniem jest unikod (ang. unicode). Unikod przechowuje nie tylko polskie znaki, ale jest systemem reprezentowania znakw ze wszystkich jzykw wiata Przykad 3.2 Deniowanie unikodowego acucha znakw >>> text = u"Nie za dugi tekst" >>> text uNie za d\u0142ugi tekst >>> print text Nie za dugi tekst #(1) #(2)

1. Aby utworzy unikodowy napis, dodajemy przedrostek u i tyle. 2. Otrzymasz taki sam wynik. Dane przechowywane w unikodzie nie zale od systemu kodowania, z ktrego korzysta Twj komputer. Pamitamy, jak w poprzednim rozdziale powiedzielimy, e do notek dokumentacyjnych zosta dodany przedrostek u, aby Python potra poprawnie zinterpretowa polskie znaki. Wtedy wanie wykorzystalimy unikod. Przykad 3.3 Unikod w buildConnectionString def buildConnectionString(params): uTworzy acuch znakw na podstawie sownika parametrw. Zwraca acuch znakw.

32

ROZDZIA 4. WBUDOWANE TYPY DANYCH

4.2

Sowniki

Sowniki
Jednym z wbudowanych typw s sowniki (ang. dictionary). Okrelaj one wzajemn relacj midzy kluczem, a wartoci. Sowniki w Pythonie s podobne do haszy w Perlu. W Perlu zmienne przechowujce hasz s reprezentowane zawsze przez pocztkowy znak %. W Pythonie typ danych jest automatycznie rozpoznawany. Pythonowy sownik przypomina ponadto instancj klasy Hashtable w Javie, a take instancj obiektu Scripting.Dictionary w Visual Basicu.

Deniowanie sownikw Przykad 3.4 Deniowanie sownikw >>> d = {"server":"mpilgrim", "database":"master"} >>> d {database: master, server: mpilgrim} >>> d["server"] mpilgrim >>> d["database"] master >>> d["mpilgrim"] Traceback (most recent call last): File "<stdin>", line 1, in ? KeyError: mpilgrim #(1)

#(2) #(3) #(4)

1. Najpierw utworzylimy nowy sownik z dwoma elementami i przypisalimy go do zmiennej d. Kady element w sowniku jest par klucz -warto, a zbir elementw jest ograniczony nawiasem klamrowym. 2. server jest kluczem, a skojarzon z nim wartoci jest mpilgrim, do ktrej odwoujemy si poprzez d[server]. 3. database jest kluczem, a skojarzon z nim wartoci jest master, do ktrej odwoujemy si poprzez d[database]. 4. Moesz dosta si do wartoci za pomoc klucza, ale nie moesz dosta si do klucza za pomoc wartoci. Tak wic d[server] zwraca mpilgrim, ale wywoanie d[mpilgrim] sprawi, e zostanie rzucony wyjtek, poniewa mpilgrim nie jest kluczem sownika d. Modykowanie sownika Przykad 3.5 Modykowanie sownika >>> d {database: master, server: mpilgrim} >>> d["database"] = "pubs" >>> d

#(1)

4.2. SOWNIKI {database: pubs, server: mpilgrim} >>> d["uid"] = "sa" #(2) >>> d {database: pubs, uid: sa, server: mpilgrim}

33

1. Klucze w sowniku nie mog si powtarza. Przypisujc warto do istniejcego klucza, bdziemy nadpisywa starsz warto. 2. W kadej chwili moesz doda now par klucz-warto. Skadnia jest identyczna do tej, ktra modykuje istniejc warto. Czasami moe to spowodowa pewien problem. Moemy myle, e dodalimy now warto do sownika, ale w rzeczywistoci nadpisalimy ju istniejc. Zauwamy, e sownik nie przechowuje kluczy w sposb posortowany. Wydawaoby si, e klucz uid powinien si znale za kluczem server, poniewa litera s jest wczeniej w alfabecie ni u. Jednak tak nie jest. Elementy sownika znajduj si w losowej kolejnoci. W sownikach nie istnieje okrelona kolejno ukadania elementw. Nie moemy dosta si do elementw w sowniku w okrelonej, uporzdkowanej kolejnoci (np. w porzdku alfabetycznym). Oczywicie s sposoby, aby to osign, ale te sposoby nie s wbudowane w sam sownik. Pracujc ze sownikami pamitajmy, e wielko liter kluczy ma znaczenie. Przykad 3.6 Nazwy kluczy s wraliwe na wielko liter >>> d = {} >>> d["klucz"] = "warto" >>> d["klucz"] = "inna warto" #(1) >>> d {klucz: inna warto} >>> d["Klucz"] = "jeszcze inna warto" #(2) >>> d {klucz: inna warto, Klucz: jeszcze inna warto} 1. Przypisujc warto do istniejcego klucza zamieniamy star warto na now. 2. Nie jest to przypisanie do istniejcego klucza, a poniewa acuchy znakw w Pythonie s wraliwe na wielko liter, dlatego te klucz nie jest tym samym co Klucz. Utworzylimy now par klucz-warto w sowniku. Obydwa klucze mog si wydawa podobne, ale dla Pythona s zupenie inne. Przykad 3.7 Mieszanie typw danych w sowniku >>> d {bazadanych: pubs, serwer: mpilgrim, uid: sa} >>> d["licznik"] = 3 >>> d {bazadanych: pubs, serwer: mpilgrim, licznik: 3, uid: sa}

#(1)

34

ROZDZIA 4. WBUDOWANE TYPY DANYCH

>>> d[42] = "douglas" #(2) >>> d {42: douglas, bazadanych: pubs, serwer: mpilgrim, licznik: 3, uid: sa} 1. Sowniki nie s przeznaczone tylko dla acuchw znakw. Warto w sowniku moe by dowolnym typem danych: acuchem znakw, liczb cakowit, obiektem, a nawet innym sownikiem. W pojedynczym sowniku wszystkie wartoci nie musz by tego samego typu; moemy wstawi do niego wszystko, co chcemy. 2. Klucze w sowniku s bardziej restrykcyjne, ale mog by acuchami znakw, liczbami cakowitymi i kilkoma innymi typami. Klucze wewntrz jednego sownika nie musz posiada tego samego typu. Usuwanie pozycji ze sownika Przykad 3.8 Usuwanie pozycji ze sownika >>> d {licznik: 3, bazadanych: master, serwer: mpilgrim, 42: douglas, uid: sa} >>> del d[42] >>> d {licznik: 3, bazadanych: master, serwer: mpilgrim, uid: sa} >>> d.clear() >>> d {} 1. Instrukcja del kae usun okrelon pozycj ze sownika, ktra jest wskazywana przez podany klucz. 2. Instrukcja clear usuwa wszystkie pozycje ze sownika. Zbir pusty ograniczony przez nawiasy klamrowe oznacza, e sownik nie ma adnego elementu. Materiay dodatkowe How to Think Like a Computer Scientist uczy o sownikach i pokazuje, jak wykorzysta sowniki do tworzenia rzadkich macierzy. W Python Knowledge Base moemy znale wiele przykadw kodw wykorzystujcych sowniki. Python Cookbook wyjania, jak sortowa wartoci sownika wzgldem klucza. Python Library Reference opisuje wszystkie metody sownika.

4.3. LISTY

35

4.3
Listy

Listy

Listy s jednym z najwaniejszych typw danych. Mona si z nimi spotka w Visual Basicu. W tym jzyku s okrelane jako tablice. Listy przypominaj tablice w Perlu. W Perlu zmienna, ktra przechowuj list rozpoczyna si od znaku @, natomiast w Pythonie nazwa moe by dowolna, poniewa Python automatycznie rozpoznaje typ.

Listy w Pythonie to co wicej ni tablice w Javie (chocia mona to traktowa jako jedno). Lepsz analogi jest klasa ArrayList, w ktrej mona przechowywa dowolny obiekt i dynamicznie dodawa nowe pozycje.

Deniowanie list Przykad 3.9 Deniowanie list >>> li = ["a", "b", "mpilgrim", "z", "przykad"] >>> li [a, b, mpilgrim, z, przykad] >>> li[0] a >>> li[4] przykad #(1)

#(2) #(3)

1. Najpierw zdeniowalimy list picioelementow. Zauwamy, e lista zachowuje swj oryginalny porzdek i nie jest to przypadkowe. Lista jest uporzdkowanym zbiorem elementw ograniczonym nawiasem kwadratowym. 2. Lista moe by uywana tak jak tablica zaczynajca si od 0. Pierwszym elementem niepustej listy o nazwie li jest zawsze li[0]. 3. Ostatnim elementem picioelementowej listy jest li[4], poniewa indeksy s liczone zawsze od 0. Przykad 3.10 Ujemne indeksy w listach >>> li [a, b, mpilgrim, z, przykad] >>> li[-1] przykad >>> li[-3] mpilgrim

#(1) #(2)

1. Za pomoc ujemnych indeksw odnosimy si do elementw idcych od koca do pocztku tzn. li[-1] oznacza ostatni element, li[-2] przedostatni, li[-3] odnosi si do 3 od koca elementu itd. Ostatnim elementem niepustej listy jest zawsze li[-1].

36

ROZDZIA 4. WBUDOWANE TYPY DANYCH 2. Jeli ciko ci zrozumie o co w tym wszystkim chodzi, moesz pomyle o tym w ten sposb: li[-n] == li[len(li) - n]. len to funkcja zwracajca ilo elementw listy. Tak wic w tym przypadku li[-3] == li[5 - 3] == li[2].

Przykad 3.11 Wycinanie list >>> li [a, b, mpilgrim, z, przyklad] >>> li[1:3] [b, mpilgrim] >>> li[1:-1] [b, mpilgrim, z] >>> li[0:3] [a, b, mpilgrim]

#(1) #(2) #(3)

1. Moesz pobra podzbir listy, ktry jest nazywany wycinkiem (ang. slice), poprzez okrelenie dwch indeksw. Zwracan wartoci jest nowa lista zawierajca wszystkie elementy z listy rozpoczynajce si od pierwszego wskazywanego indeksu (w tym przypadku li[1]) i idc w gr koczy na drugim wskazywanym indeksie, nie doczajc go (w tym przypadku li[3]). Kolejno elementw wzgldem wczeniejszej listy jest take zachowana. 2. Moemy take poda ujemn warto ktrego indeksu. Wycinanie wtedy take dobrze zadziaa. Jeli to pomoe, moemy pomyle tak: czytamy list od lewej do prawej, pierwszy indeks okrela pierwszy potrzebny element, a drugi okrela element, ktrego nie chcemy. Zwracana warto zawiera wszystko midzy tymi dwoma przedziaami. 3. Listy s indeksowane od zera tzn. w tym przypadku li[0:3] zwraca pierwsze trzy elementy listy, rozpoczynajc od li[0], a koczc na li[2], ale nie doczajc li[3]. Przykad 3.12 Skrty w wycinaniu >>> li [a, b, mpilgrim, z, przykad] >>> li[:3] [a, b, mpilgrim] >>> li[3:] [z, przykad] >>> li[:] [a, b, mpilgrim, z, przykad]

#(1) #(2) (3) #(2) (4)

1. Jeli lewy indeks wynosi 0, moemy go opuci, warto 0 jest domylna. li[:3] jest tym samym, co li[0:3] z poprzedniego przykadu. 2. Podobnie, jeli prawy indeks jest dugoci listy, moemy go pomin. Tak wic li[3:] jest tym samym, co li[3:5], poniewa lista ta posiada pi elementw. 3. Zauwamy pewn symetryczno. W picioelementowej licie li[:3] zwraca pierwsze 3 elementy, a li[3:] zwraca dwa ostatnie (a w sumie 3 + 2 = 5). W rzeczywistoci li[:n] bdzie zwraca zawsze pierwsze n elementw, a li[n:]

4.3. LISTY

37

pozosta liczb, bez wzgldu na szeroko listy (n moe by wiksze od dugoci listy). 4. Jeli obydwa indeksy zostan pominite, wszystkie elementy zostan doczone. Nie jest to jednak to samo, co oryginalna lista li. Jest to nowa lista, ktra posiada wszystkie takie same elementy. li[:] tworzy po prostu kompletn kopi listy. Dodawanie elementw do listy Przykad 3.13 Dodawanie elementw do listy >>> li [a, b, mpilgrim, z, przykad] >>> li.append("nowy") #(1) >>> li [a, b, mpilgrim, z, przykad, nowy] >>> li.insert(2, "nowy") #(2) >>> li [a, b, nowy, mpilgrim, z, przykad, nowy] >>> li.extend(["dwa", "elementy"]) #(3) >>> li [a, b, nowy, mpilgrim, z, przykad, nowy, dwa, elementy] 1. Dodajemy pojedynczy element do koca listy za pomoc metody append. 2. Za pomoc insert wstawiamy pojedynczy element do listy. Numeryczny argument jest indeksem, pod ktrym ma si znale wstawiana warto; reszta elementw, ktra znajdowaa si pod tym indeksem lub miaa wikszy indeks, zostanie przesunita o jeden indeks dalej. Zauwamy, e elementy w licie nie musz by unikalne i mog si powtarza; w przykadzie mamy dwa oddzielne elementy z wartoci nowy li[2] i li[6]. 3. Za pomoc extend czymy list z inn list. Nie moemy wywoa extend z wieloma argumentami, trzeba j wywoywa z pojedynczym argumentem list. W tym przypadku ta lista ma dwa elementy. Przykad 3.14 Rnice midzy extend a append >>> li = [a, b, c] >>> li.extend([d, e, f]) >>> li [a, b, c, d, e, f] >>> len(li) 6 >>> li[-1] f >>> li = [a, b, c] >>> li.append([d, e, f]) >>> li [a, b, c, [d, e, f]] >>> len(li) #(1)

#(2)

#(3)

#(4)

38 4 >>> li[-1] [d, e, f]

ROZDZIA 4. WBUDOWANE TYPY DANYCH

1. Listy posiadaj dwie metody extend i append, ktre wygldaj na to samo, ale w rzeczywistoci s cakowicie rne. extend wymaga jednego argumentu, ktry musi by list i dodaje kady element z tej listy do oryginalnej listy. 2. Rozpoczlimy z list trjelementow (a, b i c) i rozszerzylimy j o inne trzy elementy (d, e i f) za pomoc extend, tak wic mamy ju sze elementw. 3. append wymaga jednego argumentu, ktry moe by dowolnym typem danych. Metoda ta po prostu dodaje dany element na koniec listy. Wywoalimy append z jednym argumentem, ktry jest list z trzema elementami. 4. Teraz oryginalna lista, pierwotnie zawierajca trzy elementy, zawiera ich cztery. Dlaczego cztery? Poniewa ostatni element przed chwil do niej wstawilimy. Listy mog wewntrz przechowywa dowolny typ danych, nawet inne listy. Nie uywajmy append, jeli zamierzamy list rozszerzy o kilka elementw. Przeszukiwanie list Przykad 3.15 Przeszukiwanie list >>> li [a, b, nowy, mpilgrim, z, przykad, nowy, dwa, elementy] >>> li.index("przykad") #(1) 5 >>> li.index("nowy") #(2) 2 >>> li.index("c") #(3) Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: list.index(x): x not in list >>> "c" in li #(4) False 1. index znajduje pierwsze wystpienie pewnej warto w licie i zwraca jego indeks. 2. index znajduje pierwsze wystpienie wartoci w licie. W tym przykadzie, nowy wystpuje dwa razy w li[2] i li[6], ale metoda index bdzie zawsze zwraca pierwszy indeks, czyli 2. 3. Jeli warto nie zostanie znaleziona, Python zgosi wyjtek. Takie zachowanie nie jest czsto spotykane w innych jzykach, w wielu jzykach w takich przypadkach zostaje zwrcony niepoprawny indeks. Takie zachowanie Pythona jest dosy dobrym posuniciem, poniewa umoliwia szybkie wychwycenie bdu w kodzie, a dziki temu program nie bdzie bdnie dziaa operujc na niewaciwym indeksie.

4.3. LISTY

39

4. Aby sprawdzi czy jaka warto jest w licie uywamy sowa kluczowego in, ktry zwraca True, jeli warto zostanie znaleziona lub False jeli nie. Przed wersj 2.2.1 Python nie posiada oddzielnego boolowskiego typu danych. Aby to zrekompensowa, Python mg interpretowa wikszo typw danych jako bool (czyli warto logiczn np. w instrukcjach if) wedug poniszego schematu: 0 jest faszem; wszystkie inne liczby s prawd. Pusty acuch znakw ("") jest faszem, wszystkie inne s prawd. Pusta lista ([]) jest faszem; wszystkie inne s prawd. Pusta krotka (()) jest faszem; wszystkie inne s prawd. Pusty sownik ({}) jest faszem; wszystkie inne s prawd.

Wszystkie powysze punkty stosowane s w Pythonie 2.2.1 i nowszych, ale obecnie mona take uywa typu logicznego bool, ktry moe przyjmowa warto True (prawda) lub False (fasz). Zwrmy uwag, e wartoci te, tak jak caa skadnia jzyka Python, s wraliwe na wielko liter. Usuwanie elementw z listy Przykad 3.16 Usuwanie elementw z listy >>> li [a, b, nowy, mpilgrim, z, przykad, nowy, dwa, elementy] >>> li.remove("z") >>> li [a, b, nowy, mpilgrim, przykad, nowy, dwa, elementy] >>> li.remove("nowy") >>> li [a, b, mpilgrim, przykad, nowy, dwa, elementy] >>> li.remove("c") Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: list.remove(x): x not in list >>> li.pop() elementy >>> li [a, b, mpilgrim, przykad, nowy, dwa]

#(1)

#(2)

#(3)

#(4)

1. remove usuwa pierwsz wystpujc warto w licie. 2. remove usuwa tylko pierwsz wystpujc warto. W tym przypadku nowy wystpuje dwa razy, ale li.remove("nowy") usuwa tylko pierwsze wystpienie. 3. Jeli warto nie zostanie znaleziona w licie, Python wygeneruje wyjtek. Naladuje on w takich sytuacjach postpowanie metody index.

40

ROZDZIA 4. WBUDOWANE TYPY DANYCH 4. pop jest ciekaw metod, ktra wykonuje dwie rzeczy: usuwa ostatni element z listy i zwraca jego warto. Metoda ta rni si od li[-1] tym, e li[-1] zwraca jedynie warto, ale nie zmienia listy, a od li.remove(value) tym, e li.remove(value) zmienia list, ale nie zwraca wartoci.

Uywanie operatorw na listach Przykad 3.17 Operatory na listach >>> li = [a, b, mpilgrim] >>> li = li + [przykad, nowy] #(1) >>> li [a, b, mpilgrim, przykad, nowy] >>> li += [dwa] #(2) >>> li [a, b, mpilgrim, przykad, nowy, dwa] >>> li = [1, 2] * 3 #(3) >>> li [1, 2, 1, 2, 1, 2] 1. Aby poczy dwie listy, mona te skorzysta z operatora +. Za pomoc lista = lista + innalista uzyskujemy ten sam wynik, co za pomoc list.extend(innalista), ale operator + zwraca now list, podczas gdy extend zmienia tylko istniejc list. Oglnie extend jest szybszy, szczeglnie na duych listach. 2. Python posiada take operator +=. Operacja li += [dwa] jest rwnowana li.extend([dwa]). Operator += dziaa zarwno na listach, acuchach znakw jak i moe by nadpisany dla dowolnego innego typu danych. 3. Operator * zwielokrotnia podan list. li = [1, 2] * 3 jest rwnowane z li = [1, 2] + [1, 2] + [1, 2], ktre czy trzy listy w jedn. Materiay dodatkowe How to Think Like a Computer Scientist uczy podstaw zwizanych z wykorzystywaniem list, a take nawizuje do przekazywania listy jako argument funkcji. Python Tutorial pokazuje, e listy mona wykorzystywa jako stos lub kolejk. Python Knowledge Base odpowiada na czsto zadawane pytania dotyczce list, a take posiada take wiele przykadw kodw rdowych wykorzystujcych listy. Python Library Reference opisuje wszystkie metody, ktre zawiera lista.

4.4. KROTKI

41

4.4

Krotki

Krotki
Krotka (ang. tuple) jest niezmienn list. Zawarto krotki okrelamy tylko podczas jej tworzenia. Potem nie moemy ju jej zmieni. Przykad 3.18 Deniowanie krotki >>> t = ("a", "b", "mpilgrim", "z", "element") >>> t (a, b, mpilgrim, z, element) >>> t[0] a >>> t[-1] element >>> t[1:3] (b, mpilgrim) #(1)

#(2) #(3) #(4)

1. Krotki deniujemy w identyczny sposb jak list, lecz z jednym wyjtkiem zbir elementw jest ograniczony w nawiasach okrgych, zamiast w kwadratowych. 2. Podobnie jak w listach, elementy w krotce maj okrelony porzdek. S one indeksowane od 0, wic pierwszym elementem w niepustej krotce jest zawsze t[0]. 3. Ujemne indeksy id od koca krotki, tak samo jak w listach. 4. Krotki take mona wycina. Kiedy wycinamy list, dostajemy now list. Podobnie, gdy wycinamy krotk dostajemy now krotk. Przykad 3.19 Krotki nie posiadaj adnej metody >>> t (a, b, mpilgrim, z, element) >>> t.append("nowy") Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: tuple object has no attribute append >>> t.remove("z") Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: tuple object has no attribute remove >>> t.index("przyklad") Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: tuple object has no attribute index >>> "z" in t True

#(1)

#(2)

#(3)

#(4)

1. Nie mona dodawa elementw do krotki. Krotki nie posiadaj metod typu append, czy te extend.

42

ROZDZIA 4. WBUDOWANE TYPY DANYCH 2. Nie mona usuwa elementw z krotki. Nie posiadaj one ani metody remove ani metody pop. 3. Nie mona wyszukiwa elementw w krotce. Nie maj one metody index. 4. Mona jednak wykorzysta operator in, aby sprawdzi, czy krotka zawiera dany element. To w kocu do czego s te krotki przydatne? Krotki dziaaj szybciej ni listy. Jeli deniujemy stay zbir wartoci, ktry bdzie uywany tylko do iteracji, skorzystajmy z krotek zamiast z listy. Jeli chcielibymy uywa danych zabezpieczonych przed zapisem (np. po to, eby program by bezpieczniejszy), wykorzystajmy do tego krotki. Korzystajc z krotek, zamiast z list, mamy pewno, e dane w nich zawarte nie zostan nigdzie zmienione. To troch tak, jakbymy mieli gdzie ukryt instrukcj assert sprawdzajc, czy dane s stae. W przypadku, gdy nastpi prba nadpisania pewnych wartoci w krotce, program zwrci wyjtek. Pamitasz, jak powiedzielimy, e klucze w sowniku mog by acuchami znakw, liczbami cakowitymi i kilkoma innymi typami? Krotki s jednym z tych innych typw. W przeciwiestwie do list, mog one zosta uyte jako klucz w sowniku. Dlaczego? Jest to dosy skomplikowane. Klucze w sowniku musz by niezmienne. Krotki same w sobie s niezmienne, jednak jeli wewntrz krotki znajdzie si lista, to krotka ta nie bdzie moga zosta uyta jako klucz, poniewa lista jest zmienna. W takim przypadku krotka nie byaby sownikowo-bezpieczna. Aby krotka moga zosta wykorzystana jako klucz, musi ona zawiera wycznie acuchy znakw, liczby i inne sownikowo-bezpieczne krotki. Krotki uywane s do formatowania tekstu, co zobaczymy wkrtce.

Krotki mog zosta w atwy sposb przekonwertowane na listy. Wbudowana funkcja tuple, ktrej argumentem jest lista, zwraca krotk z takimi samymi elementami, natomiast funkcja list, ktrej argumentem jest krotka, zwraca list. W rezultacie tuple zamraa list, a list odmraa krotk.

Materiay dodatkowe How to Think Like a Computer Scientist uczy, czym s krotki i pokazuje, jak czy je ze sob. Python Knowledge Base pokazuje, jak posortowa krotk. Python Tutorial omawia, jak zdefniowa krotk z jednym elementem.

4.5. DEKLAROWANIE ZMIENNYCH

43

4.5

Deklarowanie zmiennych

Deklarowanie zmiennych
Wiemy ju troch o sownikach, krotkach i o listach, wic wrcimy do przykadowego kodu przedstawionego w rozdziale drugim, do odbchelper.py. Podobnie jak wikszo jzykw programowania Python posiada zarwno zmienne lokalne jak i globalne, cho nie deklarujemy ich w jaki wyrany sposb. Zmienne zostaj utworzone, gdy przypisujemy im pewne wartoci. Natomiast kiedy zmienna wychodzi poza zasig, zostaje automatycznie usunita. Przykad 3.20 Deniowanie zmiennej myParams if __name__ == "__main__": myParams = {"server":"mpilgrim", \ "database":"master", \ "uid":"sa", \ "pwd":"secret" \ } Zwrmy uwag na wcicia. Instrukcje warunkowe jako bloki kodu s identykowane za pomoc wci, podobnie jak funkcje. Zauwamy te, e dziki wykorzystaniu backslasha (\) moglimy przypisanie wartoci do zmiennej podzieli na kilka linii. Backslashe w Pythonie s specjalnymi znakami, ktre umoliwiaj kontynuacj danej instrukcji w nastpnej linii. Kiedy polecenie zostanie podzielone na kilka linii za pomoc znaku kontynuacji (\), nastpna linia moe zosta wcita w dowolny sposb. Python nie wemie tego wcicia pod uwag. cile mwic, wyraenia w nawiasach okrgych, kwadratowych i klamrowych (jak deniowanie sownikw) mona podzieli na wiele linii bez uywania znaku kontynuacji (\). Niektrzy zalecaj dodawa backslashe nawet wtedy, gdy nie jest to konieczne. Argumentuj to tym, e kod staje si wtedy czytelniejszy. Jest to jednak wycznie kwestia gustu. Wczeniej nigdy nie deklarowalimy adnej zmiennej o nazwie myParams, ale wanie przypisalimy do niej warto. Zachowanie to przypomina troch VBScript bez instrukcji option explicit. Na szczcie, w przeciwiestwie do VBScript, Python nie pozwala odwoywa si do zmiennych, do ktrych nie zostay wczeniej przypisane adne wartoci. Jeli sprbujemy to zrobi, Python rzuci wyjtek. Odwoywanie si do zmiennych Przykad 3.21 Odwoywanie si do niezdeniowanej zmiennej >>> x Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name x is not defined >>> x = 1

44 >>> x 1

ROZDZIA 4. WBUDOWANE TYPY DANYCH

Kiedy bdziesz za to dzikowa... Wielozmienne przypisania Jednym z lepszych Pythonowych skrtw jest wielozmienne przypisanie (ang. multi-variable assignment), czyli jednoczesne (za pomoc jednego wyraenia) przypisywanie kilku wartoci do kilku zmiennych. Przykad 3.22 Przypisywanie wielu wartoci za pomoc jednego wyraenia >>> >>> >>> a >>> b >>> e v = (a, b, e) (x, y, z) = v x y z

#(1)

1. v jest krotk trzech elementw, a (x, y, z) jest krotk trzech zmiennych. Przypisujc jedn krotk do drugiej, przypisalimy kad z wartoci v do odpowiednich zmiennych (w odpowiedniej kolejnoci). Moe to zosta wykorzystane w wielu sytuacjach. Czasami chcemy przypisa pewnym zmiennym pewien zakres wartoci np. od 1 do 10. W jzyku C moemy utworzy typy wyliczeniowe (enum) poprzez rczne utworzenie listy staych i wartoci jakie przechowuj. Moe to by troch nudn i czasochonn robot, w szczeglnoci gdy wartoci s kolejnymi liczbami. W Pythonie moemy wykorzysta wbudowan funkcj range i wielozmienne przypisanie. W ten sposb z atwoci przypiszemy kolejne wartoci do wielu zmiennych. Przykad 3.23 Przypisywanie kolejnych wartoci >>> [0, >>> >>> 0 >>> 1 >>> 6 range(7) #(1) 1, 2, 3, 4, 5, 6] (PONIEDZIALEK, WTOREK, SRODA, CZWARTEK, PIATEK, SOBOTA, NIEDZIELA) = range(7) #(2) PONIEDZIALEK #(3) WTOREK NIEDZIELA

1. Wbudowana funkcja range zwraca list liczb cakowitych. W najprostszej formie funkcja ta bierze grn granic i zwraca list liczb od 0 do podanej granicy (ale ju bez niej). (Moemy take ustawi pocztkow warto rn ni 0, a krok moe by inny ni 1. Aby otrzyma wicej szczegw wykorzystaj instrukcj print range. doc .)

4.5. DEKLAROWANIE ZMIENNYCH

45

2. PONIEDZIALEK, WTOREK, SRODA, CZWARTEK, PIATEK, SOBOTA i NIEDZIELA s zmiennymi, ktre zdeniowalimy. (Ten przykad pochodzi z moduu calendar; nazwy zostay spolszczone. Modu calendar umoliwia wywietlanie kalendarzy, podobnie jak to robi Uniksowy program cal. Modu calendar przechowuje dla odpowiednich dni tygodnia odpowiednie stae.) 3. Teraz kada zmienna ma wasn warto: PONIEDZIALEK ma 0, WTOREK ma 1 itd. Wielozmienne przypisania moemy wykorzysta przy tworzeniu funkcji zwracajcych wiele wartoci w postaci krotki. Zwrcon warto takiej funkcji moemy potraktowa jako normaln krotk lub te przypisa wszystkie elementy tej krotki do osobnych zmiennych za pomoc wielozmiennego przypisania. Wiele standardowych bibliotek korzysta z tej moliwoci np. modu os, ktry omwimy w rozdziale 6. Materiay dodatkowe Python Reference Manual pokazuje przykady, kiedy mona pomin znak kontynuacji linii, a kiedy musisz go wykorzysta. How to Think Like a Computer Scientist wyjania, jak za pomoc wielozmiennych przypisa zamieni wartoci dwch zmiennych.

46

ROZDZIA 4. WBUDOWANE TYPY DANYCH

4.6

Formatowanie acucha znakw

Formatowanie acucha znakw


W Pythonie moemy formatowa wartoci wewntrz acucha znakw. Chocia moemy tworzy bardzo skomplikowane wyraenia, jednak najczciej w prostych przypadkach wystarczy wykorzysta pole %s, aby wstawi pewien acuch znakw wewntrz innego. Formatowanie acucha znakw w Pythonie uywa tej samej skadni, co funkcja sprintf w jzyku C.

Przykad 3.24 Pierwsza prba formatowania acucha >>> k = "uid" >>> v = "sa" >>> "%s=%s" % (k, v) uid=sa

#(1)

1. Rezultatem wykonania tego wyraenia jest acuch znakw. Pierwsze %s zostao zastpione wartoci znajdujc si w k, a drugie wystpienie %s zostao zastpione wartoci v. Wszystkie inne znaki (w tym przypadku znak rwnoci) pozostay w miejscach, w ktrych byy. Zauwamy, e (k, v) jest krotk. Niedugo zobaczymy, do czego to moe by przydatne. Mogoby si wydawa, e formatowanie jest jedn z wielu moliwoci poczenia acuchw znakw, jednak formatowanie acucha znakw nie jest tym samym co czenie acuchw. Przykad 3.25 Formatowanie tekstu a czenie >>> uid = "sa" >>> pwd = "secret" >>> print pwd + " nie jest poprawnym hasem dla " + uid secret nie jest poprawnym hasem dla sa >>> print "%s nie jest poprawnym hasem dla %s" % (pwd, uid) secret nie jest poprawnym hasem dla sa >>> userCount = 6 >>> print "Uytkownikw: %d" % (userCount, ) Uytkownikw: 6 >>> print "Uytkownikw: " + userCount Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: cannot concatenate str and int objects 1. + jest operatorem czcym acuchy znakw. 2. W tym trywialnym przypadku formatowanie daje ten sam wynik co czenie.

#(1) #(2)

#(3) (4) #(5)

4.6. FORMATOWANIE ACUCHA ZNAKW

47

3. (userCount, ) jest jednoelementow krotk. Skadnia ta wyglda troch dziwnie, jednak takie oznaczenie jest jednoznaczne i wiadomo, e chodzi o krotk. Zawsze mona docza przecinek po ostatnim elemencie listy, krotki lub sownika. Jest on jednak wymagany tylko podczas tworzenia jednoelementowej krotki. Jeli przecinek nie byby wymagany, Python nie mgby stwierdzi czy (userCount) ma by krotk, czy te tylko wartoci zmiennej userCount. 4. Formatowanie acucha znakw dziaa z liczbami cakowitymi. W tym celu uywamy %d zamiast %s. 5. Sprbowalimy poczy acuch znakw z czym, co nie jest acuchem znakw. Zosta rzucony wyjtek. W przeciwiestwie do formatowania, czenie acucha znakw moemy wykonywa jedynie na innych acuchach. Tak jak sprintf w C, formatowanie tekstu w Pythonie przypomina szwajcarski scyzoryk. Mamy mnstwo opcji do wyboru, a take wiele pl dla rnych typw wartoci. Przykad 3.26 Formatowanie liczb >>> print "Dzisiejsza cena akcji: %f" % 50.4625 #(1) Dzisiejsza cena akcji: 50.462500 >>> print "Dzisiejsza cena akcji: %.2f" % 50.4625 #(2) Dzisiejsza cena akcji: 50.46 >>> print "Zmiana w stosunku do dnia wczorajszego: %+.2f" % 1.5 Zmiana w stosunku do dnia wczorajszego: +1.50

#(3)

1. Pole formatowania %f traktuje warto jako liczb rzeczywist i pokazuje j z dokadnoci do 6 miejsc po przecinku. 2. Modykator .2 pola %f umoliwia pokazywanie wartoci rzeczywistej z dokadnoci do dwch miejsc po przecinku. 3. Mona nawet czy modykatory. Dodanie modykatora + pokazuje plus albo minus przed wartoci, w zalenoci od tego jaki znak ma liczba. Modykator .2 zosta na swoim miejscu i nadal nakazuje wywietlanie liczby z dokadnoci dwch miejsc po przecinku. Materiay dodatkowe Python Library Reference wymienia wszystkie opcje formatowania. Eective AWK Programming omawia wszystkie opcje formatowania, a take mwi o innych zaawansowanych technikach.

48

ROZDZIA 4. WBUDOWANE TYPY DANYCH

4.7

Odwzorowywanie listy

Odwzorowywanie list
Jedn z bardzo uytecznych cech Pythona s wyraenia listowe (ang. list comprehension), ktre pozwalaj nam w zwizy sposb odwzorowa pewn list na inn, wykonujc na kadym jej elemencie pewn funkcj. Przykad 3.27 Wprowadzenie do wyrae listowych >>> >>> [2, >>> [1, >>> >>> [2, li = [1, 9, 8, 4] [element*2 for element in li] 18, 16, 8] li 9, 8, 4] li = [elem*2 for elem in li] li 18, 16, 8] #(1) #(2) #(3)

1. Aby zrozumie o co w tym chodzi, spjrzmy na to od strony prawej do lewej. li jest list, ktr odwzorowujemy. Python przechodzi po kadym elemencie li, tymczasowo przypisujc warto kadego elementu do zmiennej element, a nastpnie wyznacza warto funkcji element*2 i wstawia wynik do nowej, zwracanej listy. 2. Wyraenia listowe nie zmieniaj oryginalnej listy. 3. Zwracany wynik moemy przypisa do zmiennej, ktr odwzorowujemy. Nie spowoduje to adnych problemw. Python tworzy now list w pamici, a kiedy operacja odwzorowywania listy dobiegnie koca, do zmiennej zostanie przypisana lista znajdujca si w pamici. W funkcji buildConnectionString zadeklarowanej w rozdziale 2 skorzystalimy z wyrae listowych: ["%s=%s" % (k, v) for k, v in params.items()] Zauwamy, e najpierw wykonujemy funkcj items, znajdujc si w sowniku params. Funkcja ta zwraca wszystkie dane znajdujce si w sowniku w postaci listy krotek. Przykad 3.28 Funkcje keys, values i items >>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"} >>> params.keys() #(1) [pwd, database, uid, server] >>> params.values() #(2) [secret, master, sa, mpilgrim] >>> params.items() #(3) [(pwd, secret), (database, master), (uid, sa), (server, mpilgrim)]

4.7. ODWZOROWYWANIE LISTY

49

1. W sowniku metoda keys zwraca list wszystkich kluczy. Lista ta nie jest uporzdkowana zgodnie z kolejnoci, z jak deniowalimy sownik (pamitamy, e elementy w sowniku s nieuporzdkowane). 2. Metoda values zwraca list wszystkich wartoci. Lista ta jest zgodna z porzdkiem listy zwracanej przez metod keys, czyli dla wszystkich wartoci x zachodzi params.values()[x] == params[params.keys()[x]]. 3. Metoda items zwraca list krotek w formie (klucz, warto). Lista zawiera wszystkie dane ze sownika. Spjrzmy jeszcze raz na funkcj buildConnectionString. Przyjmuje ona list params.items(), odwzorowuje j na now list, korzystajc dla kadego elementu z formatowania acucha znakw. Nowa lista ma tyle samo elementw co params.items(), lecz kady element nowej listy jest acuchem znakw, ktry zawiera zarwno klucz, jak i skojarzon z nim warto ze sownika params. Przykad 3.29 Wyraenia listowe w buildConnectionString >>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"} >>> params.items() [(server, mpilgrim), (uid, sa), (database, master), (pwd, secret)] >>> [k for k, v in params.items()] [server, uid, database, pwd] >>> [v for k, v in params.items()] [mpilgrim, sa, master, secret] >>> ["%s=%s" % (k, v) for k, v in params.items()] [server=mpilgrim, uid=sa, database=master, pwd=secret] 1. Wykonujc iteracje po licie params.items() uywamy dwch zmiennych. Zauwamy, e w ten sposb korzystamy w ptli z wielozmiennego przypisania. Pierwszym elementem params.items() jest (server, mpilgrim), dlatego te podczas pierwszej iteracji odwzorowywania listy zmienna k bdzie przechowywaa warto server, a v warto mpilgrim. W tym przypadku ignorujemy warto v, doczajc tylko warto znajdujc si w k do zwracanej listy. Otrzymamy taki sam wynik, gdy wywoamy params.keys(). 2. W tym miejscu wykonujemy to samo, ale zamiast zmiennej v ignorujemy zmienn k. Otrzymamy taki sam wynik, jakbymy wywoali params.values(). 3. Dziki temu, e przerobilimy obydwa poprzednie przykady i skorzystalimy z formatowania acucha znakw, otrzymalimy list acuchw znakw. Kady acuch znakw tej listy zawiera zarwno klucz, jak i warto pewnego elementu ze sownika. Wynik wyglda podobnie do wyjcia pierwszego programu. Ponadto porzdek zosta taki sam, jaki by w sowniku. Materiay dodatkowe Python Tutorial omawia inny sposb odwzorowywania listy za pomoc wbudowanej funkcji map. Python Tutorial pokazuje kilka przykadw, jak mona korzysta z wyrae listowych.

#(1) #(2) #(3)

50

ROZDZIA 4. WBUDOWANE TYPY DANYCH

4.8

czenie list i dzielenie acuchw znakw

czenie listy i dzielenie acuchw znakw


Mamy list, ktrej elementy s w formie klucz=warto. Zamy, e chcielibymy poczy je wszystkie w pojedynczy acuch. Aby to zrobi, wykorzystamy metod join obiektu typu string. Poniej zosta przedstawiony przykad czenia listy w acuch znakw, ktry wykorzystalimy w funkcji buildConnectionString: return ";".join(["%s=%s" % (k, v) for k, v in params.items()]) Zanim przejdziemy dalej zastanwmy si nad pewn kwesti. Funkcje s obiektami, acuchy znakw s obiektami... wszystko jest obiektem. Mona by byo doj do wniosku, e take zmienna jest obiektem, ale to akurat nie jest prawd. Spjrzmy na ten przykad i zauwamy, e acuch znakw ; sam w sobie jest obiektem i z niego mona wywoa metod join. Zmienne s etykietami dla obiektw. Metoda join czy elementy listy w jeden acuch znakw, a kady element w zwracanym acuchu jest oddzielony od innego separatorem. W naszym przykadzie jest nim ;, lecz moe nim by dowolny acuch znakw. Metoda join dziaa tylko z listami przechowujcymi acuchy znakw. Nie korzysta ona z adnych wymusze czy konwersji. czenie listy, ktra posiada co najmniej jeden lub wicej elementw niebdcych acuchem znakw, rzuci wyjtek.

Przykad 3.30 Wyjcie odbchelper.py >>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"} >>> ["%s=%s" % (k, v) for k, v in params.items()] [pwd=secret, database=master, uid=sa, server=mpilgrim] >>> ";".join(["%s=%s" % (k, v) for k, v in params.items()]) pwd=secret;database=master;uid=sa;server=mpilgrim Powyszy acuch znakw otrzymalimy podczas uruchamiania odbchelper.py. W Pythonie znajdziemy take metod analogiczn do metody join, ale ktra zamiast czy, dzieli acuch znakw na list. Jest to funkcja split. Przykad 3.31 Dzielenie acuchw znakw >>> li = [pwd=secret, database=master, uid=sa, server=mpilgrim] >>> s = ";".join(li) >>> s pwd=secret;database=master;uid=sa;server=mpilgrim >>> s.split(";") #(1) [pwd=secret, database=master, uid=sa, server=mpilgrim] >>> s.split(";", 1) #(2) [pwd=secret, database=master;uid=sa;server=mpilgrim] 1. split, przeciwiestwo funkcji join, dzieli acuch znakw na wieloelementow list. Separator (w przykadzie ;) nie bdzie wystpowa w adnym elemencie zwracanej listy, zostanie pominity.

4.8. CZENIE LIST I DZIELENIE ACUCHW ZNAKW

51

2. Do funkcji split moemy doda opcjonalny drugi argument, ktry okrela, na jak maksymaln liczb kawakw ma zosta podzielony acuch. (O opcjonalnych argumentach w funkcji dowiemy si w nastpnym rozdziale.) tekst.split(separator, 1) jest przydatnym sposobem dzielenia acucha na dwa fragmenty. Pierwszy fragment dotyczy acucha znajdujcego si przed pierwszym wystpieniem separatora (jest on pierwszym elementem zwracanej listy), a drugi fragment zawiera dalszy fragment acucha znajdujcego si za pierwszym znalezionym separatorem (jest drugim elementem listy). Dalsze separatory nie s brane pod uwag.

Materiay dodatkowe Python Knowledge Base odpowiada na czsto zadawane pytania dotyczce acuchw znakw, a take posiada wiele przykadw wykorzystywania acuchw znakw. Python Library Reference wymienia wszystkie metody acuchw znakw. The Whole Python FAQ wyjania, dlaczego join jest metod acucha znakw zamiast listy.

52

ROZDZIA 4. WBUDOWANE TYPY DANYCH

4.9

Kodowanie znakw

Kodowanie znakw
W komputerze pewnym znakom odpowiadaj pewne liczby, a kodowanie znakw okrela, ktra liczba odpowiada jakiej literze. W acuchu znakw kady symbol zajmuje 8 bitw, co daje nam do dyspozycji tylko 256 rnych symboli. Podstawowym systemem kodowania jest ASCII. Przyporzdkowuje on liczbom z zakresu od 0 do 127 znaki alfabetu angielskiego, cyfry i niektre inne symbole. Pozostae standardowe systemy kodowania rozszerzaj standard ASCII, dlatego znaki z przedziau od 0 do 127 w kadym systemie kodowania s takie same. Przykad 3.32 Znaki jako liczby i na odwrt >>> ord(a) #(1) 97 >>> chr(99) #(2) c >>> ord(%) 37 #(3) >>> chr(115) s >>> chr(261) Traceback (most recent call last): #(4) File "<stdin>", line 1, in ? ValueError: chr() arg not in range(261) >>> chr(188) \xbc #(5)

1. Funkcji ord zwraca liczb, ktra odpowiada danemu symbolowi. W tym przypadku literze a odpowiada liczba 97. 2. Za pomoc funkcji chr dowiadujemy si, jaki znak odpowiada danej liczbie. Liczbie 99 odpowiada znak c. 3. Procent (%) odpowiada liczbie 37. 4. Kady symbol odpowiada liczbie z zakresu od 0 do 255. Liczba 261 nie mieci si w jednym bajcie, dlatego wyskoczy nam wyjtek. 5. Co prawda liczba 188 mieci si w 8-bitach, ale nie mieci si w standardzie ASCII i dlatego tego symbolu Python nie moe jednoznacznie zinterpretowa. W systemie kodowania ISO 8859-2 liczba ta odpowiada znakowi , ale w systemie kodowania Windows-1250 (znany te jako CP-1250) znakowi L. Kady edytor tekstu zapisuje tworzone przez nas programy korzystajc z jakiego kodowania, choby z samego ASCII. Dobrze jest korzysta z edytora, ktry daje nam moliwo ustawienia kodowania znakw. Kiedy wiemy, w jakim systemie kodowania zosta zapisany nasz skrypt, powinnimy jeszcze o tym poinformowa Pythona.

4.9. KODOWANIE ZNAKW Informowanie Pythona o kodowaniu znakw Wrmy do odbchelper.py. Na samym pocztku dodalimy lini 2 : #-*- coding: utf-8 -*-

53

W ten sposb ustawiamy kodowanie znakw danego pliku, a nie caego programu (program moe si skada z wielu plikw). Zreszt, jeli nie zdeniujemy kodowania znakw, Python nas o tym uprzedzi: sys:1: DeprecationWarning: Non-ASCII character \textbackslash{}xc5 in file test.py on line 5 but no encoding declared; see http://www.python.org/peps/pep-0263.html for detils Jeli skorzystalimy z innego kodowania znakw, zamiast utf-8 oczywicie napiszemy co innego. Dodajc polskie znaki z reguy korzysta si z kodowania UTF-8 lub ISO-8859-2, a czasami w przypadku Windowsa z Windows-1250. Ale co wtedy, gdy nie mamy moliwoci ustawi kodowania znakw i nie wiemy z jakiego korzysta nasz edytor? Mona to sprawdzi metod prb i bdw: #-*- coding: {tu wstawiamy utf-8, iso-8859-2 lub windows-1250} -*print "za gl ja" A moe pora zmieni edytor? Unikod jeszcze raz Jak wiemy, unikod jest systemem reprezentowania znakw ze wszystkich rnych jzykw wiata. Zaraz powrcimy do Pythona. Notatka historyczna. Przed powstaniem unikodu istniay oddzielne systemy kodowania znakw dla kadego jzyka, a co przed chwil troch omwilimy. Kady z nich korzysta z tych samych liczb (0-255) do reprezentowania znakw danego jzyka. Niektre jzyki (jak rosyjski) miay wiele sprzecznych standardw reprezentowania tych samych znakw. Inne jzyki (np. japoski) posiadaj tak wiele znakw, e wymagaj wielu bajtw, aby zapisa cay zbir jego znakw. Wymiana dokumentw pomidzy tymi systemami bya trudna, poniewa komputer nie mg stwierdzi, z ktrego systemu kodowania skorzysta autor. Komputer widzia tylko liczby, a liczby mog oznacza rne rzeczy. Zaczto si zastanawia nad przechowywaniem tych dokumentw w tym samym miejscu (np. w tej samej tabeli bazy danych); trzeba byo przechowywa informacje o kodowaniu kadego kawaku tekstu, a take trzeba byo za kadym razem informowa o kodowaniu przekazywanego tekstu. Wtedy te zaczto myle o wielojzycznych dokumentach, w ktrych znaki z wielu jzykw znajduj si w tym samym dokumencie. (Wykorzystyway one zazwyczaj kod ucieczki, aby przeczy tryb kodowania; ciach, jeste w rosyjskim trybie, wic 241 znaczy to; ciach, jeste w greckim trybie, wic 241 znaczy co innego itd.) Unikod zosta zaprojektowany po to, aby rozwizywa tego typu problemy. Aby rozwiza te problemy, unikod reprezentuje wszystkie znaki jako 2-bajtowe liczby, od 0 do 65535 3 Kada 2-bajtowa liczba reprezentuje unikalny znak, ktry jest
tym podrczniku bdziemy korzysta z kodowania UTF-8 cigle jest to nadmiernym uproszczeniem. Unikod zosta rozszerzony, aby obsugiwa teksty w staroytnych odmianach chiskiego, koreaskiego, czy japoskiego, ale one maj tak duo znakw, e 2-bajtowy system nie moe reprezentowa ich wszystkich.
3 Niestety 2W

54

ROZDZIA 4. WBUDOWANE TYPY DANYCH

wykorzystywany w co najmniej jednym jzyku wiata. (Znaki ktre s wykorzystywane w wielu jzykach wiata, maj ten sam kod numeryczny.) Mamy dokadnie jedn liczb na znak i dokadnie jeden znak na liczb. Dane unikodu nigdy nie s dwuznaczne. 7-bitowy ASCII przechowuje wszystkie angielskie znaki za pomoc liczb z zakresu od 0 do 127. (65 jest wielk liter A, 97 jest ma liter a itd.) Jzyk angielski posiada bardzo prosty alfabet, wic moe si cakowicie zmieci w 7-bitowym ASCII. Jzyki zachodniej Europy jak jzyk francuski, hiszpaski, czy te niemiecki, korzystaj z systemu kodowania nazwanego ISO-8859-1 (inne okrelenie to latin-1), ktre korzysta z 7-bitowych znakw ASCII dla liczb od 0 do 127, ale rozszerza zakres 128-255 dla znakw typu (241), czy (252). Take unikod wykorzystuje te same znaki n u jak 7-bitowy ASCII dla zakresu od 0 do 127, a take te same znaki jak ISO-8859-1 w zakresie od 128 do 255, a potem w zakresie od 256 do 65535 rozszerza znaki do innych jzykw. Kiedy korzystamy z danych w postaci unikodu, moe zaj potrzeba przekonwertowania danych na jaki inny system kodowania np. gdy potrzebujemy wspdziaa z innym komputerowym systemem, a ktry oczekuje danych w okrelonym 1-bajtowym systemie kodowania, czy te wysa dane na terminal, ktry nie obsuguje unikodu, czy te do drukarki. I po tej notatce, powrmy do Pythona. Przykad 3.33 Unikod w Pythonie >>> ord(u"") 261 >>> print unichr(378) #(1) #(2)

1. W unikodzie polski znak jednoznacznie odpowiada cyfrze 261. 2. Za pomoc funkcji unichr, dowiadujemy si jakiemu znakowi odpowiada dana liczba. Liczbie 378 odpowiada polska litera . Python automatycznie zakoduje wypisywany napis unikodowy, aby zosta poprawnie wywietlony na naszym systemie. Dlaczego warto korzysta z unikodu? Jest kilka powodw: Unikod bardzo dobrze sobie radzi z rnymi midzynarodowymi znakami. Reprezentacja unikodowa jest jednoznaczna; jednej liczbie odpowiada dokadnie jeden znak. Nie musimy si zamartwia szczegami technicznymi np. czy dwa acuchy, ktre ze sob czymy s w takim samym systemie kodowania 4 . Python potra waciwie zinterpretowa wszystkie znaki (np. co jest liter, co jest biaym znakiem, a co jest cyfr). Korzystajc z unikodu zapobiegamy wielu problemom. Dlatego wszdzie, gdzie bdziemy korzystali z polski znakw, bdziemy korzystali z unikodu.
4 W szczeglnoci moe si to zrobi niewygodne, kiedy korzystamy tylko ze standardowych acuchw znakw, a wsppracujce ze sob moduy korzystaj z rnych systemw kodowania np. jedne z ISO 8859-2, a inne z UTF-8.

4.9. KODOWANIE ZNAKW Materiay dodatkowe

55

PEP 0263 wyjania, w jaki sposb skongurowa kodowanie kodu rdowego.

56

ROZDZIA 4. WBUDOWANE TYPY DANYCH

4.10

Praca z unikodem

Praca z unikodem
Unikodowymi napisami posugujemy si w identyczny sposb jak normalnymi acuchami znakw. Przykad 3.34 Posugiwanie si unikodem >>> errmsg = uNie mona otworzy pliku #(1) >>> print errmsg #(2) Nie mona otworzy pliku >>> print errmsg + u, brak dostpu. #(3) Nie mona otworzy pliku, brak dostpu. >>> errmsg.split() #(4) [uNie, umo\u017cna, uotworzy\u0107, upliku] >>> print u"Bd: %s"%errmsg Bd: Nie mona otworzy pliku

1. Tworzymy unikodowy napis i przypisujemy go do zmiennej errmsg. 2. Wypisujc dowolny unikod, Python go zakoduje w taki sposb, aby by zgodny z kodowaniem znakw wyjcia, a wic dany napis zostanie zawsze wypisany z polskimi znakami. 3. Z unikodem operujemy identycznie, jak z innymi acuchami znakw. Moemy na przykad dwa unikody ze sob czy. 4. Moemy take podzieli unikod na list. 5. Ponadto, podobnie jak w przypadku standardowego ancucha znakw, moemy te unikod formatowa. Unikod a acuchy znakw Funkcjonalno unikodu moemy czy ze standardowymi acuchami znakw, o ile z operacji tych jasno wynika, co chcemy osign. Przykad 3.35 Unikod w poczeniu z acuchami znakw >>> file = myfile.txt >>> errmsg + + file #(1) uNie mo\u017cna otworzy\u0107 pliku myfile.txt >>> "%s %s"%(errmsg, file) #(2) uNie mo\u017cna otworzy\u0107 pliku myfile.txt >>> errmsg + , brak dostpu. #(3) Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeDecodeError: ascii codec cant decode byte 0xc4 in position 11: ordinal not in range(128) >>> "Bd: %s"%errmsg #(4)

4.10. PRACA Z UNIKODEM

57

Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeDecodeError: ascii codec cant decode byte 0xc4 in position 11: ordinal not in range(128) 1. Unikod moemy poczy z acuchem znakw. Powstaje nowy napis unikodowy. 2. Moemy formatowa acuch znakw korzystajc z unikodowych wartoci. Tu take powstaje nowy unikod. 3. Python rzuci wyjtek; nie potra przeksztaci napisu , brak dostpu na napis unikodowy. Z unikodem moemy czy jedynie acuchy znakw w systemie ASCII (czyli zawierajce jedynie angielskie litery i kilka innych symboli np. przecinki, kropki itp.). W tym przypadku Python nie wie, z jakiego kodowania korzystamy. 4. Tutaj mamy analogiczn sytuacj. Python nie potra przeksztaci napisu Bd: %s na unikod i rzuca wyjtek. Python zakada, e kodowaniem wszystkich acuchw znakw jest ASCII 5 , dlatego jeli mieszamy tekst unikodowy z acuchami znakw, powinnimy dopilnowa, aby acuchy znakw zawieray znaki nalece do ASCII (czyli nie mog posiada polskich znakw). encode i decode A co wtedy, gdy chcemy przeksztaci unikodowy napis na acuch znakw? acuchy znakw s jako zakodowane, wic aby stworzy acuch znakw, musimy go na co zakodowa np. ISO 8859-2, czy te UTF-8. W tym celu korzystamy z metody encode unikodu. Przykad 3.36 Metoda encode >>> errmsg.encode(iso-8859-2) #(1) Nie mo\xbfna otworzy\xe6 pliku >>> errmsg.encode(utf-8) Nie mo\xc5\xbcna otworzy\xc4\x87 pliku #(2)

1. Za pomoc metody encode informujemy Pythona na jakie kodowanie znakw chcemy zakodowa pewien unikod. W tym przypadku otrzymujemy acuch znakw zakodowany w systemie ISO 8859-2. 2. Tutaj otrzymujemy ten sam napis, ale zakodowany w systemie UTF-8. Operacj odwrotn, czyli odkodowania, wykonujemy za pomoc funkcji decode. Na przykad:
5 Istnieje moliwo zmiany domylnego kodowania acuchw znakw na inny, ale nie powinno si tego robi. Zmiana domylnego kodowania wprowadza pewne komplikacje i sprawia, e programy staj si nieprzenone, dlatego nie bdziemy tego opisywa w tej ksice.

58 Przykad 3.37 Metoda decode

ROZDZIA 4. WBUDOWANE TYPY DANYCH

>>> msg = errmsg.encode(utf-8) >>> msg.decode(utf-8) uNie mo\u017cna otworzy\u0107 pliku >>> print msg.decode(utf-8) Nie mona otworzy pliku

#(1) #(2)

1. W tym miejscu zakodowalimy napis errmsg na UTF-8. 2. Za pomoc metody decode odkodowalimy zakodowany acuch znakw i zwrcony zosta unikod. acuch znakw przechowuje znaki w zakodowanej postaci np. w systemie UTF-8, ISO 8859-1, czy ISO 8859-4; moe by wieloznaczny. Natomiast unikod jest jednoznaczny, wic nie jest zakodowany w tego typu systemach kodowania. Poniewa acuch znakw jest zakodowany, musimy odkodowa (za pomoc decode), aby otrzyma niezakodowany unikod. Z kolei unikod, poniewa jest niezakodowany, musimy zakodowa (za pomoc encode), aby otrzyma zakodowany acuch znakw.

4.11. PODSUMOWANIE

59

4.11

Wbudowane typy danych - podsumowanie

Podsumowanie
Teraz ju powinnimy wiedzie w jaki sposb dziaa program odbchelper.py i zrozumnie, dlaczego otrzymalimy takie wyjcie. def buildConnectionString(params): u"""Tworzy acuchw znakw na podstawie sownika parametrw. Zwraca acuch znakw. """ return ";".join(["%s=%s" % (k, v) for k, v in params.items()]) if __name__ == "__main__": myParams = {"server":"mpilgrim", \ "database":"master", \ "uid":"sa", \ "pwd":"secret" } print buildConnectionString(myParams) Na wyjciu z programu otrzymujemy: pwd=secret;database=master;uid=sa;server=mpilgrim Zanim przejdziemy do nastpnego rozdziau, upewnijmy si czy potramy: uywa IDE Pythona w trybie interaktywnym napisa program i uruchamia go przy pomocy twojego IDE lub z linii polece tworzy acuchy znakw i napisy unikodowe importowa moduy i wywoywa funkcje w nich zawarte deklarowa funkcje, uywa napisw dokumentacyjnych (ang. docstring), zmiennych lokalnych, a take uywa odpowiednich wci deniowa sowniki, krotki i listy dostawa si do atrybutw i metod dowolnego obiektu, wczajc w to acuchy znakw, listy, sowniki, funkcje i moduy czy wartoci poprzez formatowanie acucha znakw odwzorowywa list na inn list dzieli acuch znakw na list i czy list w acuch znakw poinformowa Pythona, z jakiego kodowania znakw korzystamy wykorzystywa metody encode i decode, aby przeksztaci unikod w acuch znakw.

60

ROZDZIA 4. WBUDOWANE TYPY DANYCH

Rozdzia 5

Potga introspekcji

61

62

ROZDZIA 5. POTGA INTROSPEKCJI

5.1

Potga introspekcji

W tym rozdziale dowiemy si o jednej z mocnych stron Pythona introspekcji. Jak ju wiemy, wszystko w Pythonie jest obiektem, natomiast introspekcja jest kodem, ktry postrzega funkcje i moduy znajdujce si w pamici jako obiekty, a take pobiera o nich informacje i operuje nimi.

Nurkujemy
Zacznijmy od kompletnego, dziaajcego programu. Przegldajc kod na pewno rozumiesz ju niektre jego fragmenty. Przy niektrych liniach znajduj si liczby w komentarzach; korzystamy tu z koncepcji, ktre wykorzystywalimy ju w rozdziale drugim. Nie przejmuj si, jeeli nie rozumiesz czci tego programu. W rozdziale tym wszystkiego si jeszcze nauczysz. Przykad 4.1 apihelper.py def info(object, spacing=10, collapse=1): #(1) (2) (3) u"""Wypisuje metody i ich notki dokumentacyjne. Argumentem moe by modu, klasa, lista, sownik, czy te acuch znakw.""" methodList = [e for e in dir(object) if callable(getattr(object, e))] processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s) print "\textbackslash{}n".join(["%s %s" % (method.ljust(spacing), processFunc(unicode(getattr(object, method).__doc__))) for method in methodList]) if __name__ == "__main__": print info.__doc__ #(4) (5)

1. Ten modu ma jedn funkcj info. Zgodnie ze swoj deklaracj wymaga ona trzech argumentw: object, spacing oraz collapse. Dwa ostatnie parametry s opcjonalne, co za chwil zobaczymy. 2. Funkcja info posiada wieloliniow notk dokumentacyjn, ktra opisuje jej zastosowanie. Zauwa, e funkcja nie zwraca adnej wartoci. Ta funkcja bdzie wykorzystywana, aby wykona pewn czynno, a nie eby otrzyma pewn warto. 3. Kod wewntrz funkcji jest wcity. 4. Sztuczka z if name pozwala wykona programowi co uytecznego, kiedy zostanie uruchomiony samodzielnie. Jeli powyszy kod zaimportujemy jako modu do innego programu, kod pod t instrukcj nie zostanie wykonany. W tym wypadku program wypisuje po prostu notk dokumentacyjn funkcji info. 5. Instrukcja if wykorzystuje == (dwa znaki rwnoci), aby porwna dwie wartoci. W instrukcji if nie musimy korzysta z nawiasw okrgych.

5.1. NURKUJEMY

63

Funkcja info zostaa zaprojektowana tak, aby uatwi sobie prac w IDE Pythona. IDE bierze jaki obiekt, ktry posiada funkcje lub metody (jak na przykad modu zawierajcy funkcje lub list, ktra posiada metody) i wywietla funkcje i ich notki dokumentacyjne. Przykad 4.2 Proste zastosowanie apihelper.py >>> from apihelper import info >>> li = [] >>> info(li) [...ciach...] append L.append(object) -- append object to end count L.count(value) -> integer -- return number of occurrences of value extend L.extend(iterable) -- extend list by appending elements from the iterable index L.index(value, [start, [stop]]) -> integer -- return first index of value insert L.insert(index, object) -- insert object before index pop L.pop([index]) -> item -- remove and return item at index (default last) remove L.remove(value) -- remove first occurrence of value reverse L.reverse() -- reverse *IN PLACE* sort L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*; cmp(x, y) -> -1, 0, 1 Domylnie wynik jest formatowany tak, by by atwy do odczytania. Notki dokumentacyjne skadajce si z wielu linii zamieniane s na jednoliniowe, ale t opcj moemy zmieni ustawiajc warto 0 dla argumentu collapse. Jeeli nazwa funkcji jest dusza ni 10 znakw, moemy okreli inn warto dla argumentu spacing, by uatwi sobie czytanie. Przykad 4.3 Zaawansowane uycie apihelper.py >>> import odbchelper >>> info(odbchelper) buildConnectionString Tworzy acuchw znakw na podstawie sownika parametrw. Zwraca acuch znakw. >>> info(odbchelper, 30) buildConnectionString Tworzy acuchw znakw na podstawie sownika parametrw. Zwraca acuch znakw. >>> info(odbchelper, 30, 0) buildConnectionString Tworzy acuchw znakw na podstawie sownika parametrw. Zwraca acuch znakw.

64

ROZDZIA 5. POTGA INTROSPEKCJI

5.2

Argumenty opcjonalne i nazwane

Argumenty opcjonalne i nazwane


W Pythonie argumenty funkcji mog posiada wartoci domylne. Jeeli funkcja zostanie wywoana bez podania pewnego argumentu, argumentowi temu zostanie przypisana jego domylna warto. Co wicej moemy podawa argumenty w dowolnej kolejnoci poprzez uycie ich nazw. Poniej przykad funkcji info z dwoma argumentami opcjonalnymi: def info(object, spacing=10, collapse=1): spacing oraz collapse s argumentami opcjonalnymi, poniewa maj przypisane wartoci domylne. Argument object jest wymagany, poniewa nie posiada wartoci domylnej. Jeeli info zostanie wywoana tylko z jednym argumentem, spacing przyjmie wartoci 10, a collapse warto 1. Jeeli wywoamy t funkcj z dwoma argumentami, jedynie collapse przyjmuje warto domyln (czyli 1). Zamy, e chcielibymy okreli warto dla collapse, ale dla argumentu spacing chcielibymy skorzysta z domylnej wartoci. W wikszoci jzykw programowania jest to niewykonalne, poniewa wymagaj one od nas wywoania funkcji z trzema argumentami. Na szczcie w Pythonie moemy okrela argumenty w dowolnej kolejnoci poprzez odwoanie si do ich nazw. Przykad 4.4 Rne sposoby wywoania funkcji info info(odbchelper) info(odbchelper, 12) info(odbchelper, collapse=0) info(spacing=15, object=odbchelper) #(1) #(2) #(3) #(4)

1. Kiedy wywoamy t funkcj z jednym argumentem, spacing przyjmie warto domyln rwn 10, a collapse warto 1. 2. Kiedy podamy dwa argumenty, collapse przyjmie warto domyln, czyli 1. 3. Tutaj podajemy argument collapse odwoujc si do jego nazwy i okrelamy warto, ktr chcemy mu przypisa. spacing przyjmuje warto domyln 10. 4. Nawet wymagany argument (jak object, ktry nie posiada wartoci domylnej) moe zosta okrelony poprzez swoj nazw i moe wystpi na jakimkolwiek miejscu w wywoaniu funkcji. Takie dziaanie moe si wydawa troch niejasne, dopki nie zdamy sobie sprawy, e lista argumentw jest po prostu sownikiem. Gdy wywoujemy dan funkcj w sposb normalny, czyli bez podawania nazw argumentw, Python dopasowuje wartoci do okrelonych argumentw w takiej kolejnoci w jakiej zostay zadeklarowane. Najczciej bdziemy wykorzystywali tylko normalne wywoania funkcji, ale zawsze mamy moliwo bardziej elastycznego podejcia do okrelania kolejnoci argumentw. Jedyn rzecz, ktr musimy zrobi by poprawnie wywoa funkcj jest okrelenie w jakikolwiek sposb wartoci dla kadego wymaganego argumentu. Sposb i kolejno okrelania argumentw zaley tylko od nas.

5.2. ARGUMENTY OPCJONALNE I NAZWANE Materiay dodatkowe

65

Python Tutorial omawia w jaki sposb domylne wartoci s okrelane, czyli co si stanie, gdy domylny argument bdzie list lub te pewnym wyraeniem.

66

ROZDZIA 5. POTGA INTROSPEKCJI

5.3

Dwa sposoby importowania moduw

Dwa sposoby importowania moduw


W Pythonie mamy dwa sposoby importowania moduw. Obydwa s przydatne, dlatego te powinnimy umie je wykorzystywa. Jednym ze sposobw jest uycie polecenia import module, ktry moglimy zobaczy w podrozdziale Wszystko jest obiektem. Istnieje inny sposb, ktry realizuje t sam czynno, ale posiada pewne rnice. Poniej zosta przedstawiony przykad wykorzystujcy instrukcj from module import: from apihelper import info Jak widzimy, skadnia tego wyraenia jest bardzo podobna do import module, ale z jedn wan rnic: atrybuty i metody danego moduu s importowane bezporednio do lokalnej przestrzeni nazw, a wic bd dostpne bezporednio i nie musimy okrela, z ktrego moduu korzystamy. Moemy importowa okrelone pozycje albo skorzysta z from module import *, aby zaimportowa wszystko. from module import * w Pythonie przypomina use module w Perlu, a Pythonowe import module przypomina Perlowskie require module.

from module import * w Pythonie jest analogi do import module.* w Javie, a import module w Pythonie przypomina import module w Javie.

Przykad 4.5 Rnice midzy import module a from module import >>> import types >>> types.FunctionType #(1) <type function> >>> FunctionType #(2) Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name FunctionType is not defined >>> from types import FunctionType #(3) >>> FunctionType #(4) <type function> 1. Modu types nie posiada adnych metod. Posiada on jedynie atrybuty okrelajce wszystkie typy zdeniowane przez Pythona. Zauwamy, e atrybut tego moduu (w tym przypadku FunctionType) musi by poprzedzony nazw moduu types. 2. FunctionType nie zosta sam w sobie okrelony w przestrzeni nazw; istnieje on tylko w kontekcie moduu types. 3. Za pomoc tego wyraenia atrybut FunctionType z moduu types zosta zaimportowany bezporednio do lokalnej przestrzeni nazw.

5.3. DWA SPOSOBY IMPORTOWANIA MODUW

67

4. Teraz moemy odwoywa si bezporednio do FunctionType, bez odwoywania si do types. Kiedy powinnimy uywa from module import? Kiedy czsto odwoujemy si do atrybutw i metod, a nie chcemy wielokrotnie wpisywa nazwy moduu, wtedy najlepiej wykorzysta from module import. Jeli potrzebujemy selektywnie zaimportowa tylko kilka atrybutw lub metod, powinnimy skorzysta z from module import. Jeli modu zawiera atrybuty lub metody, ktre posiadaj tak sam nazw jaka jest w naszym module, powinnimy wykorzysta import module, aby unikn koniktu nazw. W pozostaych przypadkach to kwestia stylu programowania, mona spotka kod napisany obydwoma sposobami. Uywajmy from module import * oszczdnie, poniewa taki sposb importowania utrudnia okrelenie, skd pochodzi dana funkcja lub atrybut, a to z kolei utrudnia debugowanie.

Materiay dodatkowe e-bot opowie nam wicej na temat rnic midzy import module a from module import. Python Tutorial omawia zaawansowane techniki importu, wczajc w to from module import *.

68

ROZDZIA 5. POTGA INTROSPEKCJI

5.4

type, str, dir i inne wbudowane funkcje

type, str, dir i inne wbudowane funkcje


Python posiada may zbir bardzo uytecznych wbudowanych funkcji. Wszystkie inne funkcje znajduj si w rnych moduach. Bya to wiadoma decyzja projektowa, aby unikn przeadowania rdzenia jzyka, jak to ma miejsce w przypadku innych jzykw (jak np. Visual Basic czy Object Pascal). Funkcja type Funkcja type zwraca typ danych podanego obiektu. Wszystkie typy znajduj si w module types. Funkcja ta moe si okaza przydatna podczas tworzenia funkcji obsugujcych kilka typw danych. Przykad 4.6 Wprowadzenie do type >>> type(1) #(1) <type int> >>> li = [] >>> type(li) #(2) <type list> >>> import odbchelper >>> type(odbchelper) #(3) <type module> >>> import types #(4) >>> type(odbchelper) == types.ModuleType True 1. Argumentem type moe by cokolwiek: staa, acuch znakw, lista, sownik, krotka, funkcja, klasa, modu, wszystkie typy s akceptowane. 2. Kiedy podamy funkcji type dowoln zmienn, zostanie zwrcony jej typ. 3. type take dziaa na moduach. 4. Moemy uywa staych z moduu types, aby porwnywa typy obiektw. Wykorzystuje to funkcja info, co wkrtce zobaczymy. Funkcja str Funkcja str przeksztaca dane w acuch znakw. Kady typ danych moe zosta przeksztacony w acuch znakw. Przykad 4.7 Wprowadzenie do str >>> str(1) 1 >>> horsemen = [war, pestilence, famine] >>> horsemen [war, pestilence, famine] >>> horsemen.append(Powerbuilder) #(1)

5.4. TYPE, STR, DIR I INNE WBUDOWANE FUNKCJE >>> str(horsemen) #(2) "[war, pestilence, famine, Powerbuilder]" >>> str(odbchelper) #(3) "<module odbchelper from c:\\docbook\\dip\\py\\odbchelper.py>" >>> str(None) #(4) None

69

1. Mona byo si spodziewa, e str dziaa na tych prostych, podstawowych typach takich jak np. liczby cakowite. Prawie kady jzyk programowania posiada funkcj konwertujc liczb cakowit na acuch znakw. 2. Jakkolwiek funkcja str dziaa na kadym obiekcie dowolnego typu, w tym przypadku jest to lista skadajca si z kilku elementw. 3. Argumentem funkcji str moe by take modu. Zauwamy, e acuch reprezentujcy modu zawiera ciek do miejsca, w ktrym si ten modu znajduje. Na rnych komputerach moe by ona inna. 4. Subtelnym, lecz wanym zachowaniem funkcji str jest to, e argumentem moe by nawet warto None (Pythonowej wartoci pusta, czsto okrelanej w innych jzykach przez null). Dla takiego argumentu funkcja zwraca napis None. Wkrtce wykorzystamy t moliwo. Funkcja unicode Funkcja unicode peni t sam funkcj, co str, ale zamiast acucha znakw tworzy unikod. Przykad 4.8 Wprowadzenie do unicode >>> unicode(1) #(1) u1 >>> unicode(odbchelper) #(2) u"<module odbchelper from c:\\docbook\\dip\\py\\odbchelper.py>" >>> print unicode(horsemen[0]) uwar >>> unicode(jedziectwo) #(3) Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeDecodeError: ascii codec cant decode byte 0xc5 in position 2: ordinal not in range(128) >>> unicode(jedziectwo, utf-8) #(4) uje\u017adziectwo 1. Podobnie, jak w przypadku str, do funkcji unicode moemy przekaza dowolny obiekt np. moe to by liczba. Przekonwertowalimy liczb na napis unikodowy. 2. Argumentem funkcji unicode moe by take modu. 3. Poniewa litera nie naley do ASCII, wic Python nie potra jej zinterpretowa. Zostaje rzucony wyjtek.

70

ROZDZIA 5. POTGA INTROSPEKCJI 4. Do funkcji unicode moemy przekaza drugi, opcjonalny argument encoding, ktry okrela, w jakim systemie kodowania jest zakodowany acuch znakw. Komputer, na ktrym zosta uruchomiony ten przykad, korzysta z kodowania UTF-8, wic przekazany acuch znakw take bdzie w tym systemie kodowania.

Funkcja dir Kluczow funkcj wykorzystan w info jest funkcja dir. Funkcja ta zwraca list atrybutw i metod pewnego obiektu np. moduu, funkcji, acuch znakw, listy, sownika... niemal wszystkiego. Przykad 4.9 Wprowadzenie do dir >>> li = [] >>> dir(li) #(1) [__add__, __class__, __contains__, __delattr__, __delitem__, __delslice__, __doc__, __eq__, __ge__, __getattribute__, __getitem__, __getslice__, __gt__, __hash__, __iadd__, __imul__, __init__, __iter__, __le__, __len__, __lt__, __mul__, __ne__, __new__, __reduce__, __reduce_ex__, __repr__, __reversed__, __rmul__, __setattr__, __setitem__, __setslice__, __str__, append, count, extend, index, insert, pop, remove, reverse, sort] >>> d = {} >>> dir(d) #(2) [[...,clear, copy, fromkeys, get, has_key, items, iteritems, iterkeys, itervalues, keys, pop, popitem, setdefault, update, values] >>> import odbchelper >>> dir(odbchelper) #(3) [__builtins__, __doc__, __file__, __name__, buildConnectionString] 1. li jest list, dlatego te dir(li) zwrci nam list wszystkich metod, ktre posiada lista. Zwrmy uwag na to, e zwracana lista zawiera nazwy metod w formie acucha znakw, a nie metody same w sobie. Metody zaczynajce si i koczce dwoma dolnymi mylnikami s metodami specjalnymi. 2. d jest sownikiem, dlatego dir(d) zwrci list nazw metod sownika. Co najmniej jedna z nich, metoda keys, powinna wyglda znajomo. 3. Dziki temu funkcja ta staje si interesujca. odbchelper jest moduem, wic za pomoc dir(odbchelper) otrzymamy list nazw atrybutw tego moduu wczajc w to wbudowane atrybuty np. name , czy te doc , a take jakiekolwiek inne np. zdeniowane przez nas funkcje. W tym przypadku odbchelper posiada tylko jedn, zdeniowan przez nas metod funkcj buildConnectionString opisan w rozdziale Pierwszy program. Funkcja callable Funkcja callable zwraca True, jeli podany obiekt moe by wywoywany, a False w przeciwnym przypadku. Do wywoywalnych obiektw zaliczamy funkcje, metody klas, a nawet same klasy. (Wicej o klasach moemy przeczyta w nastpnym rozdziale.)

5.4. TYPE, STR, DIR I INNE WBUDOWANE FUNKCJE Przykad 4.10 Wprowadzenie do callable >>> import string >>> string.punctuation !"#$%&\()*+,-./:;<=>?@[\\]^_{|}~ >>> string.join <function join at 00C55A7C> >>> callable(string.punctuation) False >>> callable(string.join) True >>> print string.join.__doc__ join(list [,sep]) -> string #(1) #(2) #(3) #(4) #(5)

71

Return a string composed of the words in list, with intervening occurrences of sep. The default separator is a single space. (joinfields and join are synonymous)

1. Nie zaleca si, eby wykorzystywa funkcje z moduu string (chocia wci wiele osb uywa funkcji join), ale modu ten zawiera wiele przydatnych staych jak np. string.punctuation, ktry zawiera wszystkie standardowe znaki przestankowe, wic z niego tutaj skorzystalimy. 2. Funkcja string.join czy list w acuch znakw. 3. string.punctuation nie jest wywoywalny, jest acuchem znakw. (Typ string posiada metody, ktre moemy wywoywa, lecz sam w sobie nie jest wywoywalny.) 4. string.join mona wywoa. Jest to funkcja przyjmujca dwa argumenty. 5. Kady wywoywalny obiekt moe posiada notk dokumentacyjn. Kiedy wykonamy funkcj callable na kadym atrybucie danego obiektu, bdziemy mogli potencjalnie okreli, ktrymi atrybutami chcemy si bardziej zainteresowa (metody, funkcje, klasy), a ktre chcemy pomin (stae itp.). Wbudowane funkcje type, str, unicode, dir i wszystkie pozostae wbudowane funkcje s umieszczone w specjalnym module o nazwie builtin (nazwa z dwoma dolnymi mylnikami przed i po nazwie). Jeli to pomoe, moemy zaoy, e Python automatycznie wykonuje przy starcie polecenie from builtin import *, ktre bezporednio importuje wszystkie wbudowane funkcje do uywanej przez nas przestrzeni nazw. Zalet tego, e funkcje te znajduj si w module, jest to, e moemy dosta informacje o wszystkich wbudowanych funkcjach i atrybutach poprzez modu builtin . Wykorzystajmy funkcje info podajc jako argument ten modu i przejrzyjmy wywietlony spis. Niektre z waniejszych funkcji w module builtin zgbimy pniej. (Niektre z wbudowanych klas bdw np. AttributeError, powinny wyglda znajomo.).

72

ROZDZIA 5. POTGA INTROSPEKCJI

Przykad 4.11 Wbudowane atrybuty i funkcje >>> from apihelper import info >>> import __builtin__ >>> info(__builtin__, 20) ArithmeticError Base class for arithmetic errors. AssertionError Assertion failed. AttributeError Attribute not found. EOFError Read beyond end of file. EnvironmentError Base class for I/O related errors. Exception Common base class for all exceptions. FloatingPointError Floating point operation failed. IOError I/O operation failed. [...ciach...] Wraz z Pythonem dostajemy doskona dokumentacj, zawierajc wszystkie potrzebne informacje o moduach oferowanych przez ten jzyk. Porwnujc do innych jzykw, z Pythonem nie dostajemy podrcznika man, czy odwoa do innych zewntrznych podrcznikw, wszystko co potrzebujemy znajdujemy wewntrz samego Pythona.

Materiay dodatkowe Python Library Reference dokumentuje wszystkie wbudowane funkcje i wszystkie wbudowane wyjtki.

5.5. FUNKCJA GETATTR

73

5.5

Funkcja getattr

Funkcja getattr
Powinnimy ju wiedzie, e w Pythonie funkcje s obiektami. Ponadto moemy dosta referencj do funkcji bez znajomoci jej nazwy przed uruchomieniem programu. W tym celu podczas dziaania programu naley wykorzysta funkcj getattr. Przykad 4.12 Funkcja getattr >>> li = ["Larry", "Curly"] >>> li.pop #(1) <built-in method pop of list object at 010DF884> >>> getattr(li, "pop") #(2) <built-in method pop of list object at 010DF884> >>> getattr(li, "append")("Moe") #(3) >>> li ["Larry", "Curly", "Moe"] >>> getattr({}, "clear") #(4) <built-in method clear of dictionary object at 00F113D4> >>> getattr((), "pop") #(5) Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: tuple object has no attribute pop 1. Dziki temu dostalimy referencj do metody pop. Zauwamy, e w ten sposb nie wywoujemy metody pop; aby j wywoa musielibymy wpisa polecenie li.pop(). Otrzymujemy referencj do tej metody. (Adres szesnastkowy wyglda inaczej na rnych komputerach, dlatego wyjcia bd si nieco rni.) 2. Operacja ta take zwrcia referencj do metody pop, lecz tym razem nazwa metody jest okrelona poprzez acuch znakw w argumencie funkcji getattr. getattr jest bardzo przydatn, wbudowan funkcj, ktra zwraca pewien atrybut dowolnego obiektu. Tutaj wykorzystujemy obiekt, ktry jest list, a atrybutem jest metoda pop. 3. Dziki temu przykadowi moemy zobaczy, jaki duy potencja kryje si w funkcji getattr. W tym przypadku zwracan wartoci getattr jest metoda (referencja do metody). Metod t moemy wykona podobnie, jak bymy bezporednio wywoali li.append(Moe). Tym razem nie wywoujemy funkcji bezporednio, lecz okrelamy nazw funkcji za pomoc acucha znakw. 4. getattr bez problemu pracuje na sownikach 5. Teoretycznie getattr powinien pracowa na krotkach, jednak krotki nie posiadaj adnej metody, dlatego getattr spowoduje wystpienie wyjtku zwizanego z brakiem atrybutu o podanej nazwie. getattr na moduach getattr dziaa nie tylko na wbudowanych typach danych. Argumentem tej funkcji moe by take modu.

74

ROZDZIA 5. POTGA INTROSPEKCJI

Przykad 4.13 Funkcja getattr w apihelper.py >>> import odbchelper >>> odbchelper.buildConnectionString #(1) <function buildConnectionString at 00D18DD4> >>> getattr(odbchelper, "buildConnectionString") #(2) <function buildConnectionString at 00D18DD4> >>> object = odbchelper >>> method = "buildConnectionString" >>> getattr(object, method) #(3) <function buildConnectionString at 00D18DD4> >>> type(getattr(object, method)) #(4) <type function> >>> import types >>> type(getattr(object, method)) == types.FunctionType True >>> callable(getattr(object, method)) #(5) True 1. Polecenie to zwraca nam referencj do funkcji buildConnectionString z moduu odbchelper, ktry przeanalizowalimy w Rozdziale 2. 2. Wykorzystujc getattr, moemy dosta tak sam referencj, do tej samej funkcji. W oglnoci getattr(obiekt, atrybut) jest odpowiednikiem obiekt.atrybut. Jeli obiekt jest moduem, atrybutem moe by cokolwiek zdeniowane w tym module np. funkcja, klasa czy zmienna globalna. 3. T moliwo wykorzystalimy w funkcji info. Obiekt o nazwie object zosta przekazany jako argument do funkcji getattr, ponadto przekazalimy nazw pewnej metody lub funkcji jako zmienn method. 4. W tym przypadku zmienna method przechowuj nazw funkcji, co mona sprawdzi pobierajc typ zwracanej wartoci. 5. Poniewa zmienna method jest funkcj, wic mona j wywoywa. Zatem w wyniku wywoania callable otrzymalimy warto True. getattr jako funkcja poredniczca Funkcja getattr jest powszechnie uywana jako funkcja poredniczca (ang. dispatcher ). Na przykad mamy napisany program, ktry moe wypisywa dane w rnych formatach (np. HTML i PS). Wwczas dla kadego formatu wyjcia moemy zdeniowa odpowiedni funkcje, a podczas wypisywania danych na wyjcie getattr bdzie nam poredniczy midzy tymi funkcjami. Jeli wydaje si to troch zagmatwane, zaraz zobaczymy przykad. Wyobramy sobie program, ktry potra wywietla statystyki strony w formacie HTML, XML i w czystym tekcie. Wybr waciwego formatu moe by okrelony w linii polece lub przechowywany w pliku konguracyjnym. Modu statsout deniuje trzy funkcje output html, output xml i output text, a wwczas program gwny moe zdeniowa pojedyncz funkcj, ktra wypisuje dane na wyjcie:

5.5. FUNKCJA GETATTR Przykad 4.14 Poredniczenie za pomoc getattr import statsout def output(data, format="text"): output_function = getattr(statsout, "output_%s" % format) return output_function(data) #(1) #(2) #(3)

75

1. Funkcja output wymaga jednego argumentu o nazwie data, ktry ma zawiera dane do wypisania na wyjcie. Funkcja ta moe take przyj jeden opcjonalny argument format, ktry okrela format wyjcia. Gdy argument format nie zostanie okrelony, przyjmie on warto text, a funkcja si zakoczy wywoujc funkcj output text, ktra wypisuje dane na wyjcie w postaci czystego tekstu. 2. acucha znakw output poczylimy z argumentem format, aby otrzyma nazw funkcji. Nastpnie pobralimy funkcj o tej nazwie z moduu statsout. Dziki temu w przyszoci bdzie atwiej rozszerzy program nie zmieniajc funkcji poredniczcej, aby obsugiwa wicej wyjciowych programw. W tym celu wystarczy doda odpowiedni funkcj do statsout np. output pdf i wywoujemy funkcj output podajc argument format jako pdf. 3. Teraz moemy wywoa funkcj wyjciow w taki sam sposb jak inne funkcje. Zmienna output function jest referencj do odpowiedniej funkcji w statsout. Czy znalelimy bd w poprzednim przykadzie? Jest to bardzo niestabilne rozwizanie, ponadto nie ma tu kontroli bdw. Co si stanie gdy uytkownik poda format, ktry nie zdeniowalimy w statsout? Funkcja getattr rzuci nam wyjtek zwizany z bdnym argumentem, czyli podan nazw funkcji, ktra nie istnieje w module statsout. Na szczcie do funkcji getattr moemy poda trzeci, opcjonalny argument, czyli domylnie zwracan warto, gdy podany atrybut nie istnieje. Przykad 4.15 Domylnie zwracana warto w getattr import statsout def output(data, format="text"): output_function = getattr(statsout, "output_%s" % format, statsout.output_text) return output_function(data) #(1) 1. Ta funkcja ju na pewno bdzie dziaaa poprawnie, poniewa podalimy trzeci argument w wywoaniu funkcji getattr. Trzeci argument jest domyln wartoci, ktra zostanie zwrcona, gdy podany atrybut, czy metoda nie zostanie znaleziona. Jak moglimy zauway, funkcja getattr jest niezwykle uyteczna. Jest ona sercem introspekcji. W nastpnych rozdziaach zobaczymy jeszcze wicej przydatnych przykadw.

76

ROZDZIA 5. POTGA INTROSPEKCJI

5.6

Filtrowanie listy

Filtrowanie listy
Jak ju wiemy, Python ma potne moliwoci odwzorowania list w inne listy poprzez wyraenia listowe (rozdzia Odwzorowywanie listy). Wyraenia listowe moemy te czy z mechanizmem ltrowania, dziki ktremu pewne elementy listy s odwzorowywane a pewne pomijane. Poniej przedstawiono skadni ltrowania listy: [wyraenie odwzorowujce for element in odwzorowywana lista if wyraenie filtrujce] Jest to wyraenie listowe z pewnym rozszerzeniem. Pocztek wyraenia jest identyczny, ale kocowa cz zaczynajca si od if, jest wyraeniem ltrujcym. Wyraenie ltrujce moe by dowolnym wyraeniem, ktre moe zosta zinterpretowane jako wyraenie logiczne. Kady element dla ktrego wyraenie to bdzie prawdziwe, zostanie doczony do wyjciowej listy. Wszystkie inne elementy dla ktrych wyraenie ltrujce jest faszywe, zostan pominite i nie tra do wyjciowej listy. Przykad 4.16 Filtrowanie listy >>> li = ["a", "mpilgrim", "foo", "b", "c", "b", "d", "d"] >>> [elem for elem in li if len(elem) > 1] #(1) [mpilgrim, foo] >>> [elem for elem in li if elem != "b"] #(2) [a, mpilgrim, foo, c, d, d] >>> [elem for elem in li if li.count(elem) == 1] #(3) [a, mpilgrim, foo, c]

1. W tym przykadzie wyraenie odwzorowujce nie jest skomplikowane (zwraca po prostu warto kadego elementu), wic skoncentrujmy si na wyraeniu ltrujcym. Kiedy Python przechodzi przez kady element listy, sprawdza czy wyraenie ltrujce jest prawdziwe dla tego elementu. Jeli tak bdzie, to Python wykona wyraenie odwzorowujce na tym elemencie i wstawi odwzorowany element do zwracanej listy. W tym przypadku odltrowujemy wszystkie acuchy znakw, ktre maj co najwyej jeden znak, tak wic otrzymujemy list wszystkich duszych napisw. 2. Tutaj odltrowujemy elementy, ktre przechowuj warto b. Zauwamy, e to wyraenie listowe odltrowuje wszystkie wystpienia b, poniewa za kadym razem, gdy dany element bdzie rwny b, wyraenie ltrujce bdzie faszywe, a zatem warto ta nie zostanie wstawiona do zwracanej listy. 3. count jest metod listy, ktra zwraca ilo wystpie danej wartoci w licie. Mona si domyla, e ten ltr usunie duplikujce si wartoci, przez co zostanie zwrcona lista, ktra zawiera tylko jedn kopi kadej wartoci z oryginalnej listy. Jednak tak si nie stanie. Wartoci, ktre pojawiaj si dwukrotnie w oryginalnej licie (w tym wypadku b i d) zostan cakowicie odrzucone. Istnieje moliwo usunicia duplikatw z listy, jednak ltrowanie listy nie daje nam takiej moliwoci.

5.6. FILTROWANIE LISTY Wrmy teraz do apihelper.py, do poniszej linii:

77

methodList = [method for method in dir(object) if callable(getattr(object, method))] To wyraenie listowe wyglda skomplikowanie, a nawet jest skomplikowane, jednak podstawowa struktura jest taka sama. Cae to wyraenie zwraca list, ktra zostaje przypisana do zmiennej methodList. Pierwsza cz to cz odwzorowujca list. Wyraenie odwzorowujce zwraca warto danego elementu. dir(object) zwraca list atrybutw i metod obiektu object, czyli jest to po prostu lista, ktr odwzorowujemy. Tak wic now czci jest tylko wyraenie ltrujce znajdujce si za instrukcj if. To wyraenie nie jest takie straszne, na jakie wyglda. Ju poznalimy funkcje callable, getattr oraz in. Jak ju wiemy z poprzedniego podrozdziau, funkcja getattr(object, method) zwraca obiekt funkcji (czyli referencj do tej funkcji), jeli object jest moduem, a method jest nazw funkcji w tym module. Podsumowujc, wyraenie bierze pewien obiekt (nazwany object). Nastpnie pobiera list nazw atrybutw tego obiektu, a take metod i funkcji oraz kilka innych rzeczy. Nastpnie odrzuca te rzeczy, ktre nas nie interesuj, czyli pobieramy nazwy kadego atrybutu/metody/funkcji, a nastpnie za pomoc getattr pobieramy referencje do atrybutw o tych nazwach. Potem za pomoc funkcji callable sprawdzamy, czy ten obiekt jest wywoywalny, a dziki temu dowiadujemy si, czy mamy do czynienia z metod lub jak funkcj. Mog to by na przykad funkcje wbudowane (np. metoda listy o nazwie pop), czy te funkcje zdeniowane przez uytkownika (np. funkcja buildConnectionString z moduu odbchelper). Nie musimy natomiast martwi si o inne atrybuty jak np. wbudowany do kadego moduu atrybut name (nie jest on wywoywalny, czyli callable zwrci warto False). Materiay dodatkowe Python Tutorial omawia inny sposb ltrowania listy, za pomoc wbudowanej funkcji filter.

78

ROZDZIA 5. POTGA INTROSPEKCJI

5.7

Operatory and i or

Operatory and i or
Operatory and i or odpowiadaj boolowskim operacjom logicznym, jednak nie zwracaj one wartoci logicznych. Zamiast tego zwracaj ktr z podanych wartoci. Przykad 4.17 Poznajemy and >>> a and b b >>> and b >>> a and b and c c #(1) \#(2) #(3)

1. Podczas uywania and wartoci s oceniane od lewej do prawej. 0, , [], (), {} i None s faszem w kontekcie logicznym, natomiast wszystko inne jest prawd. C, prawie wszystko. Domylnie instancje klasy w kontekcie logicznym s prawd, ale moesz zdeniowa specjalne metody w swojej klasie, ktre sprawi, e bdzie ona faszem w kontekcie logicznym. Wszystkiego o klasach i specjalnych metodach nauczymy si w rozdziale Obiekty i klasy. Jeli wszystkie wartoci s prawd w kontekcie logicznym, and zwraca ostatni warto. W tym przypadku and najpierw bierze a, co jest prawd, a potem b, co te jest prawd, wic zwraca ostatni warto, czyli b. 2. Jeli jaka warto jest faszywa w kontekcie logicznym, and zwraca pierwsz faszyw warto. W tym wypadku jest pierwsz faszyw wartoci. 3. Wszystkie wartoci s prawd, tak wic and zwraca ostatni warto, c. Przykad 4.18 Poznajemy or >>> a >>> b >>> {} >>> ... ... >>> a a or b or b or [] or {} #(1) #(2) #(3)

def sidefx(): print "in sidefx()" return 1 a or sidefx() #(4)

1. Uywajc or wartoci s oceniane od lewej do prawej, podobnie jak w and. Jeli jaka warto jest prawd, or zwraca t warto natychmiast. W tym wypadku, a jest pierwsz wartoci prawdziw. 2. or wyznacza jako fasz, ale potem b, jako prawd i zwraca b. 3. Jeli wszystkie wartoci s faszem, or zwraca ostatni warto. or ocenia jako fasz, potem [] jako fasz, potem {} jako fasz i zwraca {}.

5.7. OPERATORY AND I OR

79

4. Zauwamy, e or ocenia kolejne wartoci od lewej do prawej, dopki nie znajdzie takiej, ktra jest prawd w kontekcie logicznym, a pozosta reszt ignoruje. Tutaj, funkcja sidefx nigdy nie jest wywoana, poniewa ju a jest prawd i a zostanie zwrcone natychmiastowo. Jeli jeste osob programujc w jzyku C, na pewno znajome jest ci wyraenie bool ? a : b, z ktrego otrzymamy a, jeli bool jest prawd, lub b w przeciwnym wypadku. Dziki sposobowi dziaania operatorw and i or w Pythonie, moemy osign podobny efekt. Sztuczka and-or Przykad 4.19 Poznajemy sztuczk and-or >>> a = "first" >>> b = "second" >>> 1 and a or b first >>> 0 and a or b second

#(1) #(2)

1. Ta skadnia wyglda podobnie do wyraenia bool ? a : b w C. Cae wyraenie jest oceniane od lewej do prawej, tak wic najpierw okrelony zostanie and. Czyli 1 and first daje first, potem first or second daje first. 2. 0 and first daje 0, a potem 0 or second daje second. Jakkolwiek te wyraenia Pythona s po prostu logik boolowsk, a nie specjaln konstrukcj jzyka. Istnieje jedna bardzo wana rnica pomidzy Pythonow sztuczk and-or, a skadni bool ? a : b w C. Jeli warto a jest faszem, wyraenie to nie bdzie dziaao tak, jakbymy chcieli. Mona si na tym niele przejecha, co zobaczymy w poniszym przykadzie. Przykad 4.20 Kiedy zawodzi sztuczka and-or >>> a = "" >>> b = "second" >>> 1 and a or b second

#(1)

1. Poniewa a jest pustym napisem, ktry Python uwaa za fasz w kontekcie logicznym, wic 1 and daje , a nastpnie or second daje second. Ups! To nie to, czego oczekiwalimy. Sztuczka and-or, czyli wyraenie bool and a or b, nie bdzie dziaao w identyczny sposb, jak wyraenie w C bool ? a : b, jeli a bdzie faszem w kontekcie logicznym. Prawdziw sztuczk kryjc si za sztuczk and-or, jest upewnienie si, czy warto a nigdy nie jest faszywa. Jednym ze sposobw na wykonanie tego to przeksztacenie a w [a] i b w [b], a potem pobranie pierwszego elementu ze zwrconej listy, ktrym bdzie a lub b.

80

ROZDZIA 5. POTGA INTROSPEKCJI

Przykad 4.21 Bezpieczne uycie sztuczki and-or >>> a = "" >>> b = "second" >>> (1 and [a] or [b])[0] #(1) 1. Jako e [a] jest nie pust list, wic nigdy nie bdzie faszem (tylko pusta lista jest faszem). Nawet gdy a jest rwne 0 lub lub inn wartoci dajc fasz, lista [a] bdzie prawd, poniewa ma jeden element. Jak dotd, ta sztuczka moe wydawa si bardziej uciliwa ni pomocna. Moesz przecie osign to samo zachowanie instrukcj if, wic po co to cae zamieszanie. C, w wielu przypadkach, wybierasz pomidzy dwoma staymi wartociami, wic moesz uy prostszego zapisu i si nie martwi, poniewa wiesz, e warto zawsze bdzie prawd. I nawet kiedy potrzebujesz uy bardziej skomplikowanej, bezpiecznej formy, istniej powody, aby i tak czasami korzysta z tej sztuczki. Na przykad, s pewne przypadki w Pythonie gdzie instrukcje if nie s dozwolone np. w wyraeniach lambda. W Pythonie 2.5 wprowadzono odpowiednik wyraenia bool ? a : b z jzyka C; jest ono postaci: a if bool else b. Jeli warto bool bdzie prawdziwa, to z wyraenia zostanie zwrcona warto a, a jeli nie, to otrzymamy warto b.

Materiay dodatkowe Python Cookbook omawia alternatywy do triku and-or.

5.8. WYRAENIA LAMBDA

81

5.8

Wyraenia lambda

Wyraenia lambda
Python za pomoc pewnych wyrae pozwala nam zdeniowa jednolinijkowe minifunkcje. Te tzw. funkcje lambda s zapoyczone z Lispa i mog by uyte wszdzie tam, gdzie potrzebna jest funkcja. Przykad 4.22 Tworzymy funkcje lambda >>> ... ... >>> 6 >>> >>> 6 >>> 6 def f(x): return x*2 f(3) g = lambda x: x*2 g(3) (lambda x: x*2)(3) #(1)

#(2)

1. W ten sposb tworzymy funkcj lambda, ktra daje ten sam efekt jak normalna funkcja nad ni. Zwrmy uwag na skrcon skadni: nie ma nawiasw wok listy argumentw, brakuje te sowa kluczowego return (caa funkcja moe by tylko jednym wyraeniem). Funkcja nie posiada rwnie nazwy, ale moe by wywoana za pomoc zmiennej, do ktrej zostanie przypisana. 2. Moemy uy funkcji lambda bez przypisywania jej do zmiennej. Moe taki sposb korzystania z wyrae lambda nie jest zbyt przydatny, ale w ten sposb moemy zobaczy, e za pomoc tego wyraenia tworzymy funkcj jednolinijkow. Podsumowujc, funkcja lambda jest funkcj, ktra pobiera dowoln liczb argumentw (wczajc argumenty opcjonalne) i zwraca warto, ktr otrzymujemy po wykonaniu pojedynczego wyraenia. Funkcje lambda nie mog zawiera polece i nie mog zawiera wicej ni jednego wyraenia. Nie prbujmy upcha zbyt duo w funkcj lambda; zamiast tego jeli potrzebujemy co bardziej zoonego, zdeniujmy normaln funkcj. Korzystanie z wyrae lambda zaley od stylu programowania. Uywanie ich nigdy nie jest wymagane; wszdzie gdzie moglibymy z nich skorzysta, rwnie dobrze moglibymy zdeniowa oddzieln, normaln funkcj i uy jej zamiast funkcji lambda. Funkcje lambda moemy uywa np. w miejscach, w ktrych potrzebujemy specycznego, nieuywalnego powtrnie kodu. Dziki temu nie zamiecamy kodu wieloma maymi jednoliniowymi funkcjami.

Funkcje lambda w prawdziwym wiecie Poniej przedstawiamy funkcje lambda wykorzystan w apihelper.py: processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)

82

ROZDZIA 5. POTGA INTROSPEKCJI

Zauwamy, e uyta jest tu prosta forma sztuczki and-or, ktra jest bezpieczna, poniewa funkcja lambda jest zawsze prawd w kontekcie logicznym. (To nie znaczy, e funkcja lambda nie moe zwraca wartoci bdcej faszem. Funkcja jest zawsze prawd w kontekcie logicznym, ale jej zwracana warto moe by czymkolwiek.) Zauwamy rwnie, e uywamy funkcji split bez argumentw. Widzielimy ju j uyt z jednym lub dwoma argumentami. Jeli nie podamy argumentw, wwczas domylnym separatorem tej funkcji s biae znaki (czyli spacja, znak nowej linii, znak tabulacji itp.). Przykad 4.23 split bez argumentw >>> s = "this is\na\ttest" #(1) >>> print s this is a test >>> print s.split() #(2) [this, is, a, test] >>> print " ".join(s.split()) #(3) this is a test 1. Tutaj mamy wieloliniowy napis, zdeniowany przy pomocy znakw sterujcych zamiast uycia trzykrotnych cudzysoww. \n jest znakiem nowej linii, a \t znakiem tabulacji. 2. split bez adnych argumentw dzieli na biaych znakach, a trzy spacje, znak nowej linii i znak tabulacji s biaymi znakami. 3. Moemy unormowa biae znaki poprzez podzielenie napisu za pomoc metody split, a potem powtrne zczenie metod join, uywajc pojedynczej spacji jako separatora. To wanie robi funkcja info, aby zwin wieloliniowe notki dokumentacyjne w jedn lini. Co wic waciwie funkcja info robi z tymi funkcjami lambda, dzieleniami i sztuczkami and-or? processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s) processFunc jest teraz referencj do funkcji, ale zalen od zmiennej collapse. Jeli collapse jest prawd, processFunc(string) bdzie zwija biae znaki, a w przeciwnym wypadku processFunc(string) zwrci swj argument niezmieniony. Aby to zrobi w mniej zaawansowanym jzyku (np. w Visual Basicu), prawdopodobnie stworzylibymy funkcj, ktra pobiera napis oraz argument collapse i uywa instrukcji if, aby okreli czy zawija biae znaki czy te nie, a potem zwracaaby odpowiedni warto. Takie podejcie nie byoby zbyt efektywne, poniewa funkcja musiaaby obsuy kady moliwy przypadek. Za kadym jej wywoaniem, musiaaby zdecydowa czy zawija biae znaki zanim daaby nam to, co chcemy. W Pythonie logik wyboru moemy wyprowadzi poza funkcj i zdeniowa funkcj lambda, ktra jest dostosowana do tego, aby da nam dokadnie to (i tylko to), co chcemy. Takie podejcie jest bardziej efektywne, bardziej eleganckie i mniej podatne na bdy typu Ups! Te argumenty miay by w odwrotnej kolejnoci....

5.8. WYRAENIA LAMBDA Materiay dodatkowe

83

Python Knowledge Base omawia, jak wykorzystywa lambda, aby wywoywa funkcje w sposb niebezporedni. Python Tutorial pokazuje, jak dosta si do zewntrznych zmiennych z wntrza funkcji lambda. The Whole Python FAQ zawiera przykady, pokazujce, jak mona zagmatwa kod funkcji lambda.

84

ROZDZIA 5. POTGA INTROSPEKCJI

5.9

Potga introspekcji - wszystko razem

Wszystko razem
Ostatnia linia kodu, jedyna ktrej jeszcze nie rozpracowalimy, to ta, ktra odwala ca robot. Teraz umieszczamy wszystkie puzzle w jednym miejscu i nadchodzi czas, aby je uoy. To jest najwaniejsza cz apihelper.py: print "\n".join(["%s %s" % (method.ljust(spacing), processFunc(unicode(getattr(object, method).__doc__))) for method in methodList]) Zauwamy, e jest to tylko jedne wyraenie podzielone na wiele linii, ale ktre nie uywa znaku kontynuacji (znaku odwrotnego ukonika, \). Pamitasz jak powiedzielimy, e pewne instrukcje mog by rozdzielone na kilka linii bez uywania odwrotnego ukonika? Wyraenia listowe s jednym z tego typu wyrae, poniewa cae wyraenie jest zawarte w nawiasach kwadratowych. Zacznijmy od koca i posuwajmy si w ty. Wyraenie for method in methodList okazuje si by wyraeniem listowym. Jak wiemy, methodList jest list wszystkich metod danego obiektu, ktre nas interesuj, wic za pomoc tej ptli przechodzimy t list wykorzystujc zmienn method. Przykad 4.24 Dynamiczne pobieranie notki dokumentacyjnej >>> import odbchelper >>> object = odbchelper #(1) >>> method = buildConnectionString #(2) >>> getattr(object, method) #(3) <function buildConnectionString at 010D6D74> >>> print getattr(object, method).__doc__ #(4) Tworzy acuchw znakw na podstawie sownika parametrw. Zwraca acuch znakw. 1. W funkcji info, object jest obiektem do ktrego otrzymujemy pomoc, a ten obiekt zostaje przekazany jako argument. 2. Podczas iterowania listy methodList, method jest nazw aktualnej metody. 3. Uywajc funkcji getattr otrzymujemy referencj do funkcji method z moduu object. 4. Teraz wypisanie notki dokumentacyjnej bdzie bardzo proste. Nastpnym elementem puzzli jest uycie unicode na notce dokumentacyjnej. Jak sobie przypominamy, unicode jest wbudowan funkcj, ktra przeksztaca dane na unikod, ale notka dokumentacyjna jest zawsze acuchem znakw, wic po co j jeszcze konwertowa na unikod? Nie kada funkcja posiada notk dokumentacyjn, a jeli ona nie istnieje, atrybut doc ma warto None.

5.9. POTGA INTROSPEKCJI - WSZYSTKO RAZEM Przykad 4.25 Po co uywa unicode na notkach dokumentacyjnych? >>> def foo(): print 2 >>> foo() 2 >>> foo.__doc__ #(1) >>> foo.__doc__ == None #(2) True >>> unicode(foo.__doc__) #(3) uNone

85

1. Moemy atwo zdeniowa funkcj, ktra nie posiada notki dokumentacyjnej, tak wic jej atrybut doc ma warto None. Dezorientujce jest to, e jeli bezporednio odwoamy si do atrybutu doc , IDE w ogle nic nie wypisze. Jednak jeli si troch zastanowimy nad tym, takie zachowanie IDE ma jednak pewien sens 1 2. Moemy sprawdzi, e warto atrybutu doc porwnanie jej bezporednio z t wartoci. aktualnie wynosi None przez

3. W tym przypadku funkcja unicode przyjmuje pust warto, None i zwraca jej unikodow reprezentacj, czyli None. 4. li.append. doc jest acuchem znakw. Zauwamy, e wszystkie angielskie notki dokumentacyjne Pythona korzystaj ze znakw ASCII, dlatego moemy spokojnie je przekonwertowa do unikodu za pomoc funkcji unicode.

W SQL musimy skorzysta z IS NULL zamiast z = NULL, aby porwna co z pust wartoci. W Pythonie moemy uy albo == None albo is None, lecz is None jest szybsze. Teraz kiedy ju mamy pewno, e otrzymamy unikod, moemy przekaza otrzymany unikodowy napis do processFunc, ktr ju zdeniowalimy jako funkcj zwijajc lub niezwijajc biae znaki (w zalenoci od przekazanego argumentu). Czy ju wiemy, dlaczego wykorzystalimy unicode? Do przekonwertowania wartoci None na reprezentacj w postaci unikodowego acucha znakw. processFunc przyjmuje argument bdcy unikodem i wywouje jego metod split. Nie zadziaaoby to, gdybymy przekazali samo None, poniewa None nie posiada metody o nazwie split i rzucony zostaby wyjtek. Moe si zastanawiasz, dlaczego nie konwertujemy do str? Poniewa tworzone przez nas notki s napisami unikodowymi, w ktrych nie wszystkie znaki nale do ASCII, a zatem str rzuciby wyjtek. Idc wstecz, widzimy, e ponownie uywamy formatowania acucha znakw, aby poczy wartoci zwrcone przez processFunc i przez metod ljust. Jest to metoda acucha znakw (dodajmy, e napis unikodowy take jest acuchem znakw, tylko nieco o wikszych moliwociach), ktrej jeszcze nie poznalimy.
1 Pamitamy, e kada funkcja zwraca pewn warto? Jeli funkcja nie wykorzystuje instrukcji return, zostaje zwrcone None, a czste wywietlanie None po wykonaniu pewnych funkcji przez IDE Pythona mogoby by troch uciliwe.

86 Przykad 4.26 Poznajemy ljust >>> s = buildConnectionString >>> s.ljust(30) #(1) buildConnectionString >>> s.ljust(20) #(2) buildConnectionString

ROZDZIA 5. POTGA INTROSPEKCJI

1. ljust wypenia napis spacjami do zadanej dugoci. Z tej moliwoci korzysta funkcja info, aby stworzy dwie kolumny na wyjciu i aby wszystkie notki dokumentacyjne umieci w drugiej kolumnie. 2. Jeli podana dugo jest mniejsza ni dugo napisu, ljust zwrci po prostu napis niezmieniony. Metoda ta nigdy nie obcina acucha znakw. Ju prawie skoczylimy. Majc nazw metody method uzupenion spacjami poprzez ljust i (prawdopodobnie zwinit) notk dokumentacyjn otrzyman z wywoania processFunc, czymy je i otrzymujemy pojedynczy napis, acuch znakw. Poniewa odwzorowujemy list methodList, dostajemy list zoon z takich acuchw znakw. Uywajc metody join z napisu \n, czymy t list w jeden acuch znakw, gdzie kady elementem listy znajduje si w oddzielnej linii i ostatecznie wypisujemy rezultat. Przykad 4.27 Wypisywanie listy >>> li = [a, b, c] >>> print "\n".join(li) #(1) a b c 1. Ta sztuczka moe by pomocna do znajdowania bdw, gdy pracujemy na listach, a w Pythonie zawsze pracujemy na listach. I to ju by ostatni element puzzli. Teraz powiniene zrozumie ten kod. print "\n".join(["%s %s" % (method.ljust(spacing), processFunc(unicode(getattr(object, method).__doc__))) for method in methodList])

5.10. PODSUMOWANIE

87

5.10

Potga introspekcji - podsumowanie

Podsumowanie
Program apihelper.py i jego wyjcie powinno teraz nabra sensu. def info(object, spacing=10, collapse=1): u"""Wypisuje metody i ich notki dokumentacyjne. Argumentem moe by modu, klasa, lista, sownik, czy te acuch znakw.""" methodList = [e for e in dir(object) if callable(getattr(object, e))] processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s) print "\n".join(["%s %s" % (method.ljust(spacing), processFunc(unicode(getattr(object, method).__doc__))) for method in methodList]) if __name__ == "__main__": print info.__doc__ A z tutaj mamy przykad wyjcia, ktre otrzymujemy z programu apihelper.py: >>> from apihelper import info >>> li = [] >>> info(li) append L.append(object) -- append object to end count L.count(value) -> integer -- return number of occurrences of value extend L.extend(iterable) -- extend list by appending elements from the iterable index L.index(value, [start, [stop]]) -> integer -- return first index of value insert L.insert(index, object) -- insert object before index pop L.pop([index]) -> item -- remove and return item at index (default last) remove L.remove(value) -- remove first occurrence of value reverse L.reverse() -- reverse *IN PLACE* sort L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*; cmp(x, y) -> -1, 0, 1 append Zanim przejdziemy do nastpnego rozdziau, upewnijmy si, e nie mamy problemw z wykonywaniem poniszych czynnoci: Deniowanie i wywoywanie funkcji z opcjonalnymi i nazwanymi argumentami Importowanie moduw za pomoc import module i from module import Uywanie str do przeksztacenia jakiejkolwiek przypadkowej wartoci na reprezentacj w postaci acucha znakw, a take uywanie unicode do przeksztacania wartoci na unikod. Uywanie getattr do dynamicznego otrzymywania referencji do funkcji i innych atrybutw Tworzenie wyrae listowych z ltrowaniem Rozpoznawanie sztuczki and-or i uywanie jej w sposb bezpieczny

88 Deniowanie funkcji lambda

ROZDZIA 5. POTGA INTROSPEKCJI

Przypisywanie funkcji do zmiennych i wywoywanie funkcji przez zmienn. Trudno jest to mocniej zaakcentowa, jednak umiejtno wykonywania tego jest niezbdne do lepszego rozumienia Pythona. Zobaczymy bardziej zoone aplikacje opierajce si na tej koncepcji w dalszej czci ksiki.

Rozdzia 6

Obiekty i klasy

89

90

ROZDZIA 6. OBIEKTY I KLASY

6.1

Obiekty i klasy

Rozdzia ten zaznajomi nas ze zorientowanym obiektowo programowaniem przy uyciu jzyka Python.

Nurkujemy
Poniej znajduje si kompletny program, ktry oczywicie dziaa. Czytanie notki dokumentacyjnej moduu, klasy, czy te funkcji jest pomocne w zrozumieniu co dany program waciwie robi i w jaki sposb dziaa. Jak zwykle, nie martwmy si, e nie moemy wszystkiego zrozumie. W kocu zasada dziaania tego programu zostanie dokadnie opisana w dalszej czci tego rozdziau. Przykad 5.1 leinfo.py #-*- coding: utf-8 -*u"""Framework do pobierania metadanych specyficznych dla danego typu pliku. Mona utworzy instancj odpowiedniej klasy podajc jej nazw pliku w konstruktorze. Zwrcony obiekt zachowuje si jak sownik posiadajcy par klucz-warto dla kadego fragmentu metadanych. import fileinfo info = fileinfo.MP3FileInfo("/music/ap/mahadeva.mp3") print "\\n".join(["%s=%s" % (k, v) for k, v in info.items()]) Lub uy funkcji listDirectory, aby pobra informacje o wszystkich plikach w katalogu. for info in fileinfo.listDirectory("/music/ap/", [".mp3"]): ...

Framework moe by roszerzony poprzez dodanie klas dla poszczeglnych typw plikw, np.: HTMLFileInfo, MPGFileInfo, DOCFileInfo. Kada klasa jest cakowicie odpowiedzialna za waciwe sparsowanie swojego pliku; zobacz przykad MP3FileInfo. """ import os import sys def stripnulls(data): u"usuwa biae znaki i nulle" return data.replace("\00", " ").strip() class FileInfo(dict): u"przechowuje metadane pliku" def init (self, filename=None): dict. init (self) self["plik"] = filename class MP3FileInfo(FileInfo):

6.1. NURKUJEMY u"przechowuje znaczniki ID3v1.0 MP3" tagDataMap = {u"tytu" : ( 3, 33, "artysta" : ( 33, 63, "album" : ( 63, 93, "rok" : ( 93, 97, "komentarz" : ( 97, 126, "gatunek" : (127, 128, def

91

stripnulls), stripnulls), stripnulls), stripnulls), stripnulls), ord)}

parse(self, filename): u"parsuje znaczniki ID3v1.0 z pliku MP3" self.clear() try: fsock = open(filename, "rb", 0) try: fsock.seek(-128, 2) tagdata = fsock.read(128) finally: fsock.close() if tagdata[:3] == TAG: for tag, (start, end, parseFunc) in self.tagDataMap.items(): self[tag] = parseFunc(tagdata[start:end]) except IOError: pass setitem if key == self. FileInfo. (self, key, item): "plik" and item: parse(item) setitem (self, key, item)

def

def listDirectory(directory, fileExtList): u"zwraca list obiektw zawierajcych metadane dla plikw o podanych rozszerzeniach" fileList = [os.path.normcase(f) for f in os.listdir(directory)] fileList = [os.path.join(directory, f) for f in fileList \ if os.path.splitext(f)[1] in fileExtList] def getFileInfoClass(filename, module=sys.modules[FileInfo. module ]): u"zwraca klas metadanych pliku na podstawie podanego rozszerzenia" subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:] return hasattr(module, subclass) and getattr(module, subclass) or FileInfo return [getFileInfoClass(f)(f) for f in fileList] if name == " main ": for info in listDirectory("/music/ singles/", [".mp3"]): #(1) print "\n".join("%s=%s" % (k, v) for k, v in info.items()) print 1. Wynik wykonania tego programu zaley od tego jakie pliki mamy na twardym dysku. Aby otrzyma sensowne wyjcie, potrzebujemy zmieni ciek, aby wskazywa na katalog, w ktrym przechowujemy pliki MP3. W wyniku wykonania tego programu moemy otrzyma podobne wyjcie do poniszego. Jest niemal niemoliwe, aby otrzyma identyczne wyjcie.

92

ROZDZIA 6. OBIEKTY I KLASY

album= rok=1999 komentarz=http://mp3.com/ghostmachine tytu=A Time Long Forgotten (Concept artysta=Ghost in the Machine gatunek=31 plik=/music/_singles/a_time_long_forgotten_con.mp3 album=Rave Mix rok=2000 komentarz=http://mp3.com/DJMARYJANE tytu=HELLRAISER****Trance from Hell artysta=***DJ MARY-JANE*** gatunek=31 plik=/music/_singles/hellraiser.mp3 album=Rave Mix rok=2000 komentarz=http://mp3.com/DJMARYJANE tytu=KAIRO****THE BEST GOA artysta=***DJ MARY-JANE*** gatunek=31 plik=/music/_singles/kairo.mp3 album=Journeys rok=2000 komentarz=http://mp3.com/MastersofBalan tytu=Long Way Home artysta=Masters of Balance gatunek=31 plik=/music/_singles/long_way_home1.mp3 album= rok=2000 komentarz=http://mp3.com/cynicproject tytu=Sidewinder artysta=The Cynic Project gatunek=18 plik=/music/_singles/sidewinder.mp3 album=Digitosis@128k rok=2000 komentarz=http://mp3.com/artists/95/vxp tytu=Spinning artysta=VXpanded gatunek=255 plik=/music/_singles/spinning.mp3

6.2. DEFINIOWANIE KLAS

93

6.2

Deniowanie klas

Deniowanie klas
Python jest cakowicie zorientowany obiektowo: moemy deniowa wasne klasy, dziedziczy z wasnych lub wbudowanych klas, a take tworzy instancje zdeniowanych przez siebie klas. Tworzenie klas w Pythonie jest proste. Podobnie jak z funkcjami, nie uywamy oddzielnego interfejsu denicji. Po prostu deniujemy klas i zaczynamy j implementowa. Klasa w Pythonie rozpoczyna si sowem kluczowym class, po ktrym nastpuje nazwa klasy, a nastpnie w nawiasach okrgych umieszczamy, z jakich klas dziedziczymy. Przykad 5.3 Prosta klasa

class Nicosc(object): pass

#(1) #(2) (3)

1. Nazwa tej klasy to Nicosc, a klasa ta dziedziczy z wbudowanej klasy object. Nazwy klas s zazwyczaj pisane przy uyciu wielkich liter np. KazdeSlowoOdzieloneWielkaLitera, ale to kwestia konwencji nazewnictwa; nie jest to wymagane. 2. Klasa ta nie deniuje adnych metod i atrybutw, ale eby kod by zgodny ze skadni Pythona, musimy co umieci w denicji, tak wic uylimy pass. Jest to zastrzeone przez Pythona sowo, ktre mwi interpreterowi przejd dalej, nic tu nie ma. Instrukcja ta nie wykonuje adnej operacji i powinnimy stosowa j, gdy chcemy zostawi funkcj lub klas pust. 3. Prawdopodobnie zauwaylimy ju, e elementy w klasie s wyszczeglnione za pomoc wci, podobnie jak kod funkcji, instrukcji warunkowych, ptli itp. Pierwsza nie wcita instrukcja nie bdzie naleaa ju do klasy. Od Pythona 2.2 wprowadzono nowy styl klas (ang. new-style classes), ktrego bdziemy si uczyli. Aby pisa klasy w nowym stylu, musimy dziedziczy je z ktrej wbudowanej klasy, najczciej bdzie to object. W przeciwnym wypadku pisane przez nas klasy bd w starym stylu i niektre moliwoci nie bd dostpne. Dziki takiemu zachowaniu Pythona, jest on kompatybilny wstecz.

Instrukcja pass w Pythonie jest analogiczna do pustego zbioru nawiasw klamrowych ({}) w Javie lub w jzyku C++. W prawdziwym wiecie wikszo klas deniuje wasne metody i atrybuty. Jednak, jak mona zobaczy wyej, denicja klasy, oprcz swojej nazwy z dodatkiem object w nawiasach, nie musi nic zawiera. Programici C++ mog zauway, e w Pythonie klasy nie maj wyranie sprecyzowanych konstruktorw i destruktorw. Pythonowe klasy maj co, co przypomina konstruktor metod init .

94 Przykad 5.4 Deniowanie klasy FileInfo

ROZDZIA 6. OBIEKTY I KLASY

class FileInfo(dict): #(1) 1. Jak ju wiemy, klasy, z ktrych chcemy dziedziczy wyszczeglniamy w nawiasach okrgych, ktre z kolei umieszczamy bezporednio po nazwie naszej klasy. Tak wic klasa FileInfo dziedziczy z wbudowanej klasy dict, a ta klasa, to po prostu klasa sownika. W Pythonie klas, z ktrej dziedziczymy, wyszczeglniamy w nawiasach okrgych, umieszczonych bezporednio po nazwie naszej klasy. Python nie posiada specjalnego sowa kluczowego jak np. extends w Javie. Python obsuguje dziedziczenie wielokrotne. Wystarczy w nawiasach okrgych, umiejscowionych zaraz po nazwie klasy, wstawi nazwy klas, z ktrych chcemy dziedziczy i oddzieli je przecinkami np. class klasa(klasa1,klasa2). Inicjalizowanie i implementowanie klasy Ten przykad przedstawia inicjalizacj klasy FileInfo za pomoc metody Przykad 5.5 Inicjalizator klasy FileInfo class FileInfo(dict): u"przechowuje metadane pliku" def __init__(self, filename=None): #(1) #(2) (3) (4) init .

1. Klasy mog (a nawet powinny) posiada take notk dokumentacyjn, podobnie jak moduy i funkcje. 2. Metoda init jest wywoywana bezporednio po utworzeniu instancji klasy. Moe kusi, aby nazwa j konstruktorem klasy, co jednak nie jest prawd. Metoda init wyglda podobnie do konstruktora (z reguy init jest pierwsz metod deniowan dla klasy), dziaa podobnie (jest pierwszym fragmentem kodu wykonywanego w nowo utworzonej instancji klasy), a nawet podobnie brzmi (sowo init sugeruje, e jest to konstruktor). Niestety nie jest to prawda, poniewa obiekt jest ju utworzony przed wywoaniem metody init , a my ju otrzymujemy poprawn referencj do wieo utworzonego obiektu. Jednak init w Pythonie, jest tym co najbardziej przypomina konstruktor, a ponadto peni prawie tak sam rol. 3. Pierwszym argumentem kadej metody znajdujcej si w klasie, wczajc w to init , jest zawsze referencja do biecej instancji naszej klasy. Wedug powszechnej konwencji, argument ten jest zawsze nazywany self. W metodzie init self odwouje si do wanie utworzonego obiektu; w innych metodach klasy, self odwouje si do instancji, z ktrej wywoalimy dan metod. Mimo, e musimy wyranie okreli argument self podczas deniowania metody, ale nie okrelamy go w czasie wywoywania metody; Python dodaje go automatycznie.

6.2. DEFINIOWANIE KLAS

95

4. Metoda init moe posiada dowoln liczb argumentw i podobnie jak w funkcjach, argumenty mog by zdeniowane z domylnymi wartociami (w ten sposb staj si argumentami opcjonalnymi). W tym przypadku argument filename ma domyln warto okrelon jako None, ktry jest Pythonow pust wartoci. Wedug konwencji, pierwszy argument metody nalecej do pewnej klasy (referencja do biecej instancji klasy) jest nazywany self. Argument ten peni t sam rol, co zastrzeone sowo this w C++ czy Javie, ale self nie jest zastrzeonym sowem w Pythonie, jest jedynie konwencj nazewnictwa. Niemniej lepiej pozosta przy tej konwencji, poniewa jest to wrcz bardzo silna umowa.

Przykad 5.6 Kodowanie klasy FileInfo class FileInfo(dict): u"przechowuje metadane pliku" def __init__(self, filename=None): dict.__init__(self) self["plik"] = filename

#(1) #(2) #(3)

1. Niektre jzyki pseudo-zorientowane obiektowo jak Powerbuilder posiadaj koncepcj rozszerzania konstruktorw i innych zdarze, w ktrych metoda naleca do nadklasy jest wykonywana automatycznie przed metod podklasy. Python takiego czego nie wykonuje; zawsze naley wyranie wywoa odpowiedni metod nalec do przodka klasy. 2. Klasa ta dziaa podobnie jak sownik (w kocu z niego dziedziczymy), co moglimy zauway po spojrzeniu na t lini. Przypisalimy argument filename jako warto klucza plik w naszym obiekcie. 3. Zauwamy, e metoda Kiedy uywa self i init nigdy nie zwraca adnej wartoci.

init

Podczas deniowania metody pewnej klasy, musimy wyranie wstawi self jako pierwszy argument kadej metody, wczajc w to init . Kiedy wywoujemy metod z klasy nadrzdnej, musimy doczy argument self, ale jeli wywoujemy metod z zewntrz, nie okrelamy argumentu self, po prostu go pomijamy. Python automatycznie wstawi odpowiedni referencj za nas. Na pocztku moe si to wydawa troch namieszane, jednak wynika to z pewnych rnic, o ktrych jeszcze nie wiemy 1 . Metoda init jest opcjonalna, ale jeli j deniujemy, musimy pamita o wywoaniu metody init , ktra naley do przodka klasy. W szczeglnoci, jeli potomek chce poszerzy pewne zachowanie przodka, dana metoda podklasy musi w odpowiednim miejscu bezporednio wywoywa metod nalec do klasy nadrzdnej, oczywicie z odpowiednimi argumentami.
1 Wynika to z rnic pomidzy metodami instancji klasy (ang. bound method), a metodami samej klasy (ang. unbound method)

96 Materiay dodatkowe

ROZDZIA 6. OBIEKTY I KLASY

New-style classes opisuje klasy w nowym stylu Python Tutorial opisuje klasy, przestrzenie nazw i dziedziczenie

6.3. TWORZENIE INSTANCJI KLASY

97

6.3

Tworzenie instancji klasy

Tworzenie instancji klasy


Tworzenie instancji klas jest dosy proste. W tym celu wywoujemy klas tak jakby bya funkcj, dodajc odpowiednie argumenty, ktre s okrelone w metodzie init . Zwracan wartoci bdzie zawsze nowo utworzony obiekt. Przykad 5.7 Tworzenie instacji klasy FileInfo >>> import fileinfo >>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3") >>> f.__class__ <class fileinfo.FileInfo> >>> f.__doc__ uprzechowuje metadane pliku >>> f {plik: /music/_singles/kairo.mp3} #(1) #(2) #(3) #(4)

1. Utworzylimy instancj klasy FileInfo (zdeniowan w module fileinfo) i przypisalimy wanie utworzony obiekt do zmiennej f. Skorzystalimy z parametru /music/ singles/kairo.mp3, a ktry bdzie odpowiada argumentowi filename w metodzie init klasy FileInfo. 2. Kada instancja pewnej klasy ma wbudowany atrybut class , ktry jest klas danego obiektu. Programici Javy mog by zaznajomieni z klas Class, ktra posiada metody takie jak getName, czy te getSuperclass, aby pobra metadane o pewnym obiekcie. W Pythonie ten rodzaj metadadanych, jest dostpny bezporednio z obiektu wykorzystujc atrybuty takie jak class , name , czy bases . 3. Moemy pobra notk dokumentacyjn w podobny sposb, jak to czynilimy w przypadku funkcji czy moduu. Wszystkie instancje klasy wspdziel t sam notk dokumentacyjn. 4. Pamitamy, e metoda init przypisuje argument filename do self[plik]? To dobrze, w tym miejscu mamy rezultat tej operacji. Argumenty podawane podczas tworzenia instancji pewnej klasy s wysane do metody init (wyczajc pierwszy argument, self. Python zrobi to za nas). W Pythonie, aby utworzy instancj pewnej klasy, wywoujemy klas tak, jakby to bya zwyka funkcja zwracajca pewn warto. Python nie posiada jakiego kluczowego sowa jakim jest np. operator new w Javie czy C++.

Odmiecanie pamici Jeli tworzenie nowej instancji jest proste, to jej usuwanie jest jeszcze prostsze. W oglnoci nie musimy wyranie zwalnia instancji klasy, poniewa Python robi to automatycznie, gdy wychodzi one poza swj zasig. W Pythonie rzadko wystpuj wycieki pamici.

98

ROZDZIA 6. OBIEKTY I KLASY

Przykad 5.8 Prba zaimplementowania wycieku pamici >>> def leakmem(): ... f = fileinfo.FileInfo(/music/_singles/kairo.mp3) ... >>> for i in range(100): ... leakmem() #(1)

#(2)

1. Za kadym razem, gdy funkcja leakmem jest wywoywana, zostaje utworzona instancja klasy FileInfo, a ta zostaje przypisana do zmiennej f, ktra jest lokaln zmienn wewntrz funkcji. Funkcja ta koczy si bez jakiegokolwiek wyranego zwolnienia pamici zajmowanej przez zmienn f, a wic spodziewalibymy si wycieku pamici, lecz tak nie bdzie. Kiedy funkcja si koczy, lokalna zmienna f wychodzi poza swj zasig. W tym miejscu nie ma wicej adnych referencji do nowej instancji FileInfo, poniewa nigdzie nie przypisywalimy jej do czego innego ni f, tak wic Python zniszczy instancj za nas. 2. Niezalenie od tego, jak wiele razy wywoamy funkcj leakmem, nigdy nie nastpi wyciek pamici, poniewa za kadym razem kiedy to zrobimy, Python bdzie niszczy nowo utworzony obiekt przed wyjciem z funkcji leakmem. Technicznym terminem tego sposobu odmiecania pamici jest zliczanie odwoa (zobacz w Wikipedii). Python przechowuje list referencji do kadej utworzonej instancji. W powyszym przykadzie, mamy tylko jedn referencj do instancji FileInfo zmienn f. Kiedy funkcja si koczy, zmienna f wychodzi poza zasig, wic licznik odwoa zmniejsza si do 0 i Python zniszczy t instancj automatycznie. W poprzednich wersjach Pythona wystpoway sytuacje, gdy zliczanie odwoa zawodzio i Python nie mg wyczyci po nas pamici. Jeli tworzylimy dwie instancje, ktre odwoyway si do siebie nawzajem (np. instancja listy dwukierunkowej 2 , w ktrych kady wze wskazuje na poprzedni i nastpny znajdujcy si w licie), adna instancja nie bya niszczona automatycznie, poniewa Python uwaa (poprawnie), e cigle mamy referencj do kadej instancji. Od Pythona 2.0 mamy dodatkowy sposb odmiecania pamici nazywany po ang. mark-and-sweep (oznacz i zamiataj, zobacz w Wikipedii), dziki ktremu Python w sprytny sposb wykrywa rne wirtualne blokady i poprawnie czyci cykliczne odwoania. Podsumowujc, w jzyku tym mona po prostu zapomnie o zarzdzaniu pamici i pozostawi t spraw Pythonowi. Materiay dodatkowe Python Library Reference omawia wbudowane atrybuty podobne do class

Python Library Reference dokumentuje modu gc, ktry daje niskopoziomow kontrol nad odmiecaniem pamici

2 ang.

double linked list

6.4. KLASA OPAKOWUJCA USERDICT

99

6.4

Klasa opakowujca UserDict

Klasa opakowujca UserDict


Wrcimy na chwil do przeszoci. Za czasw, kiedy nie mona byo dziedziczy wbudowanych typw danych np. sownika, powstaway tzw. klasy opakowujce, ktre peniy te same funkcj, co typy wbudowane, ale mona je byo dziedziczy. Klas opakowujc dla sownika bya klasa UserDict, ktra nadal jest dostpna wraz z nowymi wersjami Pythona. Przygldnicie si implementacji tej klasy moe by dla nas cenn lekcj. Zatem zajrzyjmy do kodu rdowego klasy UserDict, ktry znajduj si w module UserDict. Modu ten z kolei jest przechowywany w katalogu lib instalacji Pythona, a pena nazwa pliku to UserDict.py (nazwa moduu z rozszerzeniem .py). W IDE ActivePython na Windowsie moemy szybko otworzy dowolny modu, ktry znajduje si w ciece do bibliotek, gdy wybierzemy File->Locate... (Ctrl-L).

Przykad 5.9 Denicja klasy UserDict class UserDict: #(1) def init (self, dict=None): #(2) self.data = {} #(3) if dict is not None: self.update(dict)

#(4) (5)

1. Klasa UserDict nie dziedziczy nic z innych klas. Jednak nie patrzmy si na to, pamitajmy, eby zawsze dziedziczy z object (lub innego wbudowanego typu), bo wtedy mamy dostp do dodatkowych moliwoci, ktre daj nam klasy w nowym stylu. 2. Jak pamitamy, metoda init jest wywoywana bezporednio po utworzeniu instancji klasy. Przy tworzeniu instancji klasy UserDict moemy zdeniowa pocztkowe wartoci, poprzez przekazanie sownika w argumencie dict. 3. W Pythonie moemy tworzy atrybuty danych (zwane polami w Javie i PowerBuilderze). Atrybuty to kawaki danych przechowywane w konkretnej instancji klasy (moglibymy je nazwa atrybutami instancji ). W tym przypadku kada instancja klasy UserDict bdzie posiada atrybut data. Aby odwoa si do tego pola z kodu spoza klasy, dodajemy z przodu nazw instancji np. instancja.data; robimy to w identyczny sposb, jak odwoujemy si do funkcji poprzez nazw moduu, w ktrym ta funkcja si znajduje. Aby odwoa si do atrybutu danych z wntrza klasy, uywamy self. Zazwyczaj wszystkie atrybuty s inicjalizowane sensownymi wartociami ju w metodzie init . Jednak nie jest to wymagane, gdy atrybuty, podobnie jak zmienne lokalne, s tworzone, gdy po raz pierwszy przypisze si do nich jak warto. 4. Metoda update powiela zachowanie metody sownika: kopiuje wszystkie klucze i wartoci z jednego sownika do drugiego. Metoda ta nie czyci sownika docelowego (tego, z ktrego wywoalimy metod), ale jeli byy tam ju jakie klucze, to zostan one nadpisane tymi, ktre s w sowniku rdowym; pozostae klucze nie zmieni si. Mylmy o update jak o funkcji czenia, nie kopiowania.

100

ROZDZIA 6. OBIEKTY I KLASY

5. Z tej skadni nie korzystalimy jeszcze w tej ksice. Jest to instrukcja if, ale zamiast wcitego bloku, ktry rozpoczynaby si w nastpnej linii, korzystamy tu z instrukcji, ktra znajduje si w jednej linii, zaraz za dwukropkiem. Jest to cakowicie poprawna, skrtowa skadnia, ktrej moemy uywa, jeli mamy tylko jedn instrukcj w bloku (tak jak pojedyncza instrukcja bez klamer w C++). Moemy albo skorzysta z tej skrtowej skadni, albo tworzy wcite bloki, jednak nie moemy ich ze sob czy w odniesieniu do tego samego bloku kodu. Java i Powerbuilder mog przecia funkcje majce rne listy argumentw, na przykad klasa moe mie kilka metod z tak sam nazw, ale z rn liczb argumentw lub z argumentami rnych typw. Inne jzyki (na przykad PL/SQL) obsuguj nawet przecianie funkcji, ktre rni si jedynie nazw argumentu np. jedna klasa moe mie kilka metod z t sam nazw, t sam liczb argumentw o tych samych typw, ale inaczej nazwanych. Python nie ma adnej z tych moliwoci, nie ma tu w ogle przeciania funkcji. Metody s jednoznacznie deniowane przez ich nazwy i w danej klasie moe by tylko jedna metoda o danej nazwie. Jeli wic mamy w jakiej klasie potomnej metod init , to zawsze zasoni ona metod init klasy rodzicielskiej, nawet jeli klasa pochodna deniuje j z innymi argumentami. Ta uwaga stosuje si do wszystkich metod.

Guido, pierwszy twrca Pythona, tak wyjania zasanianie funkcji: Klasy pochodne mog zasoni metody klas bazowych. Poniewa metody nie maj adnych specjalnych przywilejw, kiedy wywoujemy inne metody tego samego obiektu, moe okaza si, e metoda klasy bazowej wywoujca inn metod zdeniowan w tej samej klasie bazowej wywouje waciwie metod klasy pochodnej, ktra j zasania. (Dla programistw C++: wszystkie metody w Pythonie zachowuj si tak, jakby byy wirtualne.) Jeli dla Ciebie nie ma to sensu, moesz to zignorowa. Po prostu warto byo o tym wspomnie.

Zawsze przypisujmy wartoci pocztkowe wszystkim zmiennym obiektu w jego metodzie init . Oszczdzi to godzin debugowania w poszukiwaniu wyjtkw AtributeError, ktre s spowodowane odwoaniami do niezainicjalizowanych (czyli nieistniejcych) atrybutw.

Przykad 5.10 Standardowe metody klasy UserDict def clear(self): self.data.clear() def copy(self): if self. class is UserDict: return UserDict(self.data) import copy return copy.copy(self) def keys(self): return self.data.keys() def items(self): return self.data.items() def values(self): return self.data.values() #(1) #(2) #(3) #(4) #(5)

6.4. KLASA OPAKOWUJCA USERDICT

101

1. clear jest normaln metod klasy; jest dostpna publicznie i moe by woana przez kogokolwiek w dowolnej chwili. Zauwamy, e w clear, jak we wszystkich metodach klas, pierwszym argumentem jest self. (Pamitajmy, e nie dodajemy self, gdy wywoujemy metod; Python robi to za nas.) Zwrmy uwag na podstawow cech tej klasy opakowujcej: przechowuje ona prawdziwy sownik w atrybucie data i deniuje wszystkie metody wbudowanego sownika, a w kadej z tych metod zwraca wynik identyczny do odpowiedniej metody sownika. (Gdybymy zapomnieli, metoda sownika clear czyci cay sownik kasujc jego wszystkie klucze i wartoci.) 2. Metoda sownika o nazwie copy zwraca nowy sownik, ktry jest dokadn kopi oryginau (majcy takie same pary klucz-warto). Natomiast klasa UserDict nie moe po prostu wywoa self.data.copy, poniewa ta metoda zwraca wbydowany sownik, a my chcemy zwrci now instancj klasy tej samej klasy, jak ma self. 3. Uywamy atrybutu class , eby sprawdzi, czy self jest obiektem klasy UserDict; jeli tak, to jestemy w domu, bo wiemy, jak zrobi kopi UserDict: tworzymy nowy obiekt UserDict i przekazujemy mu sownik wycignity z self.data, a wtedy moemy od razu zwrci nowy obiekt UserDict nie wykonujc nawet instrukcji import copy z nastpnej linii. 4. Jeli self. class nie jest UserDict-em, to self musi by jak podklas UserDict-a, a w takim przypadku ycie wymaga uycia pewnych trikw. UserDict nie wie, jak utworzy dokadn kopi jednego ze swoich potomkw. W tym celu moemy np. znajc atrybuty zdeniowane w podklasie, wykona na nich ptl, podczas ktrej kopiujemy kady z tych atrybutw. Na szczcie istnieje modu, ktry wykonuje dokadnie to samo, nazywa si on copy. Nie bdziemy si tutaj wdawa w szczegy (cho jest to wypany modu, jeli si w niego troch wgbimy). Wystarczy wiedzie, e copy potra kopiowa dowolne obiekty, a tu widzimy, jak moemy z niego skorzysta. 5. Pozostae metody s bezporednimi przekierowaniami, ktre wywouj wbudowane metody z self.data. Od Pythona 2.2 nie korzystamy z klasy UserDict, poniewa od tej wersji moemy ju dziedziczy z wbudowanych typw danych.

Materiay dodatkowe Python Library Reference dokumentuje modu copy

102

ROZDZIA 6. OBIEKTY I KLASY

6.5

Metody specjalne

Pobieranie i ustawianie elementw


Oprcz normalnych metod, jest te kilka (moe kilkanacie) metod specjalnych, ktre mona deniowa w klasach Pythona. Nie wywoujemy ich bezporednio z naszego kodu (jak zwyke metody). Wywouje je za nas Python w okrelonych okolicznociach lub gdy uyjemy okrelonej skadni np. za pomoc metod specjalnych moemy nadpisa operacj dodawania, czy te odejmowania. Z normalnym sownikiem moemy zrobi duo wicej, ni bezporednio wywoa jego metody. Same metody nie wystarcz. Moemy na przykad pobiera i wstawia elementy dziki wykorzystaniu odpowiedniej skadni, bez jawnego wywoywania metod. Moemy tak robi dziki metodom specjalnym. Python odpowiednie elementy skadni przeksztaca na odpowiednie wywoania funkcji specjalnych. Przykad 5.12 Metoda getitem

>>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3") >>> f {plik:/music/_singles/kairo.mp3} >>> f.__getitem__("plik") #(1) /music/_singles/kairo.mp3 >>> f["plik"] #(2) /music/_singles/kairo.mp3 1. Metoda specjalna getitem wyglda do prosto. Ta metoda specjalna pozwala sownikowi zwrci pewn warto na podstawie podanego klucza. A jak ta metod moemy wywoa? Moemy to zrobi bezporednio, ale w praktyce nie robimy w ten sposb, byoby to niezbyt wygodne. Najlepiej pozwoli Pythonowi wywoa t metod za nas. 2. Z takiej skadni korzystamy, by dosta pewn warto ze sownika. W rzeczywistoci Python automatycznie przeksztaca tak skadni na wywoanie metody f. getitem (plik). Wanie dlatego getitem nazywamy metod specjaln: nie tylko moemy j wywoa, ale Python wywouje t metod take za nas, kiedy skorzystamy z odpowiedniej skadni. Istnieje take analogiczna do getitem metoda setitem , ktra zamiast pobiera pewn warto, zmienia dan warto korzystajc z pewnego klucza. Przykad 5.13 Metoda setitem

>>> f {plik:/music/_singles/kairo.mp3} >>> f.__setitem__("gatunek", 31) #(1) >>> f {plik:/music/_singles/kairo.mp3, gatunek:31} >>> f["gatunek"] = 32 #(2) >>> f {plik:/music/_singles/kairo.mp3, gatunek:32}

6.5. METODY SPECJALNE

103

1. Analogicznie do getitem , moemy za pomoc setitem zmieni warto pewnego klucza znajdujcego si w sowniku. Podobnie, jak w przypadku getitem nie musimy jej wywoywa w sposb bezporedni. Python wywoa setitem , jeli tylko uyjemy odpowiedniej skadni. 2. W taki praktyczny sposb korzystamy ze sownika. Za pomoc tej linii kodu Python wywouje w sposb ukryty f. setitem (gatunek, 32). setitem jest metod specjaln, poniewa Python wywouje j za nas, ale cigle jest metod klasy. Kiedy deniujemy klasy, moemy deniowa pewne metody, nawet jeli nadklasa ma ju zdeniowan t metod. W ten sposb nadpisujemy (ang. override) metody nadklas. Tyczy si to take metod specjalnych. Koncepcja ta jest baz caego szkieletu, ktry analizujemy w tym rozdziale. Kady typ plikw moe posiada wasn klas obsugi, ktra wie, w jaki sposb pobra metadane z konkretnego typu plikw. Natychmiast po poznaniu niektrych atrybutw (jak nazwa pliku i pooenie), klasa obsugi bdzie wiedziaa, jak pobra dalsze metaatrybuty automatycznie. Moemy to zrobi poprzez nadpisanie metody setitem , w ktrej sprawdzamy poszczeglne klucze i jeli dany klucz zostanie znaleziony, wykonujemy dodatkowe operacje. Na przykad MP3FileInfo jest podklas FileInfo. Kiedy w MP3FileInfo ustawiamy klucz plik, nie tylko ustawiamy warto samego klucza plik (jak to robi sownik), lecz take zagldamy do samego pliku, odczytujemy tagi MP3 i tworzymy peny zbir kluczy. Poniszy przykad pokazuje, w jaki sposb to dziaa. Przykad 5.14 Nadpisywanie metody def setitem w klasie MP3FileInfo

setitem (self, key, item): #(1) if key == "plik" and item: #(2) self. parse(item) #(3) FileInfo. setitem (self, key, item) #(4)

1. Zwrmy uwag na kolejno i liczb argumentw w setitem . Pierwszym argumentem jest instancja danej klasy (argument self), z ktrej ta metoda zostaa wywoana, nastpnym argumentem jest klucz (argument key), ktry chcemy ustawi, a trzecim jest warto (argument item), ktr chcemy skojarzy z danym kluczem. Kolejno ta jest wana, poniewa Python bdzie wywoywa t metod w takiej kolejnoci i z tak liczb argumentw. (Nazwy argumentw nic nie znacz, wana jest ich ilo i kolejno.) 2. W tym miejscu zawarte jest sedno caej klasy MP3FileInfo: jeli przypisujemy pewn warto do klucza plik, chcemy wykona dodatkowo pewne operacje. 3. Dodatkowe operacje dla klucza plik zawarte s w metodzie parse. Jest to inna metoda klasy MP3FileInfo. Kiedy wywoujemy metod parse uywamy zmiennej self. Gdybymy wywoali samo parse, odnielibymy si do normalnej funkcji, ktra jest zdeniowana poza klas, a tego nie chcemy wykona. Kiedy natomiast wywoamy self. parse bdziemy odnosi si do metody znajdujcej si wewntrz klasy. Nie jest to niczym nowym. W identyczny sposb odnosimy si do atrybutw obiektu. 4. Po wykonaniu tej dodatkowej operacji, chcemy wykona metod nadklasy. Pamitajmy, e Python nigdy nie zrobi tego za nas; musimy zrobi to rcznie.

104

ROZDZIA 6. OBIEKTY I KLASY Zwrmy uwag na to, e odwoujemy si do bezporedniej nadklasy, czyli do FileInfo, chocia on nie posiada adnej metody o nazwie setitem . Jednak wszystko jest w porzdku, poniewa Python bdzie wdrowa po drzewie przodkw jeszcze wyej dopki nie znajdzie klasy, ktra posiada metod, ktr wywoujemy. Tak wic ta linia kodu znajdzie i wywoa metod setitem , ktra jest zdeniowana w samej wbudowanej klasie sownika, w klasie dict.

Kiedy odwoujemy si do danych zawartych w atrybucie instancji, musimy okreli nazw atrybutu np. self.attribute. Podczas wywoywania metody klasy, musimy okreli nazw metody np. self.method.

Przykad 5.15 Ustawianie klucza plik w MP3FileInfo >>> import fileinfo >>> mp3file = fileinfo.MP3FileInfo() #(1) >>> mp3file {plik:None} >>> mp3file["plik"] = "/music/_singles/kairo.mp3" #(2) >>> mp3file {album: Rave Mix, rok: 2000, komentarz: http://mp3.com/DJMARYJANE, utytu\u0142: KAIRO****THE BEST GOA, artysta: ***DJ MARY-JANE***, gatunek: 31, plik: /music/_singles/kairo.mp3} >>> mp3file["plik"] = "/music/_singles/sidewinder.mp3" #(3) >>> mp3file {album: , rok: 2000, nazwa: /music/_singles/sidewinder.mp3, komentarz: http://mp3.com/cynicproject, utytu\u0142: Sidewinder, artysta: The Cynic Project, gatunek: 18} 1. Najpierw tworzymy instancj klasy MP3FileInfo bez podawania nazwy pliku. (Moemy tak zrobi, poniewa argument filename metody init jest opcjonalny.) Poniewa MP3FileInfo nie posiada wasnej metody init , Python idzie wyej po drzewie nadklas i znajduje metod init w klasie FileInfo. Z kolei init w tej klasie rcznie wykonuje metod init w klasie dict, a potem ustawia klucz plik na warto w zmiennej filename, ktry wynosi None, poniewa pominlimy nazw pliku. Ostatecznie mp3file pocztkowo jest sownikiem (waciwie klas potomn sownika) z jednym kluczem plik, ktrego warto wynosi None. 2. Teraz rozpoczyna si prawdziwa zabawa. Ustawiajc klucz plik w mp3file spowoduje wywoanie metody setitem klasy MP3FileInfo (a nie sownika, czyli klasy dict). Z kolei metoda ta zauwaa, e ustawiamy klucz plik z prawdziw wartoci (item jest prawd w kontekcie logicznym) i wywouje self. parse. Chocia jeszcze nie analizowalimy dziaania metody parse, moemy na podstawie wyjcia zobaczy, e ustawia ona kilka innych kluczy jak album, artysta, gatunek, utytu (w unikodzie, bo korzystamy z polskich znakw), rok, czy te comment. 3. Kiedy zmienimy klucz plik, proces ten zostanie wykonany ponownie. Python wywoa setitem , ktry nastpnie wywoa self. parse, a ten ustawi wszystkie inne klucze.

6.6. ZAAWANSOWANE METODY SPECJALNE

105

6.6

Zaawansowane metody specjalne

Zaawansowane metody specjalne


W Pythonie, oprcz getitem i setitem , s jeszcze inne metody specjalne, . Niektre z nich pozwalaj doda funkcjonalno, ktrej si nawet nie spodziewamy. Ten przykad pokazuje inne metody specjalne znanej ju nam klasy UserDict. Przykad 5.16 Inne metody specjalne w klasie UserDict def def repr (self): return repr(self.data) cmp (self, dict): if isinstance(dict, UserDict): return cmp(self.data, dict.data) else: return cmp(self.data, dict) def len (self): return len(self.data) def delitem (self, key): del self.data[key] 1. #(1) #(2)

#(3) #(4)

repr jest metod specjaln, ktra zostanie wywoana, gdy uyjemy repr(obiekt). repr jest wbudowan funkcj Pythona, ktra zwraca reprezentacj danego obiektu w postaci acucha znakw. Dziaa dla dowolnych obiektw, nie tylko obiektw klas. Ju wielokrotnie uywalimy tej funkcji, nawet o tym nie wiedzc. Gdy w oknie interaktywnym wpisujemy nazw zmiennej i naciskamy ENTER, Python uywa repr do wywietlenia wartoci zmiennej. Stwrzmy sownik d z jakimi danymi i wywoajmy repr(d), eby si o tym przekona.

2. Metoda cmp zostanie wywoywana, gdy porwnujemy za pomoc == dwa dowolne obiekty Pythona, nie tylko instancje klas. Aby porwna wbudowane typy danych (i nie tylko), wykorzystywane s pewne reguy np. sowniki s sobie rwny, gdy maj dokadnie takie same pary klucz-warto; acuchy znakw s sobie rwne, gdy maj tak sam dugo i zawieraj taki sam cig znakw. Dla instancji klas moemy zdeniowa metod cmp i zaimplementowa sposb porwnania wasnorcznie, a potem uywa == do porwnywania obiektw klasy. Python wywoa cmp za nas. 3. Metoda len zostanie wywoana, gdy uyjemy len(obiekt). len jest wbudowan funkcj Pythona, ktra zwraca dugo obiektu. Ta metoda dziaa dla dowolnego obiektu, ktry mona uzna za obiekt posiadajcy jak dugo. Dugo acucha znakw jest rwna iloci jego znakw, dugo sownika, to liczba jego kluczy, a dugo listy lub krotki to liczba ich elementw. Dla obiektw klas moesz zdeniowa metod len i samemu zaimplementowa obliczanie dugoci, a nastpnie moemy uywa len(obiekt). Python za nas wywoa metod specjaln len . 4. Metoda delitem zostanie wywoana, gdy uyjemy del obiekt[klucz]. Dziki tej funkcji moemy usuwa pojedyncze elementy ze sownika. Kiedy uyjemy del dla pewnej instancji, Python wywoa metod delitem za nas.

106

ROZDZIA 6. OBIEKTY I KLASY

W Javie sprawdzamy, czy dwie referencje do acucha znakw zajmuj to samo zyczne miejsce w pamici, uywajc str1 == str2. Jest to zwane identycznoci obiektw i w Pythonie zapisywane jest jako str1 is str2. Aby porwna wartoci acuchw znakw w Javie, uylibymy str1.equals(str2). W Pythonie uzyskujemy to samo, gdy napiszemy str1 == str2. Programici Javy, ktrzy zostali nauczeni, e wiat jest lepsze, poniewa == w Javie porwnuje identyczno, zamiast wartoci, mog mie trudnoci z akceptacj braku tego dobra w Pythonie. Metody specjalne sprawiaj, e dowolna klasa moe przechowywa pary kluczwarto w ten sposb, jak to robi sownik. W tym celu deniujemy metod setitem . Kada klasa moe dziaa jak sekwencja, dziki metodzie getitem . Obiekty dowolnej klasy, ktre posiadaj metod cmp , mog by porwnywane przy uyciu ==. A jeli dana klasa reprezentuje co, co ma pewn dugo, nie musimy deniowa metody getLength; po prostu deniujemy metod len i korzystamy z len(obiekt). Inne obiektowo zorientowane jzyki programowania pozwalaj nam zdeniowa tylko zyczny model obiektu (Ten obiekt ma metod getLength). Metody specjalne Pythona pozwalaj zdeniowa logiczny model obiektu (ten obiekt ma dugo). Python posiada wiele innych metod specjalnych. S takie, ktre pozwalaj klasie zachowywa si jak liczby, umoliwiajc dodawanie, odejmowanie i inne operacje arytmetyczne na obiektach. (Klasycznym przykadem jest klasa reprezentujca liczby zespolone z czci rzeczywist i urojon, na ktrych moemy wykonywa wszystkie dziaania). Metoda call pozwala klasie zachowywa si jak funkcja, a dziki czemu moemy bezporednio wywoywa instancj pewnej klasy. Materiay dodatkowe Python Reference Manual dokumentuje wszystkie specjalne metody klasy

6.7. ATRYBUTY KLAS

107

6.7

Atrybuty klas

Atrybuty klas
Wiemy ju, co to s atrybuty, ktre s czci konkretnych obiektw. W Python moemy tworzy te atrybuty klas, czyli zmienne nalece do samej klasy (a nie do instancji tej klasy). Przykad 5.17 Atrybuty klas class MP3FileInfo(FileInfo): u"przechowuje znaczniki ID3v1.0 MP3" tagDataMap = {u"tytu" : ( 3, 33, "artysta" : ( 33, 63, "album" : ( 63, 93, "rok" : ( 93, 97, "komentarz" : ( 97, 126, "gatunek" : (127, 128,

stripnulls), stripnulls), stripnulls), stripnulls), stripnulls), ord)}

>>> import fileinfo >>> fileinfo.MP3FileInfo #(1) <class fileinfo.MP3FileInfo> >>> fileinfo.MP3FileInfo.tagDataMap #(2) {album: (63, 93, <function stripnulls at 0xb7c000d4>), rok: (93, 97, <function stripnulls at 0xb7c000d4>), komentarz: (97, 126, <function stripnulls at 0xb7c000d4>), utytu\u0142: (3, 33, <function stripnulls at 0xb7c000d4>), artysta: (33, 63, <function stripnulls at 0xb7c000d4>), gatunek: (127, 128, <built-in function ord>)} >>> m = fileinfo.MP3FileInfo() #(3) >>> m.tagDataMap {album: (63, 93, <function stripnulls at 0xb7c000d4>), rok: (93, 97, <function stripnulls at 0xb7c000d4>), komentarz: (97, 126, <function stripnulls at 0xb7c000d4>), utytu\u0142: (3, 33, <function stripnulls at 0xb7c000d4>), artysta: (33, 63, <function stripnulls at 0xb7c000d4>), gatunek: (127, 128, <built-in function ord>)} 1. MP3FileInfo jest klas, nie jest instancj klasy. 2. tagDataMap jest atrybutem klasy i jest dostpny ju przed stworzeniem jakiegokolwiek obiektu danej klasy. 3. Atrybuty klas s dostpne na dwa sposoby: poprzez bezporednie odwoanie do klasy lub poprzez jakkolwiek instancjtej klasy. W Javie zarwno zmienne statyczne (w Pythonie zwane atrybutami klas), jak i zmienne obiektw (w Pythonie zwane atrybuty lub atrybutami instancji) s deniowane bezporednio za denicj klasy (jedne ze sowem kluczowym static, a inne bez niego). W Pythonie tylko atrybuty klas s deniowane bezporednio po denicji klasy. Atrybuty instancji deniujemy w metodzie init .

108

ROZDZIA 6. OBIEKTY I KLASY

Atrybuty klas mog by uywane jako stae tych klas (w takim celu korzystamy z nich w klasie MP3FileInfo), ale tak naprawd nie s stae. Mona je zmienia. W Pythonie nie istniej stae. Wszystko moemy zmieni, jeli tylko odpowiednio si postaramy. To efekt jednej z podstawowych zasad Pythona: powinno si zniechca do zego dziaania, ale nie zabrania go. Jeli naprawd chcesz zmieni warto None, moesz to zrobi, ale nie przychod z paczem, kiedy twj kod stanie si niemoliwy do debugowania.

Przykad 5.18 Modykowanie atrybutw klas >>> class counter(object): ... count = 0 ... def __init__(self): ... self.__class__.count += 1 ... >>> counter <class __main__.counter> >>> counter.count 0 >>> c = counter() >>> c.count 1 >>> counter.count 1 >>> d = counter() >>> d.count 2 >>> c.count 2 >>> counter.count 2 1. count jest atrybutem klasy counter. 2. class jest wbudowanym atrybutem kadego obiektu. Jest to referencja do klasy, ktrej obiektem jest self (w tym wypadku do klasy counter). #(1) #(2)

#(3)

#(4)

#(5)

3. Poniewa count jest atrybutem klasy, jest dostpny porzez bezporednie odwoanie do klasy, przed stworzeniem jakiegokolwiek obiektu. 4. Kiedy tworzymy instancj tej klasy, automatycznie zostanie wykonana metoda init , ktra zwiksza atrybut tej klasy o nazwie count o 1. Operacja ta wpywa na klas sam w sobie, a nie tylko na dopiero co stworzony obiekt. 5. Stworzenie drugiego obiektu ponownie zwikszy atrybut count. Zauwamy, e atrybuty klas s wsplne dla klasy i wszystkich jej instancji.

6.8. FUNKCJE PRYWATNE

109

6.8

Funkcje prywatne

Funkcje prywatne
Jak wikszo jzykw programowania, Python posiada koncepcj elementw prywatnych: prywatne funkcje, ktre nie mog by wywoywane spoza moduw w ktrych s zdeniowane prywatne metody klas, ktre nie mog by spoza nich wywoane prywatne atrybuty do ktrych nie ma dostpu spoza klasy Inaczej ni w wikszoci jzykw, to czy element jest prywatny, czy nie, zaley tylko od jego nazwy. Jeeli nazwa funkcji, metody czy atrybutu zaczyna si od dwch podkrele (ale nie koczy si nimi), to wtedy element ten jest prywatny, wszystko inne jest publiczne. Python nie posiada koncepcji chronionych metod (tak jak na przykad w Javie, C++, ktre s dostpnych tylko w tej klasie oraz w klasach z niej dziedziczcych). Metody mog by tylko prywatne (dostpne tylko z wntrza klasy), bd publiczne (dostpne wszdzie). W klasie MP3FileInfo istniej tylko dwie metody: parse oraz setitem . Jak ju zostao przedstawione, setitem jest metod specjaln: zostanie wywoana, gdy skorzystamy ze skadni dostpu do sownika bezporednio na instancji danej klasy. Jednak ta metoda jest publiczna i mona j wywoa bezporednio (nawet z zewntrz moduu fileinfo), jeli istnieje jaki dobry do tego powd. Jednak metoda parse jest prywatna, poniewa posiada dwa podkrelenia na pocztku nazwy. W Pythone wszystkie specjalne metody (takie jak setitem ) oraz wbudowane atrybuty (np. doc ) trzymaj si standardowej konwencji nazewnictwa. Ich nazwy zaczynaj si oraz kocz dwoma podkreleniami. Nie nazywajmy wasnych metod w ten sposb, bo jeli tak bdziemy robi, moemy wprawi w zakopotanie nie tylko siebie, ale i inne osoby czytajce nasz kod pniej.

Przykad 5.19 Prba wywoania metody prywatnej >>> import fileinfo >>> m = fileinfo.MP3FileInfo() >>> m.__parse("/music/_singles/kairo.mp3") #(1) Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: MP3FileInfo object has no attribute __parse 1. Jeli sprbujemy wywoa prywatn metod, Python rzuci nieco mylcy wyjtek, ktry informuje, e metoda nie istnieje. Oczywicie istnieje ona, lecz jest prywatna, wic nie jest moliwe uzyskanie dostpu do niej spoza klasy. cile mwic, metody prywatne s dostpne spoza klasy w ktrej byy zdeniowane, jednak nie w taki prosty sposb. Tak naprawd nic w Pythonie nie jest prywatne, nazwy metod prywatnych s kodowane oraz odkodowane w locie, aby wyglday na niedostpne, gdy korzystamy z ich prawdziwych nazw. Moemy jednak wywoa

110

ROZDZIA 6. OBIEKTY I KLASY metod parse klasy MP3FileInfo przy pomocy nazwy MP3FileInfo. parse. Informacja ta jest dosy interesujca, jednak obiecajmy sobie nigdy nie korzysta z niej w prawdziwym kodzie. Metody prywatne s prywatne nie bez powodu, lecz jak wiele rzeczy w Pythonie, ich prywatno jest kwesti konwencji, nie przymusu.

Materiay dodatkowe Python Tutorial omawia dziaanie prywatnych zmiennych od rodka

6.9. PODSUMOWANIE

111

6.9

Obiekty i klasy - podsumowanie

Podsumowanie
To wszystko, jeli chodzi o triki z obiektami. W rozdziale dwunastym zobaczymy, w jaki sposb wykorzystywa metody specjalne w normalnej aplikacji w ktrej bdziemy zajmowali si tworzeniem porednikw (ang. proxy) dla zdalnych usug sieciowych z uyciem getattr. W nastpnym rozdziale dalej bdziemy uywa kodu programu leinfo.py, aby pozna takie pojcia jak wyjtki, operacje na plikach oraz ptl for. Zanim zanurkujemy w nastpnym rozdziale, upewnijmy si, e nie mamy problemw z: deniowaniem i tworzeniem instancji klasy deniowaniem metody init kiedy s one wywoywane oraz innych metod specjalnych, a take wiem,

dziedziczeniem innych klas (w ten sposb tworzc podklas danej klasy) deniowaniem atrybutw instancji i atrybutw klas, a take rozumiemy rnice midzy nimi deniowaniem prywatnych metod oraz atrybutw

112

ROZDZIA 6. OBIEKTY I KLASY

Rozdzia 7

Wyjtki i operacje na plikach

113

114

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH

7.1

Obsuga wyjtkw

W tym rozdziale zajmiemy si wyjtkami, obiektami pliku, ptlami for oraz moduami os i sys. Jeli uywalimy wyjtkw w innych jzykach programowania, moemy tylko szybko przyjrze si skadni Pythona, ktra odpowiada za obsug wyjtkw, ale powinnimy zwrci uwag na cz, ktra omawia w jaki sposb zarzdza plikami.

Obsuga wyjtkw
Jak wiele innych jzykw programowania, Python obsuguje wyjtki. Przy pomocy blokw try...except przechwytujemy wyjtki, natomiast raise za rzuca wyjtek. Python uywa sw kluczowych try i except do obsugi wyjtkw, natomiast za pomoc sowa raise wyrzuca wyjtki. Java i C++ uywaj sw try i catch do przechwytywania wyjtkw, a sowa throw do ich generacji. Wyjtki s w Pythonie wszdzie. Praktycznie kady modu w bibliotece standardowej Pythona ich uywa. Sam interpreter Pythona rwnie rzuca wyjtki w rnych sytuacjach. Ju wiele razy widzielimy je w tej ksice: Prba uycia nieistniejcego klucza w sowniku rzuci wyjtek KeyError Wyszukiwanie w licie nieistniejcej wartoci rzuci wyjtek ValueError Wywoanie nieistniejcej metody obiektu rzuci wyjtek AttributeError Uycie nieistniejcej zmiennej rzuci wyjtek NameError Mieszanie niezgodnych typw danych spowoduje wyjtek TypeError W kadym z tych przypadkw, gdy uywalimy IDE Pythona i wystpi bd, to zosta wypisany wyjtek (w zalenoci od uytego IDE na przykad na czerwono). Jest to tak zwany nieobsuony wyjtek. Kiedy podczas wykonywania programu zosta rzucony wyjtek, nie byo w nim specjalnego kodu, ktry by go wykry i zaznajomi si z nim, dlatego obsuga tego wyjtku zostaje zrzucona na domylne zachowanie Pythona, ktre z kolei wypisuje troch informacji na temat bdu i koczy prac programu. W przypadku IDE nie jest to wielka przeszkoda, ale wyobramy sobie, co by si stao, gdyby podczas wykonywania waciwego programu nastpiby taki bd, a co z kolei spowodowaoby, e program wyczyby si. Jednak efektem wyjtku nie musi by katastrofa programu. Kiedy wyjtki zostan rzucone, mog zosta obsuone. Czasami przyczyn wystpienia wyjtku jest bd w kodzie (na przykad prba uycia zmiennej, ktra nie istnieje), jednak bardzo czsto wyjtek moemy przewidzie. Jeli otwieramy plik, moe on nie istnie. Jeli czysz si z baz danych, moe ona by niedostpna lub moemy nie mie odpowiednich parametrw dostpu np. hasa. Jeli wiemy, e jaka linia kodu moe wygenerowa wyjtek, powinnimy prbowa j obsuy przy pomocy bloku try...except. Przykad 6.1 Otwieranie nieistniejcego pliku >>> fsock = open("/niemapliku", "r") Traceback (most recent call last): #(1)

7.1. OBSUGA WYJTKW File "<stdin>", line 1, in ? IOError: [Errno 2] No such file or directory: /niemapliku >>> try: ... fsock = open("c:/niemapliku.txt") #(2) ... except IOError: ... print "Plik nie istnieje" ... print "Ta linia zawsze zostanie wypisana" #(3) Plik nie istnieje Ta linia zawsze zostanie wypisana

115

1. Uywajc wbudowanej funkcji open, moemy sprbowa otworzy plik do odczytu (wicej o tej funkcji w nastpnym podrozdziale). Jednak ten plik nie istnieje i dlatego zostanie rzucony wyjtek IOError. Poniewa nie przechwytujemy tego wyjtku Python po prostu wypisuje troch danych pomocnych przy znajdywaniu bdu, a potem zakacza dziaanie programu. 2. Prbujemy otworzy ten sam nieistniejcy plik, jednak tym razem robimy to we wntrzu bloku try...except. Gdy metoda open rzuca wyjtek IOError, jestemy na to przygotowany. Linia except IOError: przechwytuje ten wyjtek i wykonuje blok kodu, ktry w tym wypadku wypisuje bardziej przyjazny opis bdu. 3. Gdy wyjtek zostanie ju obsuony, program wykonuje si dalej w sposb normalny, od pierwszej linii po bloku try...except. Zauwamy, e ta linia zawsze wypisze tekst Ta linia zawsze zostanie wypisana, niezalenie, czy wyjtek wystpi, czy te nie. Jeli naprawd mielibymy taki plik na dysku, to i tak ta linia zostaaby wykonana. Wyjtki mog wydawa si nieprzyjazne (w kocu, jeli nie przechwycimy wyjtku, program zostanie przerwany), jednak pomylmy, z jakiej innej alternatywy moglibymy skorzysta. Czy chcielibymy dosta bezuyteczny obiekt, ktry przestawia nieistniejcy plik? I tak musielibymy sprawdzi jego poprawno w jaki sposb, a jeli bymy tego nie zrobili, to nasz program wykonaby jakie dziwne, nieprzewidywalne operacje, ktrych bymy si nie spodziewali. Nie byaby to wcale dobra zabawa. Z wyjtkami bdy wystpuj natychmiast i moemy je obsugiwa w standardowy sposb u rda problemu. Wykorzystanie wyjtkw do innych celw Jest wiele innych sposobw wykorzystania wyjtkw, oprcz do obsugi bdw. Dobrym przykadem jest importowanie moduw Pythona, sprawdzajc czy nastpi wyjtek. Jeli modu nie istnieje zostanie rzucony wyjtek ImportError. Dziki temu moemy zdeniowa wiele poziomw funkcjonalnoci, ktre zale od moduw dostpnych w czasie wykonania, a dziki temu moemy wspiera rnorodne platformy (kod zaleny od platformy jest podzielony na oddzielne moduy). Moemy te zdeniowa wasne wyjtki, tworzc klas, ktra dziedziczy z wbudowanej klasy Exception, a nastpnie moemy rzuca wyjtki przy pomocy polecenia raise. Moemy zajrze do czci materiay dodatkowe, aby dowiedzie si wicej na ten temat. Nastpny przykad pokazuje, w jaki sposb wykorzystywa wyjtki, aby obsuy funkcjonalno zdeniowan jedynie dla konkretnej platformy. Kod pochodzi z moduu

116

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH

getpass, ktry jest moduem opakowujcym, ktrym umoliwia pobranie hasa od uytkownika. Pobieranie hasa jest cakowicie rne na platformach UNIX, Windows, czy Mac OS, ale kod ten obsuguje wszystkie te rnice. Przykad 6.2 Obsuga funkcjonalnoci zdeniowanej dla konkretnej platformy # Bind the name getpass to the appropriate function try: import termios, TERMIOS #(1) except ImportError: try: import msvcrt #(2) except ImportError: try: from EasyDialogs import AskPassword #(3) except ImportError: getpass = default_getpass #(4) else: #(5) getpass = AskPassword else: getpass = win_getpass else: getpass = unix_getpass 1. termios jest moduem okrelonym dla UNIX-a, ktry dostarcza niskopoziomow kontrol nad terminalem wejcia. Jeli modu ten jest niedostpny (poniewa, nie ma tego na naszym systemie, poniewa system tego nie obsuguje), importowanie nawali, a Python rzuci wyjtek ImportError, ktry przechwycimy. 2. OK, nie mamy termios, wic sprbujmy z msvcrt, ktry jest moduem charakterystycznym dla systemu Windows, a dostarcza on API do wielu przydatnych funkcji dla tego systemu. Jeli to take nie zadziaa, Python rzuci wyjtek ImportError, ktry take przechwycimy. 3. Jeli pierwsze dwa nie zadziaaj, prbujemy zaimportowa funkcj z EasyDialogs, ktra jest w module okrelonym dla Mac OS-a, ktry dostarcza funkcje przeznaczone dla wyskakujcych okien dialogowych rnego typu. I ponownie, jeli modu nie istnieje, Python rzuci wyjtek ImportError, ktry te przechwytujemy. 4. aden z tych moduw, ktre s przeznaczone dla konkretnej platformy, nie s dostpne (jest to moliwe, poniewa Python zosta przeportowany na wiele rnych platform), wic musimy powrci do domylnej funkcji do pobierania hasa (ktra jest zdeniowana gdzie w module getpass). Zauwamy, co robimy: przypisujemy funkcj default getpass do zmiennej getpass. Jeli czytalimy ocjaln dokumentacj getpass, mwi ona, e modu getpass deniuje funkcj getpass. Wykonuje to poprzez powizanie getpass z odpowiedni funkcj, ktra zaley od naszej platformy. Kiedy wywoujemy funkcj getpass, tak naprawd wywoujemy funkcj okrelon dla konkretnej platformy, ktr okreli za nas powyszy kod. Nie musimy si martwi, na jakiej platformie uruchamiamy nasz kod wywoujemy tylko getpass, a funkcja ta wykona zawsze odpowiedni czynno.

7.1. OBSUGA WYJTKW

117

5. Blok try...except, podobnie jak instrukcja if, moe posiada klauzule else. Jeli aden wyjtek nie zostanie rzucony podczas wykonywania bloku try, spowoduje to wywoanie klauzuli else. W tym przypadku oznacza to, e import from EasyDialogs import AskPassword zadziaa, a wic moemy powiza getpass z funkcj AskPassword. Kady inny blok try...except w przedstawionym kodzie posiada podobn klauzul else, ktra pozwala, gdy zostanie zaimportowany dziaajcy modu, przypisa do getpass odpowiedni funkcj. Materiay dodatkowe Python Tutorial mwi na temat deniowania, rzucania wasnych wyjtkw i jednoczesnej obsugi wielu wyjtkw Python Library Reference opisuje wszystkie wbudowane wyjtki Python Library Reference dokumentuje modu getpass. Python Library Reference dokumentuje modu traceback, ktry zapewnia niskopoziomowy dostp do atrybutw wyjtkw, po tym, jak wyjtek zostanie rzucony. Python Reference Manual omawia bardziej technicznie blok try...except.

118

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH

7.2

Praca na plikach

Praca z obiektami plikw


Python posiada wbudowan funkcj open, suc do otwierania plikw z dysku. open zwraca obiekt pliku posiadajcy metody i atrybuty, dziki ktrym moemy dosta si do pliku i wykonywa na nim pewne operacje. Przykad 6.3 Otwieranie plikw >>> f = open("/muzyka/_single/kairo.mp3", "rb") >>> f <open file /muzyka/_single/kairo.mp3, mode rb at 010E3988> >>> f.mode rb >>> f.name /muzyka/_single/kairo.mp3 #(1) #(2) #(3) #(4)

1. Metoda open przyjmuje do trzech argumentw: nazw pliku, tryb i argument buforowania. Tylko pierwszy z nich, nazwa pliku, jest wymagany; pozostae dwa s opcjonalne. Jeli nie s podane, plik zostanie otwarty do odczytu w trybie tekstowym. Tutaj otworzylimy plik do odczytu w trybie binarnym. (print open. doc da nam wietne objanienie wszystkich moliwych trybw.) 2. Metoda open zwraca obiekt (w tym momencie nie powinno to ju by zaskoczeniem). Obiekt pliku ma kilka uytecznych atrybutw. 3. Atrybut mode obiektu pliku mwi nam, w jakim trybie zosta on otwarty. 4. Atrybut name zwraca ciek do pliku, ktry jest dostpny z tego obiektu. Czytanie z pliku Otworzywszy plik, bdziemy chcieli odczyta z niego informacje, tak jak pokazano to w nastpnym przykadzie. Przykad 6.4 Czytanie pliku >>> f <open file /muzyka/_single/kairo.mp3, mode rb at 010E3988> >>> f.tell() 0 >>> f.seek(-128, 2) >>> f.tell() 7542909 >>> tagData = f.read(128) >>> tagData TAGKAIRO****THE BEST GOA ***DJ MARY-JANE*** Rave Mix 2000http://mp3.com/DJMARYJANE >>> f.tell() 7543037

#(1) #(2) #(3) #(4)

\037 #(5)

7.2. PRACA NA PLIKACH

119

1. Obiekt pliku przechowuje stan otwartego pliku. Metoda tell zwraca aktualn pozycj w otwartym pliku. Z uwagi na to, e nie robilimy jeszcze nic z tym plikiem, aktualna pozycja to 0, czyli pocztek pliku. 2. Metoda seek obiektu pliku suy do poruszania si po otwartym pliku. Jej drugi argument okrela znaczenie pierwszego argument; jeli argument drugi wynosi 0, oznacza to, e pierwszy argument odnosi si do pozycji bezwzgldnej (czyli liczc od pocztku pliku), 1 oznacza przeskok do innej pozycji wzgldem pozycji aktualnej (liczc od pozycji aktualnej), 2 oznacza przeskok do danej pozycji wzgldem koca pliku. Jako e tagi MP3, o ktre nam chodzi, przechowywane s na kocu pliku, korzystamy z opcji 2 i przeskakujemy do pozycji oddalonej o 128 bajtw od koca pliku. 3. Metoda tell potwierdza, e rzeczywicie zmienilimy pozycj pliku. 4. Metoda read czyta okrelon liczb bajtw z otwartego pliku i zwraca dane w postaci acucha znakw, ktre zostay odczytane. Opcjonalny argument okrela maksymaln liczb bajtw do odczytu. Jeli nie zostanie podany argument, read bdzie czyta do koca pliku. (W tym przypadku moglibymy uy samego read(), poniewa wiemy dokadnie w jakiej pozycji w pliku jestemy i w rzeczywistoci odczytujemy ostanie 128 bajtw.) Odczytane dane przypisujemy do zmiennej tagData, a bieca pozycja zostaje uaktualniana na podstawie iloci odczytanych bajtw. 5. Metoda tell potwierdza, e zmienia si bieca pozycja. Jeli pokusimy si o wykonanie obliczenia, zauwaymy, e po odczytaniu 128 bajtw aktualna pozycja wzrosa o 128. Zamykanie pliku Otwarte pliki zajmuj zasoby systemu, a inne aplikacje czasami mog nie mie do nich dostpu (zaley to od trybu otwarcia pliku), dlatego bardzo wane jest zamykanie plikw tak szybko, jak tylko skoczymy na nich prac. Przykad 6.5 Zamykanie pliku >>> f <open file /muzyka/_single/kairo.mp3, mode rb at 010E3988> >>> f.closed False >>> f.close() >>> f <closed file /muzyka/_single/kairo.mp3, mode rb at 010E3988> >>> f.closed True >>> f.seek(0) Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: I/O operation on closed file >>> f.tell() Traceback (most recent call last): File "<stdin>", line 1, in ?

#(1) #(2)

#(3) #(4)

120

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH

ValueError: I/O operation on closed file >>> f.read() Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: I/O operation on closed file >>> f.close()

#(5)

1. Atrybut closed obiektu pliku mwi, czy plik jest otwarty, czy te nie. W tym przypadku plik jeszcze jest otwarty (closed jest rwne False). 2. Aby zamkn plik naley wywoa metod close obiektu pliku. Zwalnia to blokad, ktra naoona bya na plik (jeli bya naoona), oczyszcza buforowane dane (jeli jakiekolwiek dane w nim wystpuj), ktre system nie zdy jeszcze rzeczywicie zapisa, a nastpnie zwalnia zasoby. 3. Atrybut closed potwierdza, e plik jest zamknity. 4. To e plik zosta zamknity, nie oznacza od razu, e obiekt przestaje istnie. Zmienna f bdzie istnie pki nie wyjdzie poza swj zasig, lub nie zostanie skasowana rcznie. Aczkolwiek adna z metod sucych do operowania na otwartym pliku, nie bdzie dziaa od momentu jego zamknicia; wszystkie rzucaj wyjtek. 5. Wywoanie close na pliku uprzednio zamknitym nie zwraca wyjtku; w przypadku bdu cicho sobie z nim radzi. Bdy wejcia/wyjcia Zrozumienie kodu leinfo.py z poprzedniego rozdziau, nie powinno ju by problemem. Kolejny przykad pokazuje, jak bezpiecznie otwiera i zamyka pliki oraz jak naleycie obchodzi si z bdami. Przykad 6.6 Obiekty pliku w MP3FileInfo try: fsock = open(filename, "rb", 0) try: fsock.seek(-128, 2) tagdata = fsock.read(128) finally: fsock.close() #(1) #(2) #(3) #(4) #(5)

except IOError: pass

#(6)

1. Poniewa otwieranie pliku i czytanie z niego jest ryzykowne, a take operacje te mog rzuci wyjtek, cay ten kod jest ujty w blok try...except. (Hej, czy zestandaryzowane wcicia nie s wietne? To moment, w ktrym zaczynasz je docenia.) 2. Funkcja open moe rzuci wyjtek IOError. (Plik moe nie istnie.)

7.2. PRACA NA PLIKACH

121

3. Funkcja seek moe rzuci wyjtek IOError. (Plik moe by mniejszy ni 128 bajtw.) 4. Funkcja read moe rzuci wyjtek IOError. (By moe dysk posiada uszkodzony sektor, albo plik znajduje si na dysku sieciowym, a sie wanie przestaa dziaa.) 5. Nowo: blok try...finally. Nawet po udanym otworzeniu pliku przez open chcemy by cakowicie pewni, e zostanie on zamknity niezalenie od tego, czy metody seek i read rzuc wyjtki, czy te nie. Wanie do takich rzeczy suy blok try...finally: kod z bloku finally zostanie zawsze wykonany, nawet jeli jaka instrukcja bloku try rzuci wyjtek. Naley o tym myle jak o kodzie wykonywanym na zakoczenie operacji, niezalenie od tego co dziao si wczeniej. 6. Nareszcie poradzimy sobie z wyjtkiem IOError. Moe to by wyjtek wywoany przez ktrkolwiek z funkcji open, seek, czy read. Tym razem nie jest to dla nas istotne, gdy jedyn rzecz, ktr zrobimy to zignorowanie tego wyjtku i kontynuowanie dalszej pracy programu. (Pamitajmy, pass jest wyraeniem Pythona, ktre nic nie robi.) Takie co jest cakowicie dozwolone; to e przechwycilimy dany wyjtek, nie oznacza, e musimy z nim cokolwiek robi. Wyjtek zostanie potraktowany tak, jakby zosta obsuony, a kod bdzie normalnie kontynuowany od nastpnej linijki kodu po bloku try...except. Zapisywanie do pliku Jak mona przypuszcza, istnieje rwnie moliwo zapisywania danych do plikw w sposb bardzo podobny do odczytywania. Wyrniamy dwa podstawowe tryby otwierania plikw: w trybie append, w ktrym dane bd dodawane na kocu pliku w trybie write, w ktrym plik zostanie nadpisany. Oba tryby, jeli plik nie bdzie istnia, utworz go automatycznie, dlatego nie ma potrzeby na kune dziaania typu jeli dany plik jeszcze nie istnieje, utwrz nowy pusty plik, aby mc go otworzy po raz pierwszy. Po prostu otwieramy plik i zaczynamy do niego zapisywa dane. Przykad 6.7 Pisanie do pliku >>> logfile = open(test.log, w) >>> logfile.write(udany test) >>> logfile.close() >>> print open(test.log).read() udany test >>> logfile = open(test.log, a) >>> logfile.write(linia 2) >>> logfile.close() >>> print open(test.log).read() udany testlinia 2 #(1) #(2) #(3) #(4)

#(5)

122

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH

1. Zaczynamy odwanie: tworzymy nowy lub nadpisujemy istniejcy plik test.log, a nastpnie otwieramy do zapisu. (Drugi argument "w" oznacza otwieranie pliku do zapisu.) Tak, jest to dokadnie tak niebezpieczne, jak brzmi. Miejmy nadziej, e poprzednia zawarto pliku nie bya istota, bo ju jej nie ma. 2. Dane do nowo otwartego pliku dodajemy za pomoc metody write obiektu zwrconego przez open. 3. Ten jednowierszowiec otwiera plik, czyta jego zawarto i drukuje na ekran. 4. Przypadkiem wiemy, e test.log istnieje (w kocu wanie skoczylimy do niego pisa), wic moemy go otworzy i dodawa dane. (Argument a" oznacza otwieranie pliku w trybie dodawania danych na koniec pliku.) Waciwie moglibymy to zrobi nawet wtedy, gdyby plik nie istnia, poniewa otworzenie pliku w trybie a" spowoduje jego powstanie, jeli bdzie to potrzebne. Otworzenie w trybie a" nigdy nie uszkodzi aktualnej zawartoci pliku. 5. Jak wida, zarwno pierwotnie zapisane, jak i dopisane dane, aktualnie znajduj si w pliku test.log. Naley zauway, e znaki koca linii nie s uwzgldnione. Jako e nie zapisywalimy znakw koca linii w adnym z przypadkw, plik ich nie zawiera. Znaki koca linii moemy zapisa za pomoc symbolu "\n". Z uwagi na fakt, i tego nie zrobilimy, cao danych w pliku wyldowaa w jednej linijce. Materiay dodatkowe Python Tutorial opisuje, jak czyta i zapisywa pliki, w tym take, jak czyta pliki linia po linii lub jak wczyta wszystkie linie na raz do listy Python Knowledge Base odpowiada na najczciej zadawane pytania dotyczce plikw Python Library Reference omawia wszystkie metody obiektu pliku.

7.3. PTLA FOR

123

7.3

Ptla for

Ptla for
Podobnie jak wiele innych jzykw, Python posiada ptl for. Jedynym powodem, dla ktrego nie widzielimy tej ptli wczeniej jest to, e Python posiada tyle innych uytecznych rzeczy, e nie potrzebujemy jej a tak czsto. Wiele jzykw programowania nie posiada typu danych o takich moliwociach, jakie daje lista w Pythonie, dlatego te w tych jzykach trzeba wykonywa duo manualnej pracy, trzeba okrela pocztek, koniec i krok, aby przej zakres liczb cakowitych, znakw lub innych iteracyjnych jednostek. Jednak pythonowa ptla for, przechodzi ca list w identyczny sposb, jak ma to miejsce w wyraeniach listowych. Przykad 6.8 Wprowadzenie do ptli for >>> li = [a, b, e] >>> for s in li: #(1) ... print s #(2) a b e >>> print "\n".join(li) #(3) a b e 1. Skadnia ptli for jest bardzo podobna do skadni wyraenia listowego. li jest list, a zmienna s bdzie przyjmowa kolejne wartoci elementw tej listy podczas wykonywania ptli, zaczynajc od elementu pierwszego. 2. Podobnie jak wyraenie if i inne bloki tworzone za pomoc wci, ptla for moe posiada dowoln liczb linii kodu. 3. To gwna przyczyna, dla ktrej jeszcze nie widzielimy ptli for. Po prostu jej nie potrzebowalimy. Jest to zadziwiajce, jak czsto trzeba wykorzystywa ptle for w innych jzykach, podczas gdy tak naprawd potrzebujemy metody join, czy te wyraenia listowego. Wykonanie normalnej (zgodnie ze standardami Visual Basica), licznikowej ptli for jest take bardzo proste. Przykad 6.9 Prosty licznik >>> for i in range(5): #(1) ... print i 0 1 2 3 4 >>> li = [a, b, c, d, e] >>> for i in range(len(li)): #(2)

124 ... a b c d e print li[i]

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH

1. Jak zobaczylimy w przykadzie 3.23, Przypisywanie kolejnych wartoci, funkcja range tworzy list liczb cakowitych, a t list bdziemy chcieli przej za pomoc ptli. Powyszy kod by moe wyglda troch dziwacznie, lecz bywa przydatny do tworzenia ptli licznikowej. 2. Nigdy tego nie rbmy. Jest to visualbasicowy styl mylenia. Skoczmy z nim. Powinno si iterowa list, jak pokazano to w poprzednim przykadzie. Ptle for nie zostay stworzone do tworzenia prostych ptli licznikowych. Su one raczej do przechodzenia po wszystkich elementach w danym obiekcie. Poniej pokazano przykad, jak mona iterowa po sowniku za pomoc ptli for: Przykad 6.10 Iterowanie po elementach sownika >>> import os >>> for k, v in os.environ.items(): #(1) (2) ... print "%s=%s" % (k, v) USERPROFILE=C:\Documents and Settings\mpilgrim OS=Windows_NT COMPUTERNAME=MPILGRIM USERNAME=mpilgrim [...ciach...] >>> print "\n".join(["%s=%s" % (k, v) ... for k, v in os.environ.items()]) #(3) USERPROFILE=C:\Documents and Settings\mpilgrim OS=Windows_NT COMPUTERNAME=MPILGRIM USERNAME=mpilgrim [...ciach...] 1. os.environ jest sownikiem zmiennych rodowiskowych zdeniowanych w systemie operacyjnym. W Windowsie mamy tutaj zmienne uytkownika i systemu dostpne z MS-DOS-a. W Uniksie mamy tu zmienne wyeksportowane w twojej powoce przez skrypty startowe. W systemie Mac OS nie ma czego takiego jak zmienne rodowiskowe, wic sownik ten jest pusty. 2. os.environ.items() zwraca list krotek w postaci [(klucz1, wartosc1), (klucz2, wartosc2), ...]. Ptla for iteruje po tej licie. W pierwszym przebiegu ptli, przypisuje ona warto klucz1 do zmiennej k, a warto wartosc1 do v, wic k = USERPROFILE, a v = C:\Documents and Settings\mpilgrim. W drugim przebiegu ptli, k przyjmuje warto drugiego klucza, czyli OS, a v bierze odpowiadajc temu kluczowi warto, czyli Windows NT.

7.3. PTLA FOR

125

3. Za pomoc wielozmiennego przypisania i wyrae listowych, moemy zastpi ca ptle for jednym wyraeniem. Z ktrej metody bdziemy korzysta w kodzie, jest kwesti stylu pisania. Niektrym si to moe podoba, poniewa za pomoc wyraenia listowego jasno stwierdzamy, e to co robimy to odwzorowywanie sownika na list, a nastpnie czymy otrzyman list w jeden napis. Inni z kolei preferuj pisanie z uyciem ptli for. Otrzymane wyjcie programu jest identyczne w obydwu przypadkach, jakkolwiek wersja w tym przykadzie jest nieco szybsza, poniewa tutaj tylko raz wykorzystujemy instrukcj print. Spjrzmy teraz na ptl for w MP3FileInfo z przykadu leinfo.py wprowadzonego w rozdziale 5. Przykad 6.11 Ptla for w MP3FileInfo tagDataMap = {u"tytu" : ( 3, 33, stripnulls), "artysta" : ( 33, 63, stripnulls), "album" : ( 63, 93, stripnulls), "rok" : ( 93, 97, stripnulls), "komentarz" : ( 97, 126, stripnulls), "gatunek" : (127, 128, ord)} [...] if tagdata[:3] == TAG: for tag, (start, end, parseFunc) in self.tagDataMap.items(): self[tag] = parseFunc(tagdata[start:end]) 1. tagDataMap jest atrybutem klasy, ktry deniuje tagi, jakie bdziemy szukali w pliku MP3. Tagi s przechowywane w polach o ustalonej dugoci. Poniewa czytamy ostatnie 128 bajtw pliku, bajty od 3 do 32 z przeczytanych danych s zawsze tytuem utworu, bajty od 33 do 62 s zawsze nazw artysty, od 63 do 92 mamy nazw albumu itd. Zauwamy, e tagDataMap jest sownikiem krotek, a kada krotka przechowuje dwie liczby cakowite i jedn referencj do funkcji. 2. Wyglda to skomplikowanie, jednak takie nie jest. Struktura zmiennych w ptli for odpowiada strukturze elementw listy zwrconej poprzez metod items. Pamitamy, e items zwraca list krotek w formie (klucz, wartosc). Jeli pierwszym elementem tej listy jest (u"tytu", (3, 33, <function stripnulls at 0xb7c91f7c>)), tak wic podczas pierwszego przebiegu ptli, tag jest rwne u"tytu", start przyjmuje warto 3, end warto 33, a parseFunc zawiera funkcj stripnulls. 3. Kiedy ju wydobdziemy wszystkie parametry tagw pliku MP3, zapisanie danych odnoszcych si do okrelonych tagw bdzie proste. W tym celu wycinamy napis tagdata od wartoci w zmiennej start do wartoci w zmiennej end, aby pobra aktualne dane dla tego tagu, a nastpnie wywoujemy funkcj parseFunc, aby przetworzy te dane, a potem przypisujemy zwrcon warto do klucza tag w sowniku self (cilej, self jest instancj podklasy sownika). Po przejciu wszystkich elementw w tagDataMap, self bdzie przechowywa wartoci dla wszystkich tagw, a my bdziemy mogli si dowiedzie o utworze, co tylko bdziemy chcieli.

#(1)

#(2) #(3)

126

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH

7.4

Korzystanie z sys.modules

Korzystanie z sys.modules
Moduy, podobnie jak wszystko inne w Pythonie, s obiektami. Jeli wczeniej zaimportowalimy pewien modu, moemy pobra do niego referencj za porednictwem globalnego sownika sys.modules. Przykad 6.12 Wprowadzenie do sys.modules >>> import sys #(1) >>> print \n.join(sys.modules.keys()) #(2) win32api os.path os exceptions __main__ ntpath nt sys __builtin__ site signal UserDict stat 1. Modu sys zawiera informacje dotyczce systemu jak np. wersj uruchomionego Pythona (sys.version lub sys.version info) i opcje dotyczce systemu np. maksymalna dozwolona gboko rekurencji (sys.getrecursionlimit() i sys.setrecursionlimit()). 2. sys.modules jest sownikiem zawierajcym wszystkie moduy, ktre zostay zaimportowane od czasu startu Pythona. W sowniku tym kluczem jest nazwa danego moduu, a wartoci jest obiekt tego moduu. Dodajmy, e jest tu wicej moduw ni nasz program zaimportowa. Python wczytuje niektre moduy podczas startu, a jeli uywasz IDE Pythona , sys.modules zawiera wszystkie moduy zaimportowane przez wszystkie programy uruchomione wewntrz IDE. Poniszy przykad pokazuje, jak wykorzystywa sys.modules. Przykad 6.13 Korzystanie z sys.modules >>> import fileinfo #(1) >>> print \n.join(sys.modules.keys()) win32api os.path os fileinfo exceptions __main__ ntpath

7.4. KORZYSTANIE Z SYS.MODULES nt sys __builtin__ site signal UserDict stat >>> fileinfo <module fileinfo from fileinfo.pyc> >>> sys.modules["fileinfo"] #(2) <module fileinfo from fileinfo.pyc>

127

1. Podczas importowania nowych moduw, zostaj one dodane do sys.modules. To tumaczy dlaczego ponowne zaimportowanie tego samego moduu jest bardzo szybkie. Ot Python aktualnie posiada wczytany i zapamitany modu w sys.modules, wic za drugim razem kiedy importujemy modu, Python spoglda po prostu tylko do tego sownika. 2. Podajc nazw wczeniej zaimportowanego moduu (w postaci acucha znakw), moemy pobra referencj do samego moduu poprzez bezporednie wykorzystanie sownika sys.modules. Kolejny przykad pokazuje, jak wykorzystywa atrybut klasy module razem ze sownikiem sys.modules, aby pobra referencj do moduu, w ktrym ta klasa jest zdeniowana. Przykad 6.14 Atrybut klasy module

>>> from fileinfo import MP3FileInfo >>> MP3FileInfo.__module__ fileinfo >>> sys.modules[MP3FileInfo.__module__] <module fileinfo from fileinfo.pyc>

#(1) #(2)

1. Kada klasa Pythona posiada wbudowany atrybut klasy, jakim jest module , a ktry przechowuje nazw moduu, w ktrym dana klasa jest zdeniowana. 2. czc to z sys.modules, moemy pobra referencj do moduu, w ktrym ta klasa jest zdeniowana. Teraz ju jeste przygotowany do tego, aby zobaczy w jaki sposb sys.modules jest wykorzystywany w leinfo.py, czyli przykadowym programie wykorzystanym w rozdziale 5. Poniszy przykad przedstawia fragment kodu. Przykad 6.15 sys.modules w leinfo.py def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]): u"zwraca klas metadanych pliku na podstawie podanego rozszerzenia" subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:] return hasattr(module, subclass) and getattr(module, subclass) or FileInfo #(1) #(2) #(3)

128

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH

1. Jest to funkcja z dwoma argumentami. Argument filename jest wymagany, ale module jest argumentem opcjonalnym i domylnie wskazuje na modu, ktry zawiera klas FileInfo. Wyglda to nieefektywnie, poniewa moe si wydawa, e Python wykonuje wyraenie sys.modules za kadym razem, gdy funkcja zostaje wywoana. Tak na prawd, Python wykonuje domylne wyraenia tylko raz, podczas pierwszego zaimportowania moduu. Jak zobaczymy pniej, nigdy nie wywoamy tej funkcji z argumentem module, wic argument module suy nam raczej jako staa na poziomie tej funkcji. 2. Funkcji tej przyjrzymy si pniej, po tym, jak zanurkujemy w module os. Na razie zaufaj, e linia ta sprawia, e subclass przechowuje nazw klasy np. MP3FileInfo. 3. Ju wiemy, e funkcja getattr zwraca nam referencje do obiektu poprzez nazw. hasattr jest funkcj uzupeniajc, ktra sprawdza, czy obiekt posiada okrelony atrybut. W tym przypadku sprawdzamy, czy modu posiada okrelon klas (funkcja ta dziaa na dowolnym obiekcie i dowolnym atrybucie, podobnie jak getattr). Ten kod moemy na jzyk polski przetumaczy w ten sposb: Jeli ten modu posiada klas o nazwie zawartej w zmiennej subclass, to j zwr, w przeciwnym wypadku zwr klas FileInfo. Materiay dodatkowe Python Tutorial omawia dokadnie, kiedy i jak domylne argumenty s wyznaczane Python Library Reference dokumentuje modu sys

7.5. PRACA Z KATALOGAMI

129

7.5

Praca z katalogami

Praca z katalogami
Modu os.path zawiera kilka funkcji sucych do manipulacji plikami i katalogami (w systemie Windows nazywanymi folderami). Przyjrzymy si teraz obsudze cieek i odczytywaniu zawartoci katalogw. Przykad 6.16 Tworzenie cieek do plikw >>> import os >>> os.path.join("c:\\music\\ap\\", "mahadeva.mp3") #(1) (2) c:\\music\\ap\\mahadeva.mp3 >>> os.path.join("c:\\music\\ap", "mahadeva.mp3") #(3) c:\\music\\ap\\mahadeva.mp3 >>> os.path.expanduser("~") #(4) c:\\Documents and Settings\\mpilgrim\\My Documents >>> os.path.join(os.path.expanduser("~"), "Python") #(5) c:\\Documents and Settings\\mpilgrim\\My Documents\\Python 1. os.path jest referencj do moduu, a ten modu zaley od platformy z jakiej korzystamy. Tak jak getpass niweluje rnice midzy platformami ustawiajc getpass na funkcj odpowiedni dla naszego systemu, tak os ustawia path na modu specyczny dla konkretnej platformy. 2. Funkcja join moduu os.path tworzy ciek dostpu do pliku z jednej lub kilku cieek czciowych. W tym przypadku po prostu czy dwa acuchy znakw. (Zauwamy, e w Windowsie musimy uywa podwjnych ukonikw.) 3. W tym, troch bardziej skomplikowanym, przypadku, join dopisze dodatkowy ukonik do cieki przed doczeniem do niej nazwy pliku. Nie musimy pisa maej gupiej funkcji addSlashIfNecessary, poniewa mdrzy ludzie zrobili ju to za nas. 4. expanduser rozwinie w ciece znak na ciek katalogu domowego aktualnie zalogowanego uytkownika. Ta funkcja dziaa w kadym systemie, w ktrym uytkownicy maj swoje katalogi domowe, midzy innymi w systemach Windows, UNIX i Mac OS X, ale w systemie Mac OS nie otrzymujemy adnych efektw. 5. Uywajc tych technik, moemy atwo tworzy cieki do plikw i katalogw wewntrz katalogu domowego. Przykad 6.17 Rozdzielanie cieek >>> os.path.split("c:\\music\\ap\\mahadeva.mp3") (c:\\music\\ap, mahadeva.mp3) >>> (filepath, filename) = os.path.split("c:\\music\\ap\\mahadeva.mp3") >>> filepath c:\\music\\ap >>> filename mahadeva.mp3 >>> (shortname, extension) = os.path.splitext(filename) #(1) #(2) #(3) #(4) #(5)

130 >>> shortname mahadeva >>> extension .mp3

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH

1. Funkcja split dzieli pen ciek i zwraca list, ktra zawiera ciek do katalogu i nazw pliku. Pamitasz, jak mwilimy, e mona uywa wielozmiennego przypisania do zwracania kilku wartoci z funkcji? split jest tak wanie funkcj. 2. Przypisujesz wynik dziaania funkcji split do krotki dwch zmiennych. Kada zmienna bdzie teraz zawiera warto odpowiedniego elementu krotki zwrconej przez funkcj split. 3. Pierwsza zmienna, filepath, zawiera pierwszy element zwrconej listy ciek pliku. 4. Druga zmienna, filename, zawiera drugi element listy nazw pliku. 5. Modu os.path zawiera te funkcj splitext, ktra zwraca krotk zawierajc waciw nazw pliku i jego rozszerzenie. Uywamy tej samej techniki, co poprzednio, do przypisania kadej czci do osobnej zmiennej. Przykad 6.18 Wywietlanie zawartoci katalogu >>> os.listdir("c:\\music\\_singles\\") #(1) [a_time_long_forgotten_con.mp3, hellraiser.mp3, kairo.mp3, long_way_home1.mp3, sidewinder.mp3, spinning.mp3] >>> dirname = "c:\\" >>> os.listdir(dirname) #(2) [AUTOEXEC.BAT, boot.ini, CONFIG.SYS, cygwin, docbook, Documents and Settings, Incoming, Inetpub, IO.SYS, MSDOS.SYS, Music, NTDETECT.COM, ntldr, pagefile.sys, Program Files, Python20, RECYCLER, System Volume Information, TEMP, WINNT] >>> [f for f in os.listdir(dirname) ... if os.path.isfile(os.path.join(dirname, f))] #(3) [AUTOEXEC.BAT, boot.ini, CONFIG.SYS, IO.SYS, MSDOS.SYS, NTDETECT.COM, ntldr, pagefile.sys] >>> [f for f in os.listdir(dirname) ... if os.path.isdir(os.path.join(dirname, f))] #(4) [cygwin, docbook, Documents and Settings, Incoming, Inetpub, Music, Program Files, Python20, RECYCLER, System Volume Information, TEMP, WINNT] 1. Funkcja listdir pobiera ciek do katalogu i zwraca list jego zawartoci. 2. listdir zwraca zarwno pliki jak i katalogi, bez wskazania ktre s ktrymi. 3. Moemy uy ltrowania listy i funkcji isfile moduu os.path, aby oddzieli pliki od katalogw. isfile przyjmuje ciek do pliku i zwraca True, jeli reprezentuje ona plik albo False w innym przypadku. W przykadzie uywamy

7.5. PRACA Z KATALOGAMI

131

os.path.join, aby uzyska pen ciek, ale isfile pracuje te ze ciekami wzgldnymi wobec biecego katalogu. Moemy uy os.getcwd() aby pobra biecy katalog. 4. os.path zawiera te funkcj isdir, ktra zwraca True, jeli cieka reprezentuje katalog i False w innym przypadku. Moemy jej uy do uzyskania listy podkatalogw. Przykad 6.19 Listowanie zawartoci katalogu w leinfo.py def listDirectory(directory, fileExtList): u"zwraca list obiektw zawierajcych metadane dla plikw o podanych rozszerzeniach" fileList = [os.path.normcase(f) for f in os.listdir(directory)] #(1) (2) fileList = [os.path.join(directory, f) for f in fileList if os.path.splitext(f)[1] in fileExtList] #(3) (4) (5) 1. os.listdir(directory) zwraca list wszystkich plikw i podkatalogw w katalogu directory. 2. Iterujc po licie z uyciem zmiennej f, wykorzystujemy os.path.normcase(f), aby znormalizowa wielko liter zgodnie z domyln wielkoci liter w systemem operacyjnym. Funkcja normcase jest uyteczn, prost funkcj, ktra stanowi rwnowanik pomidzy systemami operacyjnymi, w ktrych wielko liter w nazwie pliku nie ma znaczenia, w ktrym np. mahadeva.mp3 i mahadeva.MP3 s takimi samymi plikami. Na przykad w Windowsie i Mac OS, normcase bdzie konwertowa ca nazw pliku na mae litery, a w systemach kompatybilnych z UNIX-em funkcja ta bdzie zwracaa niezmienion nazw pliku. 3. Iterujc ponownie po licie z uyciem f, wykorzystujemy os.path.splitext(f), aby podzieli nazw pliku na nazw i jej rozszerzenie. 4. Dla kadego pliku sprawdzamy, czy rozszerzenie jest w licie plikw, o ktre nam chodzi (czyli fileExtList, ktra zostaa przekazana do listDirectory). 5. Dla kadego pliku, ktry nas interesuje, wykorzystujemy os.path.join(directory, f), aby skonstruowa pen ciek pliku i zwrci list zawierajc pene cieki. Jeli to moliwe, powinnimy korzysta z funkcji w moduach os i os.path do manipulacji plikami, katalogami i ciekami. Te moduy opakowuj moduy specyczne dla konkretnego systemu, wic funkcje takie, jak os.path.split pooprawnie dziaaj w systemach UNIX, Windows, Mac OS i we wszystkich innych systemach wspieranych przez Pythona. Jest jeszcze inna metoda dostania si do zawartoci katalogu. Metoda ta jest bardzo potna i uywa zestawu symboli wieloznacznych (ang. wildcard ), z ktrymi mona si spotka pracujc w linii polece. Przykad 6.20 Listowanie zawartoci katalogu przy pomocy glob

132

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH

>>> os.listdir("c:\\music\\_singles\\") #(1) [a_time_long_forgotten_con.mp3, hellraiser.mp3, kairo.mp3, long_way_home1.mp3, sidewinder.mp3, spinning.mp3] >>> import glob >>> glob.glob(c:\\music\\_singles\\*.mp3) #(2) [c:\\music\\_singles\\a_time_long_forgotten_con.mp3, c:\\music\\_singles\\hellraiser.mp3, c:\\music\\_singles\\kairo.mp3, c:\\music\\_singles\\long_way_home1.mp3, c:\\music\\_singles\\sidewinder.mp3, c:\\music\\_singles\\spinning.mp3] >>> glob.glob(c:\\music\\_singles\\s*.mp3) #(3) [c:\\music\\_singles\\sidewinder.mp3, c:\\music\\_singles\\spinning.mp3] >>> glob.glob(c:\\music\\*\\*.mp3) #(4) 1. Jak wczeniej powiedzielimy, os.listdir pobiera ciek do katalogu i zwraca wszystkie pliki i podkatalogi, ktre si w nim znajduj. 2. Z drugiej strony, modu glob na podstawie podanego wyraenia skadajcego si z symboli wieloznacznych, zwraca pene cieki wszystkich plikw, ktre speniaj te wyraenie. Tutaj wyraenie jest ciek do katalogu plus "*.mp3", ktry bdzie dopasowywa wszystkie pliki .mp3. Dodajmy, e kady element zwracanej listy jest ju pen ciek do pliku. 3. Jeli chcemy znale wszystkie pliki w okrelonym katalogu, gdzie nazwa zaczyna si od ", a koczy na ".mp3", moemy to zrobi w ten sposb. 4. Teraz rozwa taki scenariusz: mamy katalog z muzyk z kilkoma podkatalogami, wewntrz ktrych s pliki .mp3. Moemy pobra list wszystkich tych plikw za pomoc jednego wywoania glob, wykorzystujc poczenie dwch wyrae. Pierwszym jest "*.mp3" (wyszukuje pliki .mp3), a drugim s same w sobie cieki do katalogw, aby przetworzy kady podkatalog w c:\music. Ta prosto wygldajca funkcja daje nam niesamowite moliwoci! Materiay dodatkowe Python Knowledge Base odpowiada na najczciej zadawane pytanie na temat moduu os Python Library Reference dokumentuje modu os i modu os.path

7.6. WYJTKI I OPERACJE NA PLIKACH - WSZYSTKO RAZEM

133

7.6

Wyjtki i operacje na plikach - wszystko razem

Wszystko razem
Jeszcze raz uoymy wszystkie puzzle domina w jednym miejscu. Ju poznalimy, w jaki sposb dziaa kada linia kodu. Powrcimy do tego jeszcze raz i zobaczymy, jak to wszystko jest ze sob dopasowane. Przykad 6.21 listDirectory def listDirectory(directory, fileExtList): #(1) u"zwraca list obiektw zawierajcych metadane dla plikw o podanych rozszerzeniach" fileList = [os.path.normcase(f) for f in os.listdir(directory)] fileList = [os.path.join(directory, f) for f in fileList if os.path.splitext(f)[1] in fileExtList] #(2) def getFileInfoClass(filename, module=sys.modules[FileInfo. module ]): #(3) u"zwraca klas metadanych pliku na podstawie podanego rozszerzenia" subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:] #(4) return hasattr(module, subclass) and getattr(module, subclass) or FileInfo #(5) return [getFileInfoClass(f)(f) for f in fileList] #(6) 1. listDirectory jest gwn atrakcj tego moduu. Przyjmuje ona na wejciu katalog (np. c:\music\ singles\) i list interesujcych nas rozszerze plikw (jak np. [.mp3]), a nastpnie zwraca list instancji klas, ktre s podklasami sownika, a przechowuj metadane na temat kadego interesujcego nas pliku w tym katalogu. I to wszystko jest wykonywane za pomoc kilku prostych linii kodu. 2. Jak dowiedzielimy si w poprzednim podrozdziale, ta linia kodu zwraca list penych cieek wszystkich plikw z danego katalogu, ktre maj interesujce nas rozszerzenie (podane w argumencie fileExtList). 3. Starzy programici Pascala znaj zagniedone funkcje (funkcje wewntrz funkcji), ale wikszo ludzi jest zdziwionych, gdy mwi si im, e Python je wspiera. Zagniedona funkcja getFileInfoClass moe by wywoana tylko z funkcji, w ktrej jest zadeklarowana, czyli z listDirectory. Jak w przypadku kadej innej funkcji, nie musimy przejmowa si deklaracj interfejsu, ani niczym innym. Po prostu deniujemy funkcj i implementujamy j. 4. Teraz, gdy ju znamy modu os, ta linia powinna nabra sensu. Pobiera ona rozszerzenie pliku (os.path.splitext(filename)[1]), przeksztaca je do duych liter (.upper()), odcina kropk ([1:]) i tworzy nazw klasy uywajc acucha formatujcego. c:\music\ap\mahadeva.mp3 zostaje przeksztacone na .mp3, potem na .MP3, a nastpnie na MP3 i na kocu otrzymujemy MP3FileInfo. 5. Majc nazw klasy obsugujcej ten plik, sprawdzamy czy tak klasa istnieje w tym module. Jeli tak, zwracamy t klas, jeli nie klas bazow FileInfo. To bardzo wane: zwracamy klas. Nie zwracamy obiektu, ale klas sam w sobie. 6. Dla kadego pliku z listy fileList wywoujemy getFileInfoClass z nazw pliku (f). Wywoanie getFileInfoClass(f) zwraca klas. Dokadnie nie wiadomo jak, ale to nam nie przeszkadza. Potem tworzymy obiekt tej klasy (jaka by

134

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH ona nie bya) i przekazujemy nazw pliku (znw f) do jej metody init . Jak pamitamy z wczeniejszych rozdziaw, metoda init klasy FileInfo ustawia warto self[ame"], co powoduje wywoanie setitem klasy pochodnej, czyli MP3FileInfo, eby odpowiednio przetworzy plik i wycign jego metadane. Robimy to wszystko dla kadego interesujcego pliku i zwracamy list obiektw wynikowych.

Zauwamy, e metoda listDirectory jest bardzo oglna. Nie wie w aden sposb, z jakimi typami plikw bdzie pracowa, ani jakie klasy s zdeniowane do obsugi tych plikw. Zaczyna prac od przejrzenia katalogu, w poszukiwaniu plikw do przetwarzania, a potem analizuje swj modu, eby sprawdzi, jakie klasy obsugi (np. MP3FileInfo) s zdeniowane. Moemy rozszerzy ten program, eby obsugiwa inne typy plikw deniujc klasy o odpowiednich nazwach: HTMLFileInfo dla plikw HTML, DOCFileInfo dla plikw Worda itp. listDirectory, bez potrzeby modykacji kodu tej funkcji, obsuy je wszystkie, zrzucajc cae przetwarzanie na odpowiednie klasy i zbierajc otrzymane wyniki.

7.7. WYJTKI I OPERACJE NA PLIKACH - PODSUMOWANIE

135

7.7

Wyjtki i operacje na plikach - podsumowanie

Podsumowanie
Program leinfo.py wprowadzony w rozdziale 5 powinien ju by zrozumiay. u"""Framework do pobierania matedanych specyficznych dla danego typu pliku. Mona utworzy instancj odpowiedniej klasy podajc jej nazw pliku w konstruktorze. Zwrcony obiekt zachowuje si jak sownik posiadajcy par klucz-warto dla kadego fragmentu metadanych. import fileinfo info = fileinfo.MP3FileInfo("/music/ap/mahadeva.mp3") print "\\n".join(["%s=%s" % (k, v) for k, v in info.items()]) Lub uy funkcji listDirectory, aby pobra informacje o wszystkich plikach w katalogu. for info in fileinfo.listDirectory("/music/ap/", [".mp3"]): ... Framework moe by roszerzony poprzez dodanie klas dla poszczeglnych typw plikw, np.: HTMLFileInfo, MPGFileInfo, DOCFileInfo. Kada klasa jest cakowicie odpowiedzialna za waciwe sparsowanie swojego pliku; zobacz przykad MP3FileInfo. """ import os import sys def stripnulls(data): u"usuwa biae znaki i nulle" return data.replace("\00", " ").strip() class FileInfo(dict): u"przechowuje metadane pliku" def __init__(self, filename=None): dict.__init__(self) self["plik"] = filename class MP3FileInfo(FileInfo): u"przechowuje znaczniki ID3v1.0 MP3" tagDataMap = \{u"tytu" : ( 3, 33, stripnulls), "artysta" : ( 33, 63, stripnulls), "album" : ( 63, 93, stripnulls), "rok" : ( 93, 97, stripnulls), "komentarz" : ( 97, 126, stripnulls), "gatunek" : (127, 128, ord)\} def __parse(self, filename): u"parsuje znaczniki ID3v1.0 z pliku MP3" self.clear() try:

136

ROZDZIA 7. WYJTKI I OPERACJE NA PLIKACH fsock = open(filename, "rb", 0) try: fsock.seek(-128, 2) tagdata = fsock.read(128) finally: fsock.close() if tagdata[:3] == TAG: for tag, (start, end, parseFunc) in self.tagDataMap.items(): self[tag] = parseFunc(tagdata[start:end]) except IOError: pass def __setitem__(self, key, item): if key == "plik" and item: self.__parse(item) FileInfo.__setitem__(self, key, item)

def listDirectory(directory, fileExtList): u"zwraca list obiektw zawierajcych metadane dla plikw o podanych rozszerzeniach" fileList = [os.path.normcase(f) for f in os.listdir(directory)] fileList = [os.path.join(directory, f) for f in fileList \ if os.path.splitext(f)[1] in fileExtList] def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]): u"zwraca klas metadanych pliku na podstawie podanego rozszerzenia" subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:] return hasattr(module, subclass) and getattr(module, subclass) or FileInfo return [getFileInfoClass(f)(f) for f in fileList] if __name__ == "__main__": for info in listDirectory("/music/_singles/", [".mp3"]): print "\n".join(["%s=%s" % (k, v) for k, v in info.items()]) print Zanim zanurkujemy w nastpnym rozdziale, upewnijmy si, e nie mamy problemw z: przechwytywaniem wyjtkw za pomoc try...except chronieniem zewntrznych zasobw za pomoc try...finally czytaniem plikw korzystaniem z wielozmiennych przypisa w ptli for Wykorzystywaniem moduu os do niezalenego od platformy zarzdzania plikami Dynamicznym tworzeniem instancji klas nieznanych typw poprzez traktowanie klas jak obiektw

Rozdzia 8

Wyraenia regularne

137

138

ROZDZIA 8. WYRAENIA REGULARNE

8.1

Wyraenia regularne

Wyraenia regularne s bardzo uytecznymi, a zarazem standardowymi rodkami wyszukiwania, zamiany i przetwarzania tekstu wykorzystujc skomplikowane wzorce. Jeli wykorzystywalimy ju wyraenia regularne w innych jzykach (np. w Perlu), to pewnie zauwaymy, e skadnia jest bardzo podobna, ponadto moemy przeczyta podsumowanie moduu re, aby przegldn dostpne funkcje i ich argumenty.

Nurkujemy
acuchy znakw maj metody, ktre su wyszukiwaniu (index, find i count), zmienianiu (replace) i przetwarzaniu (split), ale s one ograniczone do najprostszych przypadkw. Metody te wyszukuj pojedynczy, zapisany na stae cig znakw i zawsze uwzgldniaj wielko liter. Aby wyszuka co niezalenie od wielkoci liter w acuchu s, musimy uy s.lower() lub s.upper() i upewni si, e nasz tekst do wyszukania ma odpowiedni wielko liter. Metody suce do zamiany i podziau maj takie same ograniczenia. Jeli to, co prbujemy zrobi jest moliwe przy uyciu metod acucha znakw, powinnimy ich uy. S szybkie, proste i czytelne. Jeli jednak okazuje si, e uywamy wielu rnych metod i instrukcji if do obsugi przypadkw szczeglnych albo jeli czysz je z uyciem split, join i wyrae listowych w dziwny i nieczytelny sposb, moemy by zmuszeni do przejcia na wyraenia regularne. Pomimo, e skadnia wyrae regularnych jest zwarta i niepodobna do normalnego kodu, wynik moe by czytelniejszy ni uycie dugiego cigu metod acucha znakw. S nawet sposoby dodawania komentarzy wewntrz wyrae regularnych, aby byy one praktycznie samodokumentujce si.

8.2. ANALIZA PRZYPADKU: ADRESY ULIC

139

8.2

Analiza przypadku: Adresy ulic

Analiza przypadku: Adresy ulic


Ta seria przykadw zostaa zainspirowana problemami z prawdziwego ycia. Kilka lat temu gdzie nie w Polsce, zasza potrzeba oczyszczenia i ustandaryzowania adresw ulic zaimportowanych ze starego systemu, zanim zostan zaimportowane do nowego. (Zauwamy, e nie jest to jaki wyimaginowany przykad. Moe on si okaza przydatny.) Poniszy przykad przybliy nas do tego problemu. Przykad 7.1 Dopasowywanie na kocu napisu >>> s = 100 NORTH MAIN ROAD >>> s.replace(ROAD, RD.) #(1) 100 NORTH MAIN RD. >>> s = 100 NORTH BROAD ROAD >>> s.replace(ROAD, RD.) #(2) 100 NORTH BRD. RD. >>> s[:-4] + s[-4:].replace(ROAD, RD.) #(3) 100 NORTH BROAD RD. >>> import re #(4) >>> re.sub(ROAD$, RD., s) #(5) (6) 100 NORTH BROAD RD. 1. Naszym celem jest ustandaryzowanie adresw ulic, wic skrtem od ROAD jest RD.. Na pierwszy rzut oka wydaje si, e po prostu mona wykorzysta metod acucha znakw, jak jest replace. Zakadamy, e wszystkie dane zapisane s za pomoc wielkich liter, wic nie powinno by problemw wynikajcych z niedopasowania, ze wzgldu na wielko liter. Wyszukujemy stay napis, jakim jest ROAD. Jest to bardzo pytki przykad, wic s.replace poprawnie zadziaa. 2. ycie niestety jest troch bardziej skomplikowane, o czym do szybko mona si przekona. Problem w tym przypadku polega na tym, e ROAD wystpuje w adresie dwukrotnie: raz jako cz nazwy ulicy (BROAD) i drugi raz jako oddzielne sowo. Metoda replace znajduje te dwa wystpienia i lepo je zamienia, niszczc adres. 3. Aby rozwiza problem z adresami, gdzie podcig ROAD wystpuje kilka razy, moemy wykorzysta taki pomys: tylko szukamy i zamieniamy ROAD w ostatnich czterech znakach adresu (czyli s[-4:]), a zostawiamy pozosta cz (czyli s[:-4]). Jednak, jak moemy zreszt zobaczy, takie rozwizanie jest dosy niewygodne. Na przykad polecenie, ktre chcemy wykorzysta, zaley od dugo zamienianego napisu (jeli chcemy zamieni STREET na ST., wykorzystamy s[:-6] i s[-6:].replace(...)). Chciaoby si do tego wrci za sze miesicy i to debugowa? Pewnie nie. 4. Nadszed odpowiedni czas, aby przej do wyrae regularnych. W Pythonie caa funkcjonalno wyrae regularnych zawarta jest w module re. 5. Spjrzmy na pierwszy parametr, ROAD$. Jest to proste wyraenie regularne, ktre dopasuje ROAD tylko wtedy, gdy wystpi on na kocu tekstu. Znak $ znaczy koniec napisu. (Mamy take analogiczny znak, znak daszka ^, ktry znaczy pocztek napisu.)

140

ROZDZIA 8. WYRAENIA REGULARNE

6. Korzystajc z funkcji re.sub, przeszukujemy napis s i podcig pasujcy do wyraenia regularnego ROAD$ zamieniamy na RD.. Dziki temu wyraeniu dopasowujemy ROAD na kocu napisu s, lecz napis ROAD nie zostanie dopasowany w sowie BROAD, poniewa znajduje si on w rodku napisu s. Wracajc do historii z porzdkowaniem adresw, okazao si, e poprzedni przykad, dopasowujcy ROAD na kocu adresu, nie by poprawny, poniewa nie wszystkie adresy doczay ROAD na kocu adresu. Niektre adresy koczyy si tylko nazw ulicy. Wiele razy to wyraenie zadziaao poprawnie, jednak gdy mielimy do czynienia z ulic BROAD, wwczas wyraenie regularne dopasowywao ROAD na kocu napisu jako cz sowa BROAD, a takiego wyniku nie oczekiwalimy. Przykad 7.2 Dopasowywanie caych wyrazw >>> s = 100 BROAD >>> re.sub(ROAD$, RD., s) 100 BRD. >>> re.sub(\\bROAD$, RD., s) 100 BROAD >>> re.sub(r\bROAD$, RD., s) 100 BROAD >>> s = 100 BROAD ROAD APT. 3 >>> re.sub(r\bROAD$, RD., s) 100 BROAD ROAD APT. 3 >>> re.sub(r\bROAD\b, RD., s) 100 BROAD RD. APT 3

#(1) #(2)

#(3) #(4)

1. W istocie chcielimy odnale ROAD znajdujce si na kocu napisu i jest samodzielnym sowem, a nie czci duszego wyrazu. By opisa co takiego za pomoc wyrae regularnych korzystamy z \b, ktre znaczy tyle co tutaj musi znajdowa si pocztek lub koniec wyrazu. W Pythonie jest to nieco skomplikowane przez fakt, i znaki specjalne (takie jak np. \) musz by poprzedzone wanie znakiem \. Zjawisko to okrelane jest czasem plag ukonikw (ang. backslash plague) i wydaje si by jednym z powodw atwiejszego korzystania z wyrae regularnych w Perlu ni w Pythonie. Z drugiej jednak strony, w Perlu skadnia wyrae regularnych wymieszana jest ze skadni jzyka, co utrudnia stwierdzenie czy bd tkwi w naszym wyraeniu regularnym, czy w bdnym uyciu skadni jzyka. 2. Eleganckim obejciem problemu plagi ukonikw jest wykorzystywanie tzw. surowych napisw (ang. raw string), ktre opisywalimy w rozdziale 3, poprzez umieszczanie przed napisami litery r. Python jest w ten sposb informowany o tym, i aden ze znakw specjalnych w tym napisie nie ma by interpretowany; \t odpowiada znakowi tab, jednak r\t oznacza tyle, co litera t poprzedzona znakiem \. Przy wykorzystaniu wyrae regularnych zalecane jest stosowanie surowych napisw; w innym wypadku wyraenia szybko staj si niezwykle skomplikowane (a przecie ju ze swej natury nie s proste). 3. C... Niestety wkrtce okazuje si, i istnieje wicej przypadkw przeczcych logice naszego postpowania. W tym przypadku ROAD byo samodzielnym sowem, jednak znajdowao si w rodku napisu, poniewa na jego kocu umieszczony by jeszcze numer mieszkania. Z tego powodu nasze biece wyraenie nie

8.2. ANALIZA PRZYPADKU: ADRESY ULIC

141

zostao odnalezione, funkcja re.sub niczego nie zamienia, a co za tym idzie napis zosta zwrcony w pierwotnej postaci (co nie byo naszym celem). 4. Aby rozwiza ten problem wystarczy zamieni w wyraeniu regularnym $ na kolejne \b. Teraz bdzie ono pasowa do kadego samodzielnego sowa ROAD, niezalenie od jego pozycji w napisie.

142

ROZDZIA 8. WYRAENIA REGULARNE

8.3

Analiza przypadku: Liczby rzymskie

Analiza przypadku: Liczby rzymskie


Najprawdopodobniej spotkalimy si ju gdzie z liczbami rzymskimi. Mona je spotka w starszych lmach ogldanych w telewizji (np. Copyright MCMXLVI zamiast Copyright 1946) lub na cianach bibliotek, czy uniwersytetw (napisy typu zaoone w MDCCCLXXXVIII zamiast zaoone w 1888 roku). Moglimy je take zobaczy na przykad w referencjach bibliogracznych. Ten system reprezentowania liczb siga czasw staroytnego Rzymu (std nazwa). W liczbach rzymskich wykorzystuje si siedem znakw, ktre na rne sposoby si powtarza i czy, aby zapisa pewn liczb: I=1 V=5 X = 10 L = 50 C = 100 D = 500 M = 1000 Poniej znajduj si podstawowe zasady konstruowania liczb rzymskich: Znaki s addytywne. I to 1, II to 2, a III to 3. VI to 6 (dosownie, 5 i 1), VII to 7, a VIII to 8. Znaki dziesitek (I, X, C i M) mog si powtarza do trzech razy. Za czwartym naley odj od nastpnego wikszego znaku pitek. Nie mona zapisa liczby 4 jako IIII. Zamiast tego napiszemy IV (o 1 mniej ni 5). Liczba 40 zapisujemy jako XL (o 10 mniej ni 50), 41 jako XLI, 42 jako XLII, 43 jako XLIII, a potem 44 jako XLIV (o 10 mniej ni 50, a potem o 1 mniej ni 5). Podobnie w przypadku 9. Musimy odejmowa od wyszego znaku dziesitek: 8 to VIII, lecz 9 zapiszemy jako IX (o 1 mniej ni 10), a nie jako VIIII (poniewa znak nie moe si powtarza cztery razy). Liczba 90 to XC, a 900 zapiszemy jako CM. Znaki pitek nie mog si powtarza. Liczba 10 jest zawsze reprezentowana przez X, nigdy przez VV. Liczba 100 to zawsze C, nigdy LL. Liczby rzymskie s zawsze pisane od najwyszych do najniszych i czytane od lewej do prawej, wic porzdek znakw jest bardzo wany. DC to 600, jednak CD jest kompletnie inn liczb (400, poniewa o 100 mniej ni 500). CI to 101, jednak IC nie jest adn poprawn liczb rzymsk (nie moemy bezporednio odejmowa 1 od 100, musimy to zapisa jako XCIX, o 10 mniej ni 100, doda 1 mniej ni 10).

8.3. ANALIZA PRZYPADKU: LICZBY RZYMSKIE Sprawdzamy tysice

143

Jak sprawdzi, czy jaki acuch znakw jest liczb rzymsk? Sprbujmy sprawdza cyfra po cyfrze. Jako, e liczby rzymskie s zapisywane zawsze od najwyszych do najniszych, zacznijmy od tych najwyszych: tysicy. Dla liczby 1000 i wikszych, tysice zapisywane s przez seri liter M. Przykad 7.3 Sprawdzamy tysice >>> import re >>> pattern = ^M?M?M?$ >>> re.search(pattern, M) <SRE_Match object at 0106FB58> >>> re.search(pattern, MM) <SRE_Match object at 0106C290> >>> re.search(pattern, MMM) <SRE_Match object at 0106AA38> >>> re.search(pattern, MMMM) >>> re.search(pattern, "") <SRE_Match object at 0106F4A8> 1. Ten wzorzec skada si z 3 czci: ^, ktre umieszczone jest w celu dopasowania jedynie pocztku acucha. Gdybymy go nie umiecili, wzorzec pasowaby do kadego wystpienia znakw M, czego przecie nie chcemy. Chcemy, aby dopasowane zostay jedynie znaki M znajdujce si na pocztku acucha, o ile w ogle istniej. M?, ktre ma wykry, czy istnieje pojedyncza litera M. Jako, e powtarzamy to trzykrotnie, dopasujemy od zera do trzech liter M w szeregu. $, w celu dopasowania wzorca do koca acucha. Gdy poczymy to ze znakiem ^ na pocztku, otrzymamy wzorzec, ktry musi pasowa do caego acucha, bez adnych znakw przed czy po serii znakw M. 2. Sednem moduu re jest funkcja search, ktra jako argumenty przyjmuje wyraenie regularne (wzorzec) i acuch znakw (M), a nastpnie prbuje go dopasowa do wzorca. Gdy zostanie dopasowany, search zwraca obiekt ktry posiada wiele metod, ktre opisuj dopasowanie. Jeli nie uda si dopasowa, search zwraca None, co jest Pythonow pust wartoci i nic nie oznacza. Na razie jedyne, co nas interesuje, to czy wzorzec zosta dopasowany, czy nie, a co moemy stwierdzi przez sprawdzenie, co zwrcia funkcja search. M pasuje do wzorca, gdy pierwsze opcjonalne M zostao dopasowane, a drugie i trzecie zostao zignorowane. 3. MM pasuje, gdy pierwsze i drugie opcjonalne M zostao dopasowane, a trzecie zignorowane. 4. MMM rwnie pasuje do wzorca, gdy wszystkie trzy opcjonalne wystpienia M we wzorcu zostay dopasowane. 5. MMMM nie pasuje, gdy pomimo dopasowania pierwszych trzech opcjonalnych znakw M, za trzecim wzorzec wymaga, aby acuch si skoczy, a w naszym acuchu znakw znajduje si kolejna litera M. Tak wic search zwraca warto None. #(1) #(2) #(3) #(4) #(5) #(6)

144

ROZDZIA 8. WYRAENIA REGULARNE

6. Co ciekawe, pusty acuch te pasuje do wzorca, gdy wszystkie wystpienia M s opcjonalne. Sprawdzamy setki Setki s nieco trudniejsze, poniewa schemat zapisu nie jest a tak prosty jak w wypadku tysicy. Mamy wic nastpujce moliwoci: 100 = C 200 = CC 300 = CCC 400 = CD 500 = D 600 = DC 700 = DCC 800 = DCCC 900 = CM Wynika z tego, e mamy 4 wzorce: CM CD Zero do trzech wystpie C (zero, gdy moe nie by adnej setki) D, po ktrym nastpuje zero do trzech C Ostatnie dwa wzorce moemy poczy w opcjonalne D, a za nim od zera do trzech C. Poniszy przykad ilustruje jak sprawdza setki w liczbach Rzymskich. Przykad 7.4 Sprawdzamy setki >>> import re >>> pattern = ^M?M?M?(CM|CD|D?C?C?C?)\$ >>> re.search(pattern, MCM) <SRE_Match object at 01070390> >>> re.search(pattern, MD) <SRE_Match object at 01073A50> >>> re.search(pattern, MMMCCC) <SRE_Match object at 010748A8> >>> re.search(pattern, MCMC) >>> re.search(pattern, "") <SRE_Match object at 01071D98>

#(1) #(2) #(3) #(4) #(5) #(6)

8.3. ANALIZA PRZYPADKU: LICZBY RZYMSKIE

145

1. Ten wzorzec zaczyna si tak samo jak poprzedni, rozpoczynajc sprawdzanie od pocztku acucha (^), potem sprawdzajc tysice (M?M?M?). Tutaj zaczyna si nowa cz, ktra deniuje 3 alternatywne wzorce rozdzielone pionow kresk (|): CM, CD, i D?C?C?C? (opcjonalne D, po ktrym nastpuje od zera do trzech opcjonalnych znakw C). Analizator wyrae regularnych sprawdza kady ze wzorcw w kolejnoci od lewej do prawej, wybiera pierwszy pasujcy i ignoruje reszt. 2. MCM pasuje, gdy pierwsza litera M pasuje, drugie i trzecie M jest ignorowane, i CM pasuje (gdy CD oraz D?C?C?C? nie s nawet sprawdzane). MCM to rzymska liczba 1900. 3. MD pasuje, poniewa pierwsze M pasuje, drugie i trzecie M z wzorca jest ignorowane, oraz D pasuje do wzorca D?C?C?C? (wystpienia znaku C jest opcjonalne, wic analizator je ignoruje). MD to rzymska liczba 1500. 4. MMMCCC pasuje, gdy pasuj wszystkie trzy pierwsze znaki M, a fragment D?C?C?C? we wzorcu pasuje do CCC (D jest opcjonalne). MMMCCC to 3300. 5. MCMC nie pasuje, Pierwsze M pasuje, CM rwnie, ale $ ju nie, gdy nasz acuch zamiast si skoczy, ma kolejn liter C. Nie zostaa ona dopasowana do wzorca D?C?C?C?, gdy zosta on wykluczony przez wystpienie wzorca CM. 6. Co ciekawe, pusty acuch znakw dalej pasuje do naszego wzorca, gdy wszystkie znaki M s opcjonalne, tak jak kady ze znakw we wzorcu D?C?C?C?. U! Widzimy, jak szybko wyraenia regularne staj si brzydkie? A jak na razie wprowadzilimy do niego tylko tysice i setki. Ale jeli dokadnie ledzilimy cay ten rozdzia, dziesitki i jednostki nie powinny stanowi dla Ciebie problemu, poniewa wzr jest identyczny. A teraz zajmiemy si inn metod wyraania wzorca.

146

ROZDZIA 8. WYRAENIA REGULARNE

8.4

Skadnia ?n, m?

Skadnia {n, m}
W poprzednim podrozdziale poznalimy wzorce, w ktrych ten sam znak mg si powtarza co najwyej trzy razy. Tutaj przedstawimy inny sposb zapisania takiego wyraenia, a ktry wydaje si by bardziej czytelny. Najpierw spjrzmy na metody wykorzystane w poprzednim przykadzie. Przykad 7.5 Stary sposb: kady znak opcjonalny >>> import re >>> pattern = ^M?M?M?$ >>> re.search(pattern, M) #(1) <_sre.SRE_Match object at 0x008EE090> >>> pattern = ^M?M?M?$ >>> re.search(pattern, MM) #(2) <_sre.SRE_Match object at 0x008EEB48> >>> pattern = ^M?M?M?$ >>> re.search(pattern, MMM) #(3) <_sre.SRE_Match object at 0x008EE090> >>> re.search(pattern, MMMM) #(4) 1. Instrukcja ta dopasowuje pocztek napisu, a nastpnie pierwsz liter M, lecz nie dopasowuje drugiego i trzeciego M (wszystko jest w porzdku, poniewa s one opcjonalne), a nastpnie koniec napisu. 2. Tutaj zostaje dopasowany pocztek napisu, a nastpnie pierwsze i drugie opcjonalne M, jednak nie zostaje dopasowane trzecie M (ale wszystko jest w ok, poniewa jest to opcjonalne), ale zostaje dopasowany koniec napisu. 3. Zostanie dopasowany pocztek napisu, a nastpnie wszystkie opcjonalne M, a potem koniec napisu. 4. Dopasowany zostanie pocztek napisu, nastpnie wszystkie opcjonalne M, jednak koniec tekstu nie zostanie dopasowany, poniewa pozostanie jedno niedopasowane M, dlatego te nic nie zostanie dopasowane, a operacja zwrci None. Przykad 7.6 Nowy sposb: od n do m >>> pattern = ^M{0,3}$ #(1) >>> re.search(pattern, M) #(2) <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, MM) #(3) <_sre.SRE_Match object at 0x008EE090> >>> re.search(pattern, MMM) #(4) <_sre.SRE_Match object at 0x008EEDA8> >>> re.search(pattern, MMMM) #(5) 1. Ten wzorzec mwi: dopasuj pocztek napisu, potem od zera do trzech znakw M, a nastpnie koniec napisu. 0 i 3 moe by dowoln liczb. Jeli chcielibymy dopasowa co najmniej jeden, lecz nie wicej ni trzy znaki M, powiniennimy napisa M{1,3}.

8.4. SKADNIA ?N, M?

147

2. Dopasowujemy pocztek napisu, potem jeden znak M z trzech moliwych, a nastpnie koniec napisu. 3. Tutaj zostaje dopasowany pocztek napisu, nastpnie dwa M z trzech moliwych, a nastpnie koniec napisu. 4. Zostanie dopasowany pocztek napisu, potem trzy znaki M z trzech moliwych, a nastpnie koniec napisu. 5. W tym miejscu dopasowujemy pocztek napisu, potem trzy znaki M z pord trzech moliwych, lecz nie dopasujemy koca napisu. To wyraenie regularne pozwala wykorzysta tylko trzy litery M, zanim dojdzie do koca napisu, a my mamy cztery, wic ten wzorzec niczego nie dopasuje i zwrci None. Nie mamy programowalnej moliwoci okrelenia, czy dwa wyraenia s rwnowane. Najlepszym sposobem, aby to zrobi, jest wykonanie wielu testw w celu przekonania si, czy otrzymujemy takie same wyniki. Wicej na temat pisania testw dowiemy si w dalszej czci tej ksiki.

Sprawdzanie dziesitek i jednoci Teraz rozszerzmy wyraenie wykrywajce liczby rzymskie, eby odnajdywao te dziesitki i jednoci. Ten przykad pokazuje sprawdzanie dziesitek. Przykad 7.7 Sprawdzanie dziesitek >>> pattern = ^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$ >>> re.search(pattern, MCMXL) #(1) <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, MCML) #(2) <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, MCMLX) #(3) <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, MCMLXXX) #(4) <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, MCMLXXXX) #(5) 1. To dopasuje pocztek acucha, potem pierwsze opcjonalne M, dalej CM i XL, a potem koniec acucha. Zapamitajmy, e skadnia (A|B|C) oznacza dopasuj dokadnie jedno z A, B lub C. W tym wypadku dopasowalimy XL, wic ignorujemy XC i L?X?X?X? i przechodzimy do koca acucha. MCMXL to 1940. 2. Tutaj dopasowujemy pocztek acucha, pierwsze opcjonalne M, potem CM i L?X?X?X?. Z tego ostatniego elementu dopasowane zostaje tylko L, a opcjonalne X zostaj pominite. Potem przechodzimy na koniec acucha. MCML to 1950. 3. Dopasowuje pocztek napisu, potem pierwsze opcjonalne M, nastpnie CM, potem opcjonalne L i pierwsze opcjonalne X, pomijajc drugie i trzecie opcjonalne X, a nastpnie dopasowuje koniec napisu. MCMLX jest rzymsk reprezentacj liczby 1960.

148

ROZDZIA 8. WYRAENIA REGULARNE

4. Tutaj zostanie dopasowany pocztek napisu, nastpnie pierwsze opcjonalne M, potem CM, nastpnie opcjonalne L, wszystkie trzy opcjonalne znaki X i w kocu dopasowany zostanie koniec napisu. MCMLXXX jest rzymsk reprezentacj liczby 1980. 5. To dopasuje pocztek napisu, nastpnie pierwsze opcjonalne M, potem CM, opcjonalne L, wszystkie trzy opcjonalne znaki X, jednak nie moe dopasowa koca napisu, poniewa pozosta jeszcze jeden niewliczony znak X. Zatem cay wzorzec nie zostanie dopasowany, wic zostanie zwrcone None. MCMLXXXX nie jest poprawn liczb rzymsk. Poniej przedstawiono podobne wyraenie, ktre dodatkowo sprawdza jednoci. Oszczdzimy sobie szczegw. >>> pattern = ^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$ Jak bdzie wygldao to wyraenie wykorzystujc skadni {n,m}? Zobaczmy na poniszy przykad. Przykad 7.8 Sprawdzanie liczb rzymskich korzystajc ze skadni {n, m} >>> pattern = ^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$ >>> re.search(pattern, MDLV) #(1) <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, MMDCLXVI) #(2) <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, MMMDCCCLXXXVIII) #(3) <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, I) #(4) <_sre.SRE_Match object at 0x008EEB48> 1. Dopasowany zostanie pocztek napisu, potem jeden z moliwych czterech znakw M i nastpnie D?C{0,3}. Z kolei D?C{0,3} dopasuje opcjonalny znak D i zero z trzech moliwych znakw C. Idc dalej, dopasowany zostanie L?X{0,3} poprzez dopasowanie opcjonalnego znaku L i zero z moliwych trzech znakw X. Nastpnie dopasowujemy V?I{0,3} dodajc opcjonalne V i zero z trzech moliwych znakw I, a ostatecznie dopasowujemy koniec napisu. MDLV jest rzymsk reprezentacj liczby 1555. 2. To dopasuje pocztek napisu, nastpnie dwa z czterech moliwych znakw M, a potem D?C{0,3} z D i jednym z trzech moliwych znakw C. Dalej dopasujemy L?X{0,3} z L i jednym z trzech moliwych znakw X, a nastpnie V?I{0,3} z V i jednym z trzech moliwych znakw I, a w kocu koniec napisu. MMDCLXVI jest reprezentacj liczby 2666. 3. Tutaj dopasowany zostanie pocztek napisu, a potem trzy z trzech znakw M, a nastpnie D?C{0,3} ze znakiem D i trzema z trzech moliwych znakw C. Potem dopasujemy L?X{0,3} z L i trzema z trzech znakw X, nastpnie V?I{0,3} z V i trzema z trzech moliwych znakw I, a ostatecznie koniec napisu. MMMDCCCLXXXVIII jest reprezentacj liczby 3888 i ponadto jest najdusz liczb Rzymsk, ktr mona zapisa bez rozszerzonej skadni.

8.4. SKADNIA ?N, M?

149

4. Obserwuj dokadnie. Dopasujemy pocztek napisu, potem zero z czterech M, nastpnie dopasowujemy D?C{0,3} pomijajc opcjonalne D i dopasowujc zero z trzech znakw C. Nastpnie dopasowujemy L?X{0,3} pomijajc opcjonalne L i dopasowujc zero z trzech znakw X, a potem dopasowujemy V?I{0,3} pomijajc opcjonalne V, ale dopasowujc jeden z trzech moliwych I. Ostatecznie dopasowujemy koniec napisu. Jeli przeledzie to wszystko i zrozumiae to za pierwszym razem, jeste bardzo bystry. Teraz wyobramy sobie sytuacj, e prbujemy zrozumie jakie inne wyraenie regularne, ktre znajduje si w centralnej, krytycznej funkcji wielkiego programu. Albo wyobra sobie nawet, e wracasz do swojego wyraenia regularnego po kilku miesicach. Sytuacje takie mog nie wyglda ciekawie... W nastpnym podrozdziale dowiemy si o alternatywnej skadni, ktra pomaga zrozumie i zarzdza wyraeniami.

150

ROZDZIA 8. WYRAENIA REGULARNE

8.5

Rozwleke wyraenia regularne

Rozwleke wyraenia regularne


Jak na razie, mielimy do czynienia z czym, co nazywam zwizymi wyraeniami regularnymi. Jak pewnie zauwaylimy, s one trudne do odczytania i nawet, jeli ju je rozszyfrujemy, nie ma gwarancji, e zrobimy to za np. sze miesicy. To, czego potrzebujemy, to dokumentacja w ich treci. Python pozwala na to przez tworzenie rozwlekych wyrae regularnych (ang. verbose regular expressions). Rni si one od zwizych dwoma rzeczami: Biae znaki s ignorowane. Spacje, znaki tabulacji, znaki nowej linii nie s dopasowywane jako spacje, znaki tabulacji lub znaki nowej linii. Znaki te nie s w ogle dopasowywane. (Jeli bymy chcieli jednak dopasowa ktry z nich, musisz poprzedzi je odwrotnym ukonikiem (\).) Komentarze s ignorowane. Komentarz w rozwlekym wyraeniu regularnym wyglda dokadnie tak samo, jak w kodzie Pythona: zaczyna si od # i leci a do koca linii. W tym przypadku jest to komentarz w wieloliniowym acuchu znakw, a nie w kodzie rdowym, ale zasada dziaania jest taka sama. atwiej bdzie to zrozumie jeli skorzystamy z przykadu. Skorzystajmy ze zwizego wyraenia regularnego, ktre utworzylimy wczeniej i zrbmy z niego rozwleke. Ten przykad pokazuje jak. Przykad 7.9 Wyraenia regularne z komentarzami >>> pattern = """ ^ M{0,3} (CM|CD|D?C{0,3}) (XC|XL|L?X{0,3}) (IX|IV|V?I{0,3}) $ """ >>> re.search(pattern, <_sre.SRE_Match object >>> re.search(pattern, <_sre.SRE_Match object >>> re.search(pattern, <_sre.SRE_Match object >>> re.search(pattern, # # # # # # # # # pocztek acucha znakw tysice - 0 do 3 M setki - 900 (CM), 400 (CD), 0-300 (0 do 3 C), lub 500-800 (D, a po nim 0 do 3 C) dziesitki - 90 (XC), 40 (XL), 0-30 (0 do 3 X), or 50-80 (L, a po nim 0 do 3 X) jednoci - 9 (IX), 4 (IV), 0-3 (0 do 3 I), lub 5-8 (V, a po nim 0 do 3 I) koniec acucha znakw

M, re.VERBOSE) #(1) at 0x008EEB48> MCMLXXXIX, re.VERBOSE) #(2) at 0x008EEB48> MMMDCCCLXXXVIII, re.VERBOSE) #(3) at 0x008EEB48> M) #(4)

1. Najwaniejsz rzecz o ktrej naley pamita, gdy korzystamy z rozwlekych wyrae regularnych jest to, e musimy przekaza dodatkowy argument: re.VERBOSE. Jest to staa zdeniowana w module re, ktra sygnalizuje, e wyraenie powinno by traktowane jako rozwleke. Jak widzimy, ten wzorzec ma mnstwo biaych znakw (ktre s ignorowane) i kilka komentarzy (ktre te s ignorowane). Gdy usuniemy biae znaki i komentarze, to pozostanie dokadnie to samo wyraenie

8.5. ROZWLEKE WYRAENIA REGULARNE

151

regularne, jakie otrzymalimy w poprzednim przykadzie, ale o wiele bardziej czytelne. (Zauwamy, e co prawda acuch znakw posiada polskie znaki, ale nie tworzymy go w unikodzie, poniewa i tak te znaki nie maj dla nas adnego znaczenia, poniewa s w komentarzach.) 2. To dopasowuje pocztek acucha, potem jedno z trzech moliwych M, potem CM, L i trzy z trzech moliwych X, a nastpnie IX i koniec acucha. 3. To dopasowuje pocztek acucha, potem trzy z trzech moliwych M, dalej D, trzy z trzech moliwych C, L z trzema moliwymi X, potem V z trzema moliwymi I i na koniec koniec acucha. 4. Tutaj nie udao si dopasowa niczego. Czemu? Poniewa nie przekazalimy agi re.VERBOSE, wic funkcja re.search traktuje to wyraenie regularne jako zwize, z du iloci biaych znakw i kratek. Python nie rozpoznaje samodzielnie, czy kaemy mu dopasowa zwize, czy moe rozwleke wyraenie regularne i przyjmuje, e kade jest zwize, chyba e wyranie wskaemy, e tak nie jest.

152

ROZDZIA 8. WYRAENIA REGULARNE

8.6

Analiza przypadku: Przetwarzanie numerw telefonw

Analiza przypadku: Przetwarzanie numerw telefonw


Do tej pory koncentrowalimy si na dopasowywaniu caych wzorcw. Albo pasowa albo nie. Ale wyraenia regularne s duo potniejsze. Gdy zostanie dopasowane, mona wycign z niego wybrane kawaki i dziki temu sprawdzi co gdzie zostao dopasowane. Oto kolejny przykad z ycia wzity, z jakim mona si spotka: przetwarzanie amerykaskich numerw telefonw. Klient chcia mc wprowadza numer w dowolnej formie w jednym polu, ale potem chcia, eby przechowywa oddzielnie numer kierunkowy, numer w dwch czciach i opcjonalny numer wewntrzny w bazie danych rmy. W Internecie mona znale wiele takich wyrae regularnych, ale adne z nich nie jest a tak bardzo restrykcyjne. Oto przykady numerw telefonw jakie mia program przetwarza: 800-555-1212 800 555 1212 800.555.1212 (800) 555-1212 1-800-555-1212 800-555-1212-1234 800-555-1212x1234 800-555-1212 ext. 1234 work 1-(800) 555.1212 #1234 Cakiem due zrnicowanie! W kadym z tych przypadkw musimy wiedzie, e numerem kierunkowy 800, e pierwsz czci numeru jest 555, druga 1212, a dla tych z numerem wewntrznym 1234. Sprbujmy rozwiza ten problem. Poniszy przykad pokazuje pierwszy krok. Przykad 7.10 Odnajdywanie numerw >>> phonePattern = re.compile(r^(\d{3})-(\d{3})-(\d{4})$) #(1) >>> phonePattern.search(800-555-1212).groups() #(2) (800, 555, 1212) >>> phonePattern.search(800-555-1212-1234) #(3) >>> 1. Zawsze odczytujemy wyraenie regularne od lewej do prawej. Tutaj dopasowujemy pocztek acucha znakw, potem (\d{3}). Co to takiego te (\d{3})? {3} oznacza dopasuj dokadnie 3 wystpienia (jest to wariacja skadni {n, m}). \d oznacza jakakolwiek cyfra (od 0 do 9). Umieszczenie ich w nawiasach oznacza dopasuj dokadnie 3 cyfry, i zapamitaj je jako grup, o ktr mona zapyta pniej. Nastpnie mamy dopasowa mylnik. Dalej dopasuj kolejn grup

8.6. ANALIZA PRZYPADKU: PRZETWARZANIE NUMERW TELEFONW153 dokadnie trzech cyfr, a nastpnie kolejny mylnik, i ostatni grup tym razem czterech cyfr. Na koniec dopasuje koniec acucha znakw. 2. Aby otrzyma grupy, ktre zapamita modu przetwarzania wyrae regularnych, naley skorzysta z metody groups() obiektu zwracanego przez funkcj search. Zwrci ona krotk z iloci elementw rwn iloci grup zdeniowanych w wyraeniu regularnym. W tym przypadku mamy trzy grupy: dwie po 3 cyfry i ostatni czterocyfrow. 3. To jednak nie jest rozwizaniem naszego problemu, bo nie dopasowuje numeru telefonu z numerem wewntrznym. Musimy wic je rozszerzy. Przykad 7.11 Odnajdywanie numeru wewntrznego >>> phonePattern = re.compile(r^(\d{3})-(\d{3})-(\d{4})-(\d+)$) >>> phonePattern.search(800-555-1212-1234).groups() (800, 555, 1212, 1234) >>> phonePattern.search(800 555 1212 1234) >>> >>> phonePattern.search(800-555-1212) >>> #(1) #(2) #(3) #(4)

1. To wyraenie regularne jest praktycznie identyczne z wczeniejszym. Tak jak wczeniej, dopasowujemy pocztek acucha, potem zapamitywan grup trzech cyfr, mylnik, zapamitywan grup trzech cyfr, mylnik i zapamitywan grup czterech cyfr. Now czci jest kolejny mylnik i zapamitywana grupa jednej lub wicej cyfr. Na kocu jak w poprzednim przykadzie dopasowujemy koniec acucha. 2. Metoda groups() zwraca teraz krotk czterech elementw, poniewa wyraenie regularne deniuje teraz cztery grupy do zapamitania. 3. Niestety nie jest to wersja ostateczna, gdy zakadamy, e kada cz numeru telefonu jest rozdzielona mylnikiem. Co jeli bd rozdzielone spacj, kropk albo przecinkiem? Potrzebujemy bardziej oglnego rozwizania. 4. Ups! Nie tylko to wyraenie nie robi wszystkiego co powinno, ale cofnlimy si wstecz, gdy teraz nie dopasowuje ono numerw bez numeru wewntrznego. To nie jest to co chcielimy; jeli w numerze jest podany numer wewntrzny, to chcemy go zna, ale jeli go nie ma, to i tak chcemy zna inne czci numeru telefonu. Nastpny przykad pokazuje wyraenie regularne, ktre obsuguje rne separatory midzy czciami numeru telefonu. Przykad 7.12 Obsuga rnych separatorw >>> phonePattern = re.compile(r^(\d{3})\D+(\d{3})\D+(\d{4})\D+(\d+)$) #(1) >>> phonePattern.search(800 555 1212 1234).groups() #(2) (800, 555, 1212, 1234) >>> phonePattern.search(800-555-1212-1234).groups() #(3) (800, 555, 1212, 1234)

154

ROZDZIA 8. WYRAENIA REGULARNE #(4) #(5)

>>> phonePattern.search(80055512121234) >>> >>> phonePattern.search(800-555-1212) >>> 1. Teraz dopasowujemy pocztek acucha, grup trzech cyfr, potem \D+... zaraz, zaraz, co to jest? \D dopasowuje dowolny znak, ktry nie jest cyfr, a + oznacza jeden lub wicej. Wic \D+ dopasowuje jeden lub wicej znakw nie bdcych cyfr. Korzystamy z niego, aby dopasowa rne separatory, nie tylko mylniki. 2. Korzystanie z \D+ zamiast z - pozwala na dopasowywanie numerw telefonw ze spacjami w roli separatora czci. 3. Oczywicie mylniki te dziaaj. 4. Niestety, to dalej nie jest ostateczna wersja, poniewa nie obsuguje ona braku jakichkolwiek separatorw. 5. No i dalej nie rozwizany pozosta problem moliwoci braku numeru wewntrznego. Mamy wic dwa problemy do rozwizania, ale w obu przypadkach rozwiemy problem t sam technik. Nastpny przykad pokazuje wyraenie regularne pasujce take do numeru bez separatorw. Przykad 7.13 Obsuga numerw bez separatorw >>> phonePattern = re.compile(r^(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$) >>> phonePattern.search(80055512121234).groups() (800, 555, 1212, 1234) >>> phonePattern.search(800.555.1212 x1234).groups() (800, 555, 1212, 1234) >>> phonePattern.search(800-555-1212).groups() (800, 555, 1212, ) >>> phonePattern.search((800)5551212 x1234) >>> 1. Jedyna zmiana jakiej dokonalimy od ostatniego kroku to zamiana wszystkich + na *. Zamiast \D+ pomidzy czciami numeru telefonu dopasowujemy teraz \D*. Pamitasz, e + oznacza 1 lub wicej? * oznacza 0 lub wicej. Tak wic teraz jestemy w stanie przetworzy numer nawet bez separatorw. 2. Nareszcie dziaa! Dlaczego? Dopasowany zosta pocztek acucha, grupa 3 cyfr (800), potem zero znakw nienumerycznych, potem znowu zapamitywana grupa 3 cyfr (555), znowu zero znakw nienumerycznych, zapamitywana grupa 4 cyfr (1212), zero znakw nienumerycznych, numer wewntrzny (1234) i nareszcie koniec acucha. 3. Inne odmiany te dziaaj np. numer rozdzielony kropkami ze spacj i x-em przed numerem wewntrznym. 4. Wreszcie udao si te rozwiza problem z brakiem numeru wewntrznego. Tak czy siak groups() zwraca nam krotk z 4 elementami, ale ostatni jest tutaj pusty.

#(1) #(2) #(3) #(4) #(5)

8.6. ANALIZA PRZYPADKU: PRZETWARZANIE NUMERW TELEFONW155 5. Niestety jeszcze nie skoczylimy. Co tutaj jest nie tak? Przed numerem kierunkowym znajduje si dodatkowy znak "(", a nasze wyraenie zakada, e numer kierunkowy znajduje si na samym przodzie. Nie ma problemu, moemy zastosowa t sam metod co do znakw rozdzielajcych. Nastpny przykad pokazuje jak sobie radzi ze znakami wiodcymi w numerach telefonw. Przykad 7.14 Obsuga znakw na pocztku numeru telefonu >>> phonePattern = re.compile(r^\D*(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$) >>> phonePattern.search((800)5551212 ext. 1234).groups() (800, 555, 1212, 1234) >>> phonePattern.search(800-555-1212).groups() (800, 555, 1212, ) >>> phonePattern.search(work 1-(800) 555.1212 #1234) >>> 1. Wzorzec w tym przykadzie jest taki sam jak w poprzednim, z wyjtkiem tego, e teraz na pocztku acucha dopasowujemy \D* przed pierwsz zapamitywan grup (numerem kierunkowym). Zauwa e tych znakw nie zapamitujemy (nie s one w nawiasie). Jeli je napotkamy, to ignorujemy je i przechodzimy do numeru kierunkowego. 2. Teraz udao si przetworzy numer telefonu z nawiasem otwierajcym na pocztku. (Zamykajcy by ju wczeniej obsugiwany; by traktowany jako nienumeryczny znak pasujcy do teraz drugiego \D*.) 3. Tak na wszelki wypadek sprawdzamy czy nie popsulimy czego. Jako, e pocztkowy znak jest cakowicie opcjonalny, nastpuje dopasowanie w dokadnie taki sam sposb jak w poprzednim przykadzie. 4. W tym miejscu wyraenia regularne sprawiaj, e chce si czowiekowi rozbi bardzo duym motem monitor. Dlaczego to nie pasuje? Wszystko za spraw 1 przed numerem kierunkowym (numer kierunkowy USA), a przecie przyjlimy, e na pocztku mog by tylko nienumeryczne znaki. Ech... Cofnijmy si na chwil. Jak na razie wszystkie wyraenia dopasowywalimy od pocztku acucha. Ale teraz wida wyranie, e na pocztku naszego acucha mamy nieokrelon liczb znakw ktrych kompletnie nie potrzebujemy. Po co mamy wic dopasowywa pocztek acucha? Jeli tego nie zrobimy, to przecie pominie on tyle znakw ile mu si uda, a przecie o to nam chodzi. Takie podejcie prezentuje nastpny przykad. Przykad 7.15 Numerze telefonu, znajd ci gdziekolwiek jeste! >>> phonePattern = re.compile(r(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$) >>> phonePattern.search(work 1-(800) 555.1212 #1234).groups() (800, 555, 1212, 1234) >>> phonePattern.search(800-555-1212) (800, 555, 1212, ) >>> phonePattern.search(80055512121234) (800, 555, 1212, 1234) #(1) #(2) #(3) #(4) #(1) #(2) #(3) #(4)

156

ROZDZIA 8. WYRAENIA REGULARNE

1. Zauwa, e brakuje ^ w tym wyraeniu regularnym, Teraz ju nie dopasowujemy pocztku acucha, bo przecie nikt nie powiedzia, e wyraenie musi pasowa do caego acucha, a nie do fragmentu. Mechanizm wyrae regularnych sam zadba o namierzenie miejsca do ktrego ono pasuje (o ile w ogle). 2. Teraz nareszcie pasuje numer ze znakami na pocztku (w tym cyframi) i dowolnymi, jakimikolwiek separatorami w rodku. 3. Na wszelki wypadek sprawdzamy i to. Dziaa! 4. To te dziaa. Widzimy, jak szybko wyraenia regularne wymykaj si spod kontroli? Rzumy okiem na jedn z poprzednich przykadw. Widzimy rnice pomidzy nim i nastpnym? Pki jeszcze rozumiemy to co napisalimy, rozpiszmy to jako rozwleke wyraenie regularne, eby nie zapomnie, co jest co i dlaczego. Przykad 7.16 Przetwarzanie numerw telefonu (wersja nalna) >>> phonePattern = re.compile(r # nie dopasowuj pocztku acucha, numer moe si zacz gdziekolwiek (\d{3}) # numer kierunkowy - 3 cyfry (np. 800) \D* # opcjonalny nienumeryczny separator (\d{3}) # pierwsza cz numeru - 3 cyfry (np. 555) \D* # opcjonalny separator (\d{4}) # druga cz numeru (np. 1212) \D* # opcjonalny separator (\d*) # numer wewntrzny jest opcjonalny i moe mie dowoln dugo $ # koniec acucha , re.VERBOSE) >>> phonePattern.search(work 1-(800) 555.1212 #1234).groups() #(1) (800, 555, 1212, 1234) >>> phonePattern.search(800-555-1212) #(2) (800, 555, 1212, ) 1. Pomijajc fakt, e jest ono podzielone na wiele linii, to wyraenie jest dokadnie takie samo jak po ostatnim kroku, wic nie jest niespodziank, e dalej dziaa jak powinno. 2. Jeszcze jedna prba. Tak, dziaa! Skoczone!

8.7. PODSUMOWANIE

157

8.7

Wyraenia regularne - podsumowanie

Podsumowanie
To co przedstawilimy tutaj, to zaledwie wierzchoek gry lodowej, odnonie tego co potra wyraenia regularne. Innymi sowy, mimo e jestemy teraz nimi przytoczeni, uwierzmy, e jeszcze nic nie widzielimy. Powiniene ju by zaznajomiony z poniszymi technikami: ^ dopasowuje pocztek napisu. $ dopasowuje koniec napisu. \b dopasowuje pocztek lub koniec sowa. \d dopasowuje dowoln cyfr. \D dopasowuje dowolny znak, ktry nie jest cyfr. x? dopasowuje opcjonalny znak x (innymi sowy, dopasowuje x zero lub jeden raz). x* dopasowuje x zero lub wicej razy. x+ dopasowuje x jeden lub wicej razy. x{n,m} dopasowuje znak x co najmniej n razy, lecz nie wicej ni m razy. (a|b|c) dopasowuje a albo b albo c. (x) generalnie jest to zapamitana grupa. Mona otrzyma warto, ktra zostaa dopasowana, wykorzystujc metod groups() obiektu zwrconego przez re.search. Wyraenia regularne daj ekstremalnie potne moliwoci, lecz nie zawsze s poprawnym rozwizaniem do kadego problemu. Powinno si wicej o nich poczyta, aby si dowiedzie, kiedy bd one odpowiednie podczas rozwizywania pewnych problemw, czy te kiedy mog bardziej powodowa problemy, ni je rozwizywa. Niektrzy ludzie, kiedy napotkaj problem, myl: Wiem, uyj wyrae regularnych. I teraz maj dwa problemy. Jamie Zawinski

158

ROZDZIA 8. WYRAENIA REGULARNE

Rozdzia 9

Przetwarzanie HTML-a

159

160

ROZDZIA 9. PRZETWARZANIE HTML-A

9.1

Przetwarzanie HTML-a

Nurkujemy
Na comp.lang.python czsto mona zobaczy pytania w stylu jak mona znale wszystkie nagwki/obrazki/linki w moim dokumencie HTML?, jak mog sparsowa/przetumaczy/przerobi tekst mojego dokumentu HTML tak, aby zostawi znaczniki w spokoju? lub te jak mog natychmiastowo doda/usun/zacytowa atrybuty z wszystkich znacznikw mojego dokumentu HTML?. Rozdzia ten odpowiada na wszystkie te pytania. Poniej przedstawiono w dwch czciach cakowicie dziaajcy program. Pierwsza cz, BaseHTMLProcessor.py jest oglnym narzdziem, ktre przetwarza pliki HTML przechodzc przez wszystkie znaczniki i bloki tekstowe. Druga cz, dialect.py, jest przykadem tego, jak wykorzysta BaseHTMLProcessor.py, aby przetumaczy tekst dokumentu HTML, lecz przy tym zostawiajc znaczniki w spokoju. Przeczytaj notki dokumentacyjne i komentarze w celu zorientowania si, co si tutaj waciwie dzieje. Dua cz tego kodu wyglda jak czarna magia, poniewa nie jest oczywiste w jaki sposb dowolna z metod klasy jest wywoywana. Jednak nie martw si, wszystko zostanie wyjanione w odpowiednim czasie. Przykad 8.1 BaseHTMLProcessor.py #-*- coding: utf-8 -*from sgmllib import SGMLParser import htmlentitydefs class BaseHTMLProcessor(SGMLParser): def reset(self): # dodatek (wywoywane przez SGMLParser. init ) self.pieces = [] SGMLParser.reset(self) def unknown starttag(self, tag, attrs): # wywoywane dla kadego pocztkowego znacznika # attrs jest list krotek (atrybut, warto) # np. dla <pre class="screen"> bdziemy mieli tag="pre", # attrs=[("class", "screen")] # Chcielibymy zrekonstruowa oryginalne znaczniki i atrybuty, ale # moe si zdarzy, e umiecimy w cudzysowach wartoci, ktre nie byy # zacytowane w rdle dokumentu, a take moemy zmieni rodzaj # cudzysoww w wartoci danego atrybutu (pojedyncze cudzysowy lub podwjne). # Dodajmy, e niepoprawnie osadzony kod nie-HTML-owy (np. kod JavaScript) # moe zosta le sparsowany przez klas bazow, a to spowoduje bd # wykonania skryptu. # Cay nie-HTML musi by umieszczony w komentarzu HTML-a (<!-- kod -->), # aby parser zostawi ten niezmieniony (korzystajc z handle comment). strattrs = "".join([ %s="%s" % (key, value) for key, value in attrs]) self.pieces.append("<%(tag)s%(strattrs)s>" % locals())

9.1. NURKUJEMY

161

def unknown endtag(self, tag): # wywoywane dla kadego znacznika kocowego np. dla </pre>, tag bdzie rwny "pre" # Rekonstruuje oryginalny znacznik kocowy w wyjciowym dokumencie self.pieces.append("</%(tag)s>" % locals()) def handle charref(self, ref): # wywoywane jest dla kadego odwoania znakowego np. dla "&#160;", # ref bdzie rwne "160" # Rekonstruuje oryginalne odwoanie znakowe. self.pieces.append("&#%(ref)s;" % locals()) def handle entityref(self, ref): # wywoywane jest dla kadego odwoania do encji np. dla "&copy;", # ref bdzie rwne "copy" # Rekonstruuje oryginalne odwoanie do encji. self.pieces.append("&%(ref)s" % locals()) # standardowe encje HTML-a s zakoczone rednikiem; pozostae encje # (encje spoza HTML-a) nie s if htmlentitydefs.entitydefs.has key(ref): self.pieces.append(";") def handle data(self, text): # wywoywane dla kadego bloku czystego teksu np. dla danych spoza dowolnego #znacznika, w ktrych nie wystpuj adne odwoania znakowe, czy odwoania do encji. # Przechowuje dosownie oryginalny tekst. self.pieces.append(text) def handle comment(self, text): # wywoywane dla kadego komentarza np. <!-- wpis kod JavaScript w tym miejscu --> # Rekonstruuje oryginalny komentarz. # Jest to szczeglnie wane, gdy dokument zawiera kod przeznaczony # dla przegldarki (np. kod Javascript) wewntrz komentarza, dziki temu # parser moe przej przez ten kod bez zakce; # wicej szczegw w komentarzu metody unknown starttag. self.pieces.append("<!--%(text)s-->" % locals()) def handle pi(self, text): # wywoywane dla kadej instrukcji przetwarzania np. <?instruction> # Rekonstruuje oryginaln instrukcj przetwarzania self.pieces.append("<?%(text)s>" % locals()) def handle decl(self, text): # wywoywane dla deklaracji typu dokumentu, jeli wystpuje, np. # <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" # "http://www.w3.org/TR/html4/loose.dtd"> # Rekonstruuje oryginaln deklaracj typu dokumentu self.pieces.append("<!%(text)s>" % locals()) def output(self): u"""Zwraca przetworzony HTML jako pojedynczy acuch znakw"""

162

ROZDZIA 9. PRZETWARZANIE HTML-A return "".join(self.pieces)

if

name == " main ": for k, v in globals().items(): print k, "=", v

Przykad 8.2 dialect.py #-*- coding: utf-8 -*import re from BaseHTMLProcessor import BaseHTMLProcessor class Dialectizer(BaseHTMLProcessor): subs = () def reset(self): # dodatek (wywoywany przez init # Resetuje wszystkie atrybuty self.verbatim = 0 BaseHTMLProcessor.reset(self)

klasy bazowej)

def start pre(self, attrs): # wywoywane dla kadego znacznika <pre> w rdle HTML # Zwiksza licznik trybu dosownoci verbatim, a nastpnie # obsuguje ten znacznik normalnie self.verbatim += 1 self.unknown starttag("pre", attrs) def end pre(self): # wywoywane dla kadego znacznika </pre> # Zmiejsza licznik trybu dosownoci verbatim self.unknown endtag("pre") self.verbatim -= 1 def handle data(self, text): # metoda nadpisana # wywoywane dla kadego bloku tekstu w rdle # Jeli jest w trybie dosownym, zapisuje tekst niezmieniony; # inaczej przetwarza tekst za pomoc szeregu podstawie self.pieces.append(self.verbatim and text or self.process(text)) def process(self, text): # wywoywane z handle data # Przetwarza kady blok wykonujc serie podstawie # za pomoc wyrae regularnych (podstawienia s definiowane przez # klasy pochodne) for fromPattern, toPattern in self.subs:

9.1. NURKUJEMY text = re.sub(fromPattern, toPattern, text) return text class ChefDialectizer(Dialectizer): u"""konwertuje HTML na mow szwedzkiego szefa kuchni

163

oparte na klasycznym chef.x, copyright (c) 1992, 1993 John Hagerman """ subs = ((ra([nu]), ru\1), (rA([nu]), rU\1), (ra\B, re), (rA\B, rE), (ren\b, ree), (r\Bew, roo), (r\Be\b, re-a), (r\be, ri), (r\bE, rI), (r\Bf, rff), (r\Bir, rur), (r(\w*?)i(\w*?)$, r\1ee\2), (r\bow, roo), (r\bo, roo), (r\bO, rOo), (rthe, rzee), (rThe, rZee), (rth\b, rt), (r\Btion, rshun), (r\Bu, roo), (r\BU, rOo), (rv, rf), (rV, rF), (rw, rw), (rW, rW), (r([a-z])[.], r\1. Bork Bork Bork!)) class FuddDialectizer(Dialectizer): u"""konwertuje HTML na mow Elmer Fudda""" subs = ((r[rl], rw), (rqu, rqw), (rth\b, rf), (rth, rd), (rn[.], rn, uh-hah-hah-hah.)) class OldeDialectizer(Dialectizer): u"""konwertuje HTML na pozorowany jzyk rednioangielski""" subs = ((ri([bcdfghjklmnpqrstvwxyz])e\b, ry\1), (ri([bcdfghjklmnpqrstvwxyz])e, ry\1\1e), (rick\b, ryk), (ria([bcdfghjklmnpqrstvwxyz]), re\1e), (re[ea]([bcdfghjklmnpqrstvwxyz]), re\1e),

164

ROZDZIA 9. PRZETWARZANIE HTML-A (r([bcdfghjklmnpqrstvwxyz])y, r\1ee), (r([bcdfghjklmnpqrstvwxyz])er, r\1re), (r([aeiou])re\b, r\1r), (ria([bcdfghjklmnpqrstvwxyz]), ri\1e), (rtion\b, rcioun), (rion\b, rioun), (raid, rayde), (rai, rey), (ray\b, ry), (ray, rey), (rant, raunt), (rea, ree), (roa, roo), (rue, re), (roe, ro), (rou, row), (row, rou), (r\bhe, rhi), (rve\b, rveth), (rse\b, re), (r"s\b", res), (ric\b, rick), (rics\b, ricc), (rical\b, rick), (rtle\b, rtil), (rll\b, rl), (rould\b, rolde), (rown\b, roune), (run\b, ronne), (rrry\b, rrye), (rest\b, reste), (rpt\b, rpte), (rth\b, rthe), (rch\b, rche), (rss\b, rsse), (r([wybdp])\b, r\1e), (r([rnt])\b, r\1\1e), (rfrom, rfro), (rwhen, rwhan))

def translate(url, dialectName="chef"): u"""pobiera plik na podstawie URL-a i tumaczy korzystajc z dialektu, gdzie dialekt in ("chef", "fudd", "olde")""" import urllib sock = urllib.urlopen(url) htmlSource = sock.read() sock.close() parserName = "%sDialectizer" % dialectName.capitalize() parserClass = globals()[parserName]

9.1. NURKUJEMY parser = parserClass() parser.feed(htmlSource) parser.close() return parser.output() def test(url): u"""testuje wszystkie dialekty na pewnym URL-u""" for dialect in ("chef", "fudd", "olde"): outfile = "%s.html" % dialect fsock = open(outfile, "wb") fsock.write(translate(url, dialect)) fsock.close() import webbrowser webbrowser.open new(outfile) if name == " main ": test("http://diveintopython.org/odbchelper list.html")

165

Uruchamiajc ten skrypt, przetumaczymy podrozdzia 3.2, z ksiki Dive Into Python, na pozorowany szwedzki kuchmistrza z Muppetw, udawany jzyk Elmer Fudda (z kreskwek Krlik Bugs) i pozorowany jzyk rednioangielski (luno oparty na Chaucers The Canterbury Tales). Jeli spojrzymy na rdo HTML wyjciowej strony, zobaczymy, e znaczniki i atrybuty zostay nietknite, lecz tekst midzy znacznikami zosta przetumaczonya udawany jzyk. Jeli przygldniemy si jeszcze bardziej, zobaczymy, e tylko tytuy i akapity zostay przetumaczone. Przedstawione kody i wyniki dziaania programu zostay niezmienione. Przykad 8.3 Wyjcie z dialect.py <div class="abstract"> <p>Lists awe <span class="application">Pydon</span>s wowkhowse datatype. If youw onwy expewience wif wists is awways in <span class="application">Visuaw Basic</span> ow (God fowbid) de datastowe in <span class="application">Powewbuiwdew</span>, bwace youwsewf fow <span class="application">Pydon</span> wists.</p> </div>

166

ROZDZIA 9. PRZETWARZANIE HTML-A

9.2

Wprowadzenie do sgmllib.py

Wprowadzenie do sgmllib.py
Przetwarzanie HTML-a jest podzielone na trzy etapy: podzielenie dokumentu na elementy skadowe, manipulowanie tymi elementami i ponowna rekonstrukcja tych kawakw do HTML-a. Pierwszy krok jest wykonywany przez sgmllib.py, ktry jest czci standardowej biblioteki Pythona. Kluczem do zrozumienia tego rozdziau jest uwiadomienie sobie, e HTML to nie tylko tekst, jest to tekst z pewn struktur. Struktura ta powstaje z mniej lub bardziej hierarchicznych sekwencji znacznikw pocztkowych i znacznikw kocowych. Zazwyczaj nie pracujemy z HTML-em w sposb strukturalny, raczej tekstowo w edytorze tekstu lub wizualnie w przegldarce internetowej, czy innym narzdziu. sgmllib.py prezentuje HTML strukturalnie. sgmllib.py zawiera jedn wan klas: SGMLParser. SGMLParser rozbiera HTML na uyteczne kawaki takie jak znaczniki pocztkowe i znaczniki kocowe. Jak tylko udaje mu si rozebra jakie dane na przydatne kawaki, wywouj odpowiedni metod, w zalenoci co zostao znalezione. eby wykorzysta parser, tworzymy podklas SGMLParser-a i nadpisujemy te metody. Mwic, e sgmllib.py prezentuje HTML strukturalnie, mielimy na myli to, e struktura dokumentu HTML jest okrelana poprzez wywoywane metody, a take argumenty przekazywane do tych metod. SGMLParser parsuje HTML na 8 rodzajw danych i wykonuje odpowiednie metody dla kadego z nich: Znacznik pocztkowy Znacznik HTML, ktry rozpoczyna blok np. <html>, <head>, <body> lub <pre> lub samodzielne znaczniki jak <br> lub <img>. Kiedy odnajdzie znacznik tagname, to SGMLParser bdzie szuka metod o nazwie start tagname lub do tagname. Na przykad, jeli odnajdzie znacznik <pre>, to bdzie szuka metod start pre lub do pre. Jeli je znajdzie, SGMLParser wywoa te metody z list atrybutw tego znacznika. W przeciwnym wypadku wywoa unknown starttag z nazw znacznika i list atrybutw. Znacznik kocowy Znacznik HTML, ktry koczy blok np. </html>, </head>, </body> lub </pre>. Kiedy odnajdzie znacznik kocowy, SGMLParser bdzie szuka metody o nazwie end tagname. Jeli j znajdzie, wywoa t metod, jeli nie, wywoa metod unknown endtag z nazw znacznika. Odwoania znakowe Znak specjalny, do ktrego dowoujemy si podajc jego dziesitny lub szesnastkowy odpowiednik np. &amp;#160;. Kiedy odwoanie znakowe zostanie odnalezione, SGMLParser wywoa handle charref z tekstem dziesitnego lub szesnastkowego odpowiednika znaku. Odwoanie do encji Encja HTML to np. &amp;copy;. Kiedy zostanie znaleziona, SGMLParser wywouje handle entityref z nazw encji. Komentarz Element HTML, ktry jest ograniczony przez <!-- ... -->. Kiedy zostanie znaleziony, SGMLParser wywouje handle comment z zawartoci komentarza. Instrukcje przetwarzania Instrukcje przetwarzania HTML s ograniczone przez <? ... >. Kiedy zostan odnalezione, SGMLParser wywouje handle pi z zawartoci instrukcji przetwarzania.

9.2. WPROWADZENIE DO SGMLLIB.PY

167

Deklaracja Deklaracja HTML np. typu dokumentu (DOCTYPE), jest ograniczona przez <! ... >. Kiedy zostanie znaleziona, SGMLParser wywouje handle decl z zawartoci deklaracji. Dane tekstowe Bloki tekstu. Wszystko inne, co si nie mieci w innych 7 kategoriach. Kiedy zostan one znalezione, SGMLParser wywoa handle data z tekstem. sgmllib.py posiada zestaw testw, ktre to ilustruj. Moemy uruchomi sgmllib.py, podajc w linii polece nazw pliku, a bdzie on wywietla znaczniki i inne elementy podczas parsowania. Zrobione jest to poprzez utworzenie podklasy SGMLParser i zdeniowanie metod unknown starttag, unknown endtag, handle data i innych metod, ktre bd po prostu wywietla swoje argumenty. W ActivePython IDE pod Windows moemy okreli argumenty linii polece za pomoc Run script. Rne argumenty oddzielamy spacj.

Przykad 8.4 Test sgmllib.py <span>c:\python23\lib> type "c:\downloads\diveintopython\html\toc\index.html"</span> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Dive Into Python</title> <link rel="stylesheet" href="diveintopython.css" type="text/css"> [...ciach...] Tutaj jest kawaek spisu treci angielskiej wersji tej ksiki, w HTML-u. Oczywicie cieki do plikw moesz mie troch inne. (Angielsk wersj tej ksiki, w formacie HTML, moesz znale na http://diveintopython.org/.) Uruchomiajc to za pomoc zestawu testw sgmllib.py, zobaczymy: <span>c:\python23\lib> python sgmllib.py "c:\downloads\diveintopython\html\toc\index.html"</span> data: \n\n start tag: <html lang="en" > data: \n start tag: <head> data: \n start tag: <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" > data: \n \n start tag: <title> data: Dive Into Python end tag: </title> data: \n start tag: <link rel="stylesheet" href="diveintopython.css" type="text/css" > data: \n

168

ROZDZIA 9. PRZETWARZANIE HTML-A

[...ciach...] Taki jest plan reszty tego rozdziau: Dziedziczymy po SGMLParser, aby stworzy klasy, ktre wydobywaj interesujce dane z dokumentu HTML. Dziedziczymy po SGMLParser, aby stworzy podklas BaseHTMLProcessor, ktra nadpisuje wszystkie 8 metod obsugi i wykorzystujemy je, aby zrekonstruowa oryginalny dokument HTML z otrzymywanych kawakw. Dziedziczymy po BaseHTMLProcessor, aby utworzy Dialectizer, ktry dodaje kilka metod w celu specjalnego przetworzenia okrelonych znacznikw HTML. Ponadto nadpisuje metod handle data, aby zapewni moliwo przetwarzania blokw tekstowych pomidzy znacznikami HTML. Dziedziczymy po Dialectizer, aby stworzy klasy, ktre deniuj zasady przetwarzania tekstu wykorzystane w Dialectizer.handle data. Piszemy zestaw testw, ktre korzystaj z prawdziwej strony internetowej, http: //diveintopython.org/, i j przetwarzaj. Przy okazji dowiemy si, czym jest locals i globals, a take jak formatowa acuchy znakw za pomoc sownikw.

9.3. WYCIGANIE DANYCH Z DOKUMENTU HTML

169

9.3

Wyciganie danych z dokumentu HTML

Wyciganie danych z dokumentu HTML


Aby wycign dane z dokumentu HTML, tworzymy podklas klasy SGMLParser i deniujemy dla encji lub kadego znacznika, ktry nas interesuje, odpowiedni metod. Pierwszym krokiem do wydobycia danych z dokumentu HTML jest zdobycie jakiego dokumentu. Jeli posiadamy jaki dokument HTML na swoim twardym dysku, moemy wykorzysta funkcje do obsugi plikw, aby go odczyta, jednak prawdziwa zabawa rozpoczyna si, gdy wemiemy HTML z istniejcej strony internetowej. Przykad 8.5 Modu urllib >>> import urllib #(1) >>> sock = urllib.urlopen("http://diveintopython.org/") #(2) >>> htmlSource = sock.read() #(3) >>> sock.close() #(4) >>> print htmlSource #(5) <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Dive Into Python</title> <link rel="stylesheet" href="diveintopython.css" type="text/css"> <link rev="made" href="mailto:f8dy@diveintopython.org"> <meta name="generator" content="DocBook XSL Stylesheets V1.52.2"> <meta name="description" content=" This book lives at . If youre reading it somewhere else, you may not have the latest version."> <meta name="keywords" content="Python, Dive Into Python, tutorial, object-oriented, programming, documentation, book, free"> <link rel="alternate" type="application/rss+xml" title="RSS" href="http://diveintopython.org/history.xml"> </head> <body> <table id="Header" width="100\%" border="0" cellpadding="0" cellspacing="0" summary=""> <tr> <td id="breadcrumb" colspan="6">\&nbsp;</td> </tr> <tr> <td colspan="3" id="logocontainer"> <h1 id="logo">Dive Into Python</h1> <p id="tagline">Python from novice to pro</p> </td> [...ciach...] 1. Modu urllib jest czci standardowej biblioteki Pythona. Zawiera on funkcje suce do pobierania informacji o danych, a take pobierania samych danych z

170

ROZDZIA 9. PRZETWARZANIE HTML-A internetu na podstawie adresu URL (gwnie strony internetowe).

2. Najprostszym sposobem wykorzystania urllib-a jest pobranie caego tekstu strony internetowej przy pomocy funkcji urlopen. Otworzenie URL-a jest rwnie proste, jak otworzenie pliku. Zwracana warto funkcji urlopen przypomina normalny obiekt pliku i posiada niektre analogiczne metody do obiektu pliku. 3. Najprostsz czynnoci, ktr moemy wykona na obiekcie zwrconym przez urlopen, jest wywoanie read. Metoda ta odczyta cay HTML strony internetowej i zwrci go w postaci acucha znakw. Obiekt ten posiada take metod readlines, ktra czyta tekst linia po linii, dodajc kolejne linie do listy. 4. Kiedy skoczymy prac na tym obiekcie, powinnimy go jeszcze zamkn za pomoc close, podobnie jak normalny plik. 5. Mamy kompletny dokument HTML w postaci acucha znakw, pobrany ze strony domowej http://diveintopython.org/ i jestemy przygotowani do tego, aby go sparsowa. Przykad 8.6 Wprowadzenie do urllister.py from sgmllib import SGMLParser class URLLister(SGMLParser): def reset(self): SGMLParser.reset(self) self.urls = [] def start\_a(self, attrs): href = [v for k, v in attrs if k==href] if href: self.urls.extend(href)

#(1)

#(2) #(3) (4)

1. reset jest wywoywany przez metod init SGMLParser-a, a take mona go wywoa rcznie ju po utworzeniu instancji parsera. Zatem, jeli potrzebujemy powtrnie zainicjalizowa instancj parsera, ktry by wczeniej uywany, zrobimy to za pomoc reset (nie przez init ). Nie ma potrzeby tworzenia nowego obiektu. 2. Zawsze, kiedy parser odnajdzie znacznik <a>, wywoa metod start a. Znacznik moe posiada atrybut href, a take inne jak na przykad name, czy title. Parametr attrs jest list krotek [(atrybut1, warto1), (atrybut2, warto2), ...]. Znacznik ten moe by take samym <a>, poprawnym (lecz bezuytecznym) znacznikiem HTML, a w tym przypadku attrs bdzie pust list. 3. Moemy stwierdzi, czy znacznik <a> posiada atrybut href, za pomoc prostego wielozmiennego wyraenie listowego. 4. Porwnywanie napisw (np. k==href) jest zawsze wraliwe na wielko liter, lecz w tym przypadku takie uycie jest bezpieczne, poniewa SGMLParser konwertuje podczas tworzenia attrs nazwy atrybutw na mae litery.

9.3. WYCIGANIE DANYCH Z DOKUMENTU HTML Przykad 8.7 Korzystanie z urllister.py >>> import urllib, urllister >>> usock = urllib.urlopen("http://diveintopython.org/") >>> parser = urllister.URLLister() >>> parser.feed(usock.read()) #(1) >>> usock.close() #(2) >>> parser.close() #(3) >>> for url in parser.urls: print url #(4) toc/index.html #download #languages toc/index.html appendix/history.html download/diveintopython-html-5.0.zip download/diveintopython-pdf-5.0.zip download/diveintopython-word-5.0.zip download/diveintopython-text-5.0.zip download/diveintopython-html-flat-5.0.zip download/diveintopython-xml-5.0.zip download/diveintopython-common-5.0.zip [... ciach ...]

171

1. Wywoujemy metod feed zdeniowan w SGMLParser, aby nakarmi parser przekazujc mu kod HTML-a. Metoda ta przyjmuje acuch znakw, ktrym w tym przypadku bdzie warto zwrcona przez usock.read(). 2. Podobnie jak pliki, powinnimy zamkn swoje obiekty URL, kiedy ju nie bd ci potrzebne. 3. Powiniene take zamkn obiekt parsera, lecz z innego powodu. Podczas czytania danych przekazujemy je do parsera, lecz metoda feed nie gwarantuje, e wszystkie przekazane dane, zostay przetworzone. Parser moe te dane zbuforowa i czeka na dalsz porcj danych. Kiedy wywoamy close, mamy pewno, e bufor zostanie oprniony i wszystko zostanie cakowicie sparsowane. 4. Poniewa parser zosta zamknity, wic parsowanie zostao zakoczone i parser.urls zawiera list wszystkich URL-i, do ktrych linki zawiera dokument HTML. (Twoje wyjcie moe wyglda inaczej, poniewa z biegiem czasu linki mogy ulec zmianie.)

172

ROZDZIA 9. PRZETWARZANIE HTML-A

9.4

Wprowadzenie do BaseHTMLProcessor.py

Wprowadzenie do BaseHTMLProcessor.py
SGMLParser nie tworzy niczego samodzielnie. On po prostu parsuje, parsuje i parsuje i wywouje metod dla kadej interesujcej rzeczy jak znajdzie, ale te metody nie wykonuj niczego. SGMLParser jest konsumentem HTML-a: bierze HTML-a i rozkada go na mae, strukturalne czci. Jak ju widzielimy w poprzednim podrozdziale, moemy dziedziczy po klasie SGMLParser, aby zdeniowa klasy, ktre przechwyc poszczeglne znaczniki i jako to poytecznie wykorzystaj, np. stworz list odnonikw na danej stronie internetowej. Teraz pjdziemy krok dalej i zdeniujemy klas, ktra przechwyci wszystko, co zgosi SGMLParser i zrekonstruuje kompletny dokument HTML. Uywajc terminologii technicznej nasza klasa bdzie producentem HTML-a. BaseHTMLProcessor dziedziczy po SGMLParser i dostarcza 8 istotnych metod obsugi: unknown starttag, unknown endtag, handle charref, handle entityref, handle comment, handle pi, handle decl i handle data. Przykad 8.8 Wprowadzenie do BaseHTMLProcessor.py class BaseHTMLProcessor(SGMLParser): def reset(self): self.pieces = [] SGMLParser.reset(self)

#(1)

def unknown_starttag(self, tag, attrs): #(2) strattrs = "".join([ %s="%s" % (key, value) for key, value in attrs]) self.pieces.append("<%(tag)s%(strattrs)s>" % locals()) def unknown_endtag(self, tag): #(3) self.pieces.append("</%(tag)s>" % locals()) def handle_charref(self, ref): #(4) self.pieces.append("&#%(ref)s;" % locals()) def handle_entityref(self, ref): #(5) self.pieces.append("&%(ref)s" % locals()) if htmlentitydefs.entitydefs.has_key(ref): self.pieces.append(";") def handle_data(self, text): self.pieces.append(text) #(6)

def handle_comment(self, text): #(7) self.pieces.append("\begin{comment} %(text)s \end{comment} " % locals()) def handle_pi(self, text): #(8) self.pieces.append("<?%(text)s>" % locals()) def handle_decl(self, text):

9.4. WPROWADZENIE DO BASEHTMLPROCESSOR.PY self.pieces.append("<!%(text)s>" % locals())

173

1. reset, woany przez SGMLParser. init , inicjalizuje self.pieces jako pust list przed wywoaniem metody klasy przodka. self.pieces jest atrybutem, ktry bdzie przechowywa czci konstruowanego dokumentu HTML. Kada metoda bdzie rekonstruowa HTML parsowany przez SGMLParser i kada z tych metod bdzie dodawa jaki tekst do self.pieces. Zauwamy, e self.pieces jest list. Moglibymy ulec pokusie, aby zdeniowa ten atrybut jako obiekt acucha znakw i po prostu docza do niego kolejne kawaki tekstu. To take by dziaao, ale Python jest duo bardziej wydajny pracujc z listami. 1 2. Poniewa BaseHTMLProcessor nie deniuje adnej metody dla poszczeglnych znacznikw (jak np. metoda start a w URLLister), SGMLParser bdzie wywoywa dla kadego pocztkowego znacznika metod unknown starttag. Ta metoda przyjmuje na wejciu znacznik (argument tag) i list par postaci nazwa atrybutu/warto atrybutu (argument attrs), a nastpnie rekonstruuje oryginalnego HTML-a i dodaje do self.pieces. Napis formatujcy jest tutaj nieco dziwny; rozwikamy to pniej w tym rozdziale (a take t dziwnie wygldajc funkcj locals). 3. Rekonstrukcja znacznikw kocowych jest duo prostsza; po prostu pobieramy nazw znacznika i opakowujemy nawiasami ostrymi </...>. 4. Gdy SGMLParser napotka odwoanie znakowe wywouje metod handle charref i przekazuje jej sam warto odwoania. Jeli dokument HTML zawiera &#160;, ref przyjmie warto 160. Rekonstrukcja oryginalnego kompletnego odwoania znakowego wymaga po prostu dodania znakw &#...;. 5. Odwoanie do encji jest podobne do odwoania znakowego, ale nie zawiera znaku kratki (#). Rekonstrukcja oryginalnego odwoania do encji wymaga dodania znakw &;...;. (Waciwie, jak wskaza na to pewien czytelnik, jest to nieco bardziej skomplikowane. Tylko niektre standardowe encje HTML-a kocz si znakiem rednika; inne podobnie wygldajce encje ju nie. Na szczcie dla nas zbir standardowych encji HTML-a zdeniowany jest w Pythonie w sowniku w module o nazwie htmlentitydefs. Std ta dodatkowa instrukcja if.) 6. Bloki tekstu s po prostu doczane do self.pieces bez adnych zmian, w postaci dosownej. 7. Komentarze HTML-a opakowywane s znakami <!--...-->.
1 Powodem dla ktrego Python jest lepszy w pracy z listami ni napisami, jest fakt i listy s modykowalne (mutable), a napisy s niemodykowalne (immutable). Co oznacza, e zwikszeniem listy jest dodanie do niej po prostu nowego elementu i zaktualizowanie indeksu. Natomiast poniewa napis nie moe by zmieniony po utworzeniu, z reguy kod s = s + nowy utworzy cakowicie nowy napis powstay z poczenia oryginalnego napisu s i napisu nowy, a oryginalny napis zostanie zniszczony. To wymaga wielu kosztownych operacji zarzdzania pamici, a wielko zaangaowanego wysiku ronie wraz z dugoci napisu, a wic wykonywanie kodu s = s + nowy w ptli jest zabjcze. W terminologii technicznej dodanie n elementw do listy oznacza zoono O(n), podczas gdy dodanie n elementw do napisu zoono O(n2 ). Z drugiej strony Python korzysta z prostej optymalizacji, polegajcej na tym, e jeli dany acuch znakw posiada tylko jedno odwoanie, to nie tworzy nowego acucha, tylko rozszerza stary i akurat w tym przypadku byoby nieco szybciej na acuchach znakw (wwczas zoono byaby O(n)).

174

ROZDZIA 9. PRZETWARZANIE HTML-A

8. Instrukcje przetwarzania wstawiane s pomidzy znakami <?...>. Wane Specykacja HTML-a wymaga, aby wszystkie nie-HTML-owe elementy (jak np. JavaScript) byy zawarte pomidzy HTML-owymi znakami komentarza, ale nie wszystkie strony internetowe robi to waciwie (a wspczesne przegldarki internetowe s wyrozumiae w tym wzgldzie). BaseHTMLProcessor natomiast nie jest wyrozumiay; jeli skrypt jest osadzony niewaciwie, bdzie on sparsowany tak, jakby by HTML-em. Np. jeli skrypt zawiera znaki mniejszoci i rwnoci SGMLParser moe bdnie pomyle, e znalaz znaczniki i atrybuty. SGMLParser zawsze konwertuje nazwy znacznikw i atrybutw na mae znaki, co moe zepsu dziaanie skryptu i BaseHTMLProcessor zawsze otacza wartoci atrybutw znakami cudzysowu (nawet jeli oryginalny dokument HTML uywa apostrofw lub nie uywa niczego), co ju na pewno zepsuje skrypt. Zawsze chro swj skrypt osadzony w HTML-u znakami komentarza.

Przykad 8.9 BaseHTMLProcessor i jego metoda output def output(self): #(1) u"""Zwraca przetworzony HTML jako pojedynczy acuch znakw""" return "".join(self.pieces) 1. To jest jedyna metoda, ktra nie jest woana przez klas przodka, czyli klas SGMLParser. Poniewa pozostae metody umieszczaj swoje zrekonstruowane kawaki HTML-a w self.pieces, ta funkcja jest potrzebna, aby poczy wszystkie te kawaki w jeden napis. Gdy jak ju wspomniano wczeniej Python jest wietny w obsudze list i z reguy mierny w obsudze napisw, kompletny napis wyjciowy tworzony jest tylko wtedy, gdy kto o to wyranie poprosi. Materiay dodatkowe W3C omawia odwoania znakowe i encje. Python Library Reference potwierdza Twoje podejrzenia, i modu htmlentitydefs jest dokadnie tym na co wyglda.

9.5. LOCALS I GLOBALS

175

9.5

locals i globals

locals i globals
Odejdmy teraz na minutk od przetwarzania HTML-a. Porozmawiajmy o tym, jak Python obchodzi si ze zmiennymi. Python posiada dwie wbudowane funkcje, locals i globals, ktre pozwalaj nam uzyska w sownikowy sposb dostp do zmiennych lokalnych i globalnych. Pamitasz locals? Pierwszy raz moglimy j zobaczy tutaj: def unknown_starttag(self, tag, attrs): strattrs = "".join([ %s="%s" % (key, value) for key, value in attrs]) self.pieces.append("<%(tag)s%(strattrs)s>" % locals()) Nie, czekaj, nie moesz jeszcze si uczy o locals. Najpierw, musisz nauczy si, czym s przestrzenie nazw. Przedstawimy teraz troch suchego materiau, lecz wanego, dlatego te zachowaj uwag. Python korzysta z czego, co si nazywa przestrzeni nazw (ang. namespace), aby ledzi zmienne. Przestrze nazw jest waciwie sownikiem, gdzie kluczami s nazwy zmiennych, a wartociami sownika s wartoci tych zmiennych. Moemy dosta si do przestrzeni nazw, jak do Pythonowego sownika, co zreszt zobaczymy za chwilk. Z dowolnego miejsca Pythonowego programu mamy dostp do kliku przestrzeni nazw. Kada funkcja posiada wasn przestrze nazw, nazywan lokaln przestrzeni nazw, a ktra ledzi zmienne funkcji, wczajc w to jej argumenty i lokalnie zdeniowane zmienne. Kady modu posiada wasn przestrze nazw, nazwan globaln przestrzeni nazw, a ktra ledzi zmienne moduu, wczajc w to funkcje, klasy i inne zaimportowane moduy, a take zmienne zdeniowane w tym module i stae. Jest take wbudowana przestrze nazw, dostpna z kadego moduu, a ktra przechowuje funkcje wbudowane i wyjtki. Kiedy pewna linia kodu pyta si o warto zmiennej x, Python przeszuka wszystkie przestrzenie nazw, aby j znale, w poniszym porzdku: 1. lokalna przestrze nazw okrelona dla biecej funkcji lub metody pewnej klasy. Jeli funkcja deniuje jak lokaln zmienn x, Python wykorzysta j i zakoczy szukanie. 2. przestrzeni nazw, w ktrej dana funkcja zostaa zagniedona i przestrzeniach nazw, ktre znajduj si wyej w zagniedonej hierarchii. 3. globalna przestrze nazw okrelona dla biecego moduu. Jeli modu deniuje zmienn lub klas o nazwie x, Python wykorzysta j i zakoczy szukanie. 4. wbudowana przestrze nazw globalna dla wszystkich moduw. Poniewa jest to ostatnia deska ratunku, Python przyjmie, e x jest nazw wbudowanej funkcji lub zmiennej. Jeli Python nie znajdzie x w adnej z tych przestrzeni nazw, poddaje si i wyrzuca wyjtek NameError z wiadomoci name x is not dened, ktr zobaczylimy w przykadzie 3.21, lecz nie jestemy w stanie oceni, jak Python zadziaa, zanim dostaniemy ten bd.

176

ROZDZIA 9. PRZETWARZANIE HTML-A

Python korzysta z zagniedonych przestrzeni nazw (ang. nested scope). Jeli tworzymy wewntrz pewnej funkcji inn funkcj, Python stworzy now zagniedon przestrze nazw dla zagniedonej funkcji, ktra jednoczenie bdzie lokaln przestrzeni nazw, ale jednoczenie bdziemy mogli korzysta z przestrzeni nazw funkcji, w ktrej dan funkcj zagniedamy i pozostaych przestrzeni nazw (globalnej i wbudowanej). Zmieszae si? Nie panikuj! Jest to naprawd wypane. Podobnie, jak wiele rzeczy w Pythonie, przestrzenie nazw s bezporednio dostpne podczas wykonywania programu. Jak? Do lokalnej przestrzeni nazw mamy dostp poprzez wbudowan funkcj locals, a globalna (na poziomie moduu) przestrze nazw jest dostpna poprzez wbudowan funkcj globals. Przykad 8.10 Wprowadzenie do locals >>> def foo(arg): #(1) ... x = 1 ... print locals() ... >>> foo(7) #(2) {arg: 7, x: 1} >>> foo(bar) #(3) {arg: bar, x: 1} 1. Funkcja foo posiada dwie zmienne w swojej lokalnej przestrzeni nazw: arg, ktrej warto jest przekazana do funkcji, a take x, ktra jest zdeniowana wewntrz funkcji. 2. locals zwraca sownik par nazwa/warto. Kluczami sownika s nazwy zmiennych w postaci napisw. Wartociami sownika s biece wartoci tych zmiennych. Zatem wywoujc foo z 7, wypiszemy sownik zawierajcy dwie lokalne zmienne tej funkcji, czyli arg (o wartoci 7) i x (o wartoci 1). 3. Pamitaj, Python jest dynamicznie typowany, dlatego te moemy w prosty sposb jako argument arg przekaza napis. Funkcja (a take wywoanie locals) bd nadal dziaa jak naley. locals dziaa z wszystkimi zmiennymi dowolnych typw danych. To co locals robi dla lokalnej (nalecej do funkcji) przestrzeni nazw, globals robi dla globalnej (moduu) przestrzeni nazw. globals jest bardziej ekscytujce, poniewa przestrze nazw moduu jest bardziej pasjonujca 2 . Przestrze nazw moduu nie tylko przechowuje zmienne i stae na poziomie tego moduu, lecz take funkcje i klasy zdeniowane w tym module. Ponadto doczone do tego jest cokolwiek, co zostao zaimportowane do tego moduu. Pamitasz rnic midzy from module import, a import module? Za pomoc import module, zaimportujemy sam modu, ktry zachowa wasn przestrze nazw, a to jest przyczyn, dlaczego musimy odwoa si do nazwy moduu, aby dosta si do jakiej funkcji lub atrybutu (piszc module.function). Z kolei za pomoc from module import rzeczywicie importujemy do wasnej przestrzeni nazw okrelon funkcje i atrybuty z innego moduu, a dziki temu odwoujemy si do niego bezporednio,
2 To

zdanie za wiele nie wnosi.

9.5. LOCALS I GLOBALS

177

bez wskazywania moduu, z ktrego one pochodz. Dziki funkcji globals moemy zobaczy, e rzeczywicie tak jest. Spjrzmy na poniszy blok kodu, ktry znajduje si na dole BaseHTMLProcessor.py. Przykad 8.11 Wprowadzenie do globals if __name__ == "__main__": for k, v in globals().items(): print k, "=", v #(1)

1. Na wypadek gdyby wydawao Ci si to straszne, to pamitaj, e widzielimy to ju wczeniej. Funkcja globals zwraca sownik, po ktrym nastpnie iterujemy sownik wykorzystujc metod items i wielozmienne przypisanie. Jedyn now rzecz jest funkcja globals. Teraz, uruchamiajc skrypt z linii polece otrzymamy takie wyjcie (twoje wyjcie moe si nieco rni, jest zalene od systemu i miejsca instalacji Pythona): c:\docbook\dip\py> python BaseHTMLProcessor.py SGMLParser = sgmllib.SGMLParser htmlentitydefs = <module htmlentitydefs from BaseHTMLProcessor = __main__.BaseHTMLProcessor __name__ = __main__ [...ciach ...] #(1) C:\Python23\lib\htmlentitydefs.py> #(2) #(3) #(4)

1. SGMLParser zosta zaimportowany z sgmllib, wykorzystujc from module import. Oznacza to, e zosta zaimportowany bezporednio do przestrzeni nazw moduu i w tym te miejscu jest. 2. W przeciwiestwie do SGMLParsera, htmlentitydefs zosta zaimportowany wykorzystujc instrukcj import. Oznacza to, e modu htmlentitydefs sam w sobie jest przestrzeni nazw, ale zmienna entitydefs wewntrz htmlentitydefs ju nie. 3. Modu ten deniuje jedn klas, BaseHTMLProcessor i oto ona. Dodajmy, e ta warto jest klas sam w sobie, a nie jak specyczn instancj tej klasy. 4. Pamitasz trik if name ? Kiedy uruchamiamy modu (zamiast importowa go z innego moduu), to wbudowany atrybut name ma specjaln warto, " main ". Poniewa uruchomilimy ten modu jako skrypt z linii polece, warto name wynosi " main ", dlatego te zostanie wykonany may kod testowy, ktry wypisuje globals. Korzystajc z funkcji locals i globals moemy pobra dynamicznie warto dowolnej zmiennej, dziki przekazaniu nazwy zmiennej w postaci napisu. Funkcjonalno ta jest analogiczna do getattr, ktra pozwala dosta si do dowolnej funkcji, dziki przekazaniu jej nazwy w postaci napisu. Poniej pokaemy inn wan rnic midzy funkcjami locals i globals, a o ktrej powinienimy si dowiedzie, zanim nas to uksi. Jakkolwiek to i tak Ciebie uksi, ale przynajmniej bdziesz pamita, e bya o tym mowa w tym podrczniku.

178

ROZDZIA 9. PRZETWARZANIE HTML-A

Przykad 8.12 locals jest tylko do odczytu, a globals ju nie def foo(arg): x = 1 print locals() #(1) locals()["x"] = 2 #(2) print "x=",x #(3) z = 7 print "z=",z foo(3) globals()["z"] = 8 print "z=",z

#(4) #(5)

1. Poniewa foo zostao wywoane z argumentem 3, wic zostanie wypisane {arg: 3, x: 1}. Nie powinno to by zaskoczeniem. 2. locals jest funkcj zwracajc sownik i w tym miejscu zmieniamy warto w tym sowniku. Moemy myle, e warto zmiennej x zostanie zmieniona na 2, jednak tak nie bdzie. locals waciwie nie zwraca lokalnej przestrzeni nazw, zwraca jego kopi. Zatem zmieniajc j, nie zmieniamy wartoci zmiennych w lokalnej przestrzeni nazw. 3. Zostanie wypisane x= 1, a nie x= 2. 4. Po tym, jak zostalimy poparzeni przez locals, moemy myle, e ta operacja nie zmieni wartoci z, ale w rzeczywistoci zmieni. W skutek wewntrznych rnic implementacyjnych 3 , globals zwraca aktualn, globaln przestrze nazw, a nie jej kopi; cakowicie odwrotne zachowanie w stosunku do locals. Tak wic dowolna zmiana zwrconego przez globals sownika bezporednio wpywa na zmienne globalne. 5. Wypisze z= 8, a nie z= 7.

3 Nie

bdziemy si wdawa w szczegy

9.6. FORMATOWANIE NAPISW W OPARCIU O SOWNIKI

179

9.6

Formatowanie napisw w oparciu o sowniki

Formatowanie napisw w oparciu o sowniki


Dlaczego uczylimy si na temat funkcji locals i globals? Poniewa teraz moemy si nauczy formatowania napisw w oparciu o sowniki. Jak ju mwilimy, regularne formatowanie napisw umoliwia w atwy sposb wstawianie wartoci do napisw. Wartoci s wyszczeglnione w krotce i w odpowiednim porzdku wstawione do napisu, gdzie wystpuje pole formatujce. O ile jest to skuteczne, nie zawsze tworzy kod atwy do czytania, zwaszcza, gdy zostaje wstawianych wiele wartoci. eby zrozumie o co chodzi, nie wystarczy po prostu jednorazowo przeledzi napis; trzeba cigle skaka midzy czytanym napisem, a czytan krotk wartoci. Tutaj mamy alternatywn form formatowania napisu, a ktra zamiast krotek wykorzystuje sowniki. Przykad 8.13 Formatowanie napisw w oparciu o sowniki >>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"} >>> "%(pwd)s" % params #(1) secret >>> "%(pwd)s nie jest poprawnym hasem dla %(uid)s" % params #(2) secret nie jest poprawnym hasem dla sa >>> "%(database)s of mind, %(database)s of body" % params #(3) master of mind, master of body 1. Zamiast korzysta z krotki wartoci, formujemy napis formatujcy, ktry korzysta ze sownika params. Ponadto zamiast prostego pola %s w napisie, pole zawiera nazw w nawiasach okrgych. Nazwa ta jest wykorzystana jako klucz w sowniku params i zostaje zastpione odpowiedni wartoci, secret, w miejscu wystpienia pola %(pwd)s. 2. Takie formatowanie moe posiada dowoln liczb odwoa do kluczy. Kady klucz musi istnie w podanym sowniku, poniewa inaczej formatowanie zakoczy si niepowodzeniem i zostanie rzucony wyjtek KeyError. 3. Moemy nawet wykorzysta ten sam klucz kilka razy. Kade wystpienie zostanie zastpione odpowiedni wartoci. Zatem dlaczego uywa formatowania napisu w oparciu o sowniki? Moe to wyglda na nadmierne wmieszanie sownika z kluczami i wartociami, aby wykona proste formatowanie napisu. W rzeczywistoci jest bardzo przydatne, kiedy ju si ma sownik z kluczami o sensownych nazwach i wartociach, jak np. locals. Przykad 8.14 Formatowanie napisu w BaseHTMLProcessor.py def handle_comment(self, text): self.pieces.append("<!--%(text)s-->" % locals()) #(1) 1. Formatowanie za pomoc sownikw jest powszechnie uywane z wbudowan funkcj locals. Oznacza to, e moemy wykorzystywa nazwy zmiennych lokalnych wewntrz napisu formatujcego (w tym przypadku text, ktry zosta przykazany jako argument do metody klasy) i kada nazwa zmiennej zostanie

180

ROZDZIA 9. PRZETWARZANIE HTML-A zastpiona jej wartoci. Jeli text przechowuje warto Pocztek stopki, formatowany napis !--%(text)s-->"% locals() zostanie wygenerowany jako <!--Pocztek stopki-->.

Przykad 8.15 Wicej formatowania opartego na sownikach def unknown_starttag(self, tag, attrs): strattrs = "".join([ %s="%s" % (key, value) for key, value in attrs]) self.pieces.append("<%(tag)s%(strattrs)s>" % locals()) 1. Kiedy metoda ta zostaje wywoana, attrs jest list krotek postaci klucz/warto, podobnie jak zwrcona warto metody sownika items, a to oznacza, e moemy wykorzysta wielozmienne przypisanie, aby wykona na niej iteracj. Powinnimy ju by zaznajomieni z tymi operacjami, ale jest tu tego troch duo, wic przeledmy je po kolei: (a) Przypumy, e attrs wynosi [(href, index.html), (title, Id do strony domowej)]. (b) W pierwszym przebiegu odwzorowywania listy, key przyjmie warto href, a value wemie warto index.html. (c) Formatowanie napisu %s=%s % (key, value) przeksztaci si na href=index.html. Napis ten bdzie pierwszym elementem zwrconej listy. (d) W drugim przebiegu, key przyjmie warto title, a value warto Id do strony domowej. (e) Formatowanie napisu przeksztaci to na title=Id do strony domowej. (f) Po wykonaniu wyraenia listowego, zwrcona lista bdzie przechowywaa te dwa wygenerowane napisy, a strattrs bdzie poczeniem obydwu tych elementw, czyli bdzie przechowywa href=index.html title=Go to home page. 2. Teraz formatujc napis za pomoc sownika, wstawiamy warto zmiennej tag i strattrs do napisu. Zatem jeli tag wynosi a, w ostatecznoci otrzymamy wynik <a href="index.html"title="Id do strony domowej i to nastpnie dodajemy do self.pieces. Korzystanie z sownikowego formatowania napisu i funkcji locals jest wygodnym sposobem, aby tworzy czytelniejsze skomplikowane wyraenia listowe, lecz trzeba zapaci pewn cen. Jest tutaj drobny narzut wydajnoci zwizany z wywoaniem funkcji locals, poniewa locals wykonuje kopi lokalnej przestrzeni nazw. #(1) #(2)

9.7. DODAWANIE CUDZYSOWW DO WARTOCI ATRYBUTW

181

9.7

Dodawanie cudzysoww do wartoci atrybutw

Dodawanie cudzysoww do wartoci atrybutw


Do powszechnym pytaniem na comp.lang.python jest Mam kilka dokumentw HTML z wartociami atrybutw bez cudzysoww i chciabym odpowiednio te cudzysowy doda. Jak mog to zrobi? 4 (Przewanie wynika to z doczenia do projektu nowego kierownika, bdcego wyznawc HTML-owych standardw i bezwzgldnie wymagajcego, aby wszystkie strony bezbdnie przechodziy kontrol HTML-owych walidatorw. Wartoci atrybutw bez cudzysoww s powszechnym naruszeniem HTMLwego standardu.) Niezalenie od powodu, uzupenienie cudzysoww jest atwe przy pomocy klasy BaseHTMLProcessor. BaseHTMLProcessor konsumuje HTML-a (poniewa jest potomkiem klasy SGMLParser) i produkuje rwnowany HTML, ale ten wyjciowy HTML nie jest identyczny z wejciowym. Znaczniki i nazwy atrybutw zostan zapisane maymi literami, nawet jeli wczeniej byy duymi lub wymieszanymi, a wartoci atrybutw zostan zamknite w podwjnych cudzysowach, nawet jeli wczeniej byy otoczone pojedynczymi cudzysowami lub nie miay adnych cudzysoww. To jest taki efekt uboczny, z ktrego moemy tu skorzysta. Przykad 8.16 Dodawanie cudzysoww do wartoci atrybutw >>> htmlSource = """ #(1) ... <html> ... <head> ... <title>Test page</title> ... </head> ... <body> ... <ul> ... <li><a href=index.html>Strona gwna</a></li> ... <li><a href=toc.html>Spis treci</a></li> ... <li><a href=history.html>Historia zmian</a></li> ... </body> ... </html> ... """ >>> from BaseHTMLProcessor import BaseHTMLProcessor >>> parser = BaseHTMLProcessor() >>> parser.feed(htmlSource) #(2) >>> print parser.output() #(3) <html> <head> <title>Test page</title> </head> <body> <ul> <li><a href="index.html">Strona gwna</a></li>
No dobra, to nie jest a tak powszechne pytanie. Nie jest czstsze ni Jakiego edytora powinienem uywa do pisania kodu w Pythonie? (odpowied: Emacs) lub Python jest lepszy czy gorszy od Perla? (odpowied: Perl jest gorszy od Pythona, poniewa ludzie chcieli aby by gorszy. -Larry Wall, 10/14/1998). Jednak pytania o przetwarzanie HTML-a pojawiaj si w takiej czy innej formie okoo raz na miesic i wrd tych pyta, to jest do popularne.
4

182

ROZDZIA 9. PRZETWARZANIE HTML-A

<li><a href="toc.html">Spis treci</a></li> <li><a href="history.html">Historia zmian</a></li> </body> </html> 1. Zauwamy, e wartoci atrybutw href w znacznikach <a> nie s ograniczone cudzysowami. (Jednoczenie zauwamy, e uywamy potrjnych cudzysoww do czego innego ni notki dokumentacyjnej i to bezporednio w IDE. S one bardzo uyteczne.) 2. Karmimy parser. 3. Uywajc funkcji output zdeniowanej w klasie BaseHTMLProcessor, otrzymujemy wyjcie jako pojedynczy kompletny acuch znakw ze wszystkimi wartociami atrybutw w cudzysowach. Pomylmy, jak wiele waciwie si tutaj dziao: SGMLParser sparsowa cay dokument HTML, podzieli go na znaczniki, odwoania, dane tekstowe itp.; BaseHTMLProcessor uy tych elementw do zrekonstruowania czci HTML-a (ktre nadal s skadowane w parser.pieces, jeli chcesz je zobaczy); na kocu wywoalimy parser.output, ktra to metoda poczya wszystkie czci HTML-a w jeden napis.

9.8. WPROWADZENIE DO DIALECT.PY

183

9.8

Wprowadzenie do dialect.py

Dialectizer jest prostym (i niezbyt mdrym) potomkiem klasy BaseHTMLProcessor. Dokonuje on na bloku tekstu serii podstawie, ale wszystko co znajduje si wewntrz bloku <pre>...</pre> pozostawia niezmienione. Aby obsuy bloki <pre> deniujemy w klasie Dialectizer metody: start pre i end pre. Przykad 8.17 Obsuga okrelonych znacznikw def start_pre(self, attrs): #(1) self.verbatim += 1 #(2) self.unknown_starttag("pre", attrs) #(3) def end_pre(self): self.unknown_endtag("pre") self.verbatim -= 1 #(4) #(5) #(6)

1. start pre jest wywoywany za kadym razem, gdy SGMLParser znajdzie znacznik <pre> w rdle HTML-a. (Za chwil zobaczymy dokadnie, jak to si dzieje.) Ta metoda przyjmuje jeden parametr: attrs, ktry zawiera atrybuty znacznika (jeli jakie s). attrs jest list krotek postaci klucz/warto, tak sam jak przyjmuje unknown starttag. 2. W metodzie reset, inicjalizujemy atrybut, ktry suy jako licznik znacznikw <pre>. Za kadym razem, gdy natraamy na znacznik <pre>, zwikszamy licznik, natomiast gdy natraamy na znacznik </pre> zmniejszamy licznik. (Moglibymy te uy po prostu agi i ustawia j na warto True, a nastpnie False, ale nasz sposb jest rwnie atwy, a dodatkowo obsugujemy dziwny (ale moliwy) przypadek zagniedonych znacznikw <pre>.) Za chwil zobaczymy jak mona wykorzysta ten licznik. 3. To jest ta jedyna akcja wykonywana dla znacznikw <pre>. Przekazujemy tu list atrybutw do metody unknown starttag, aby wykonaa ona domyln akcj. 4. Metoda end pre jest wywoywana za kadym razem, gdy SGMLParser znajdzie znacznik </pre>. Poniewa znaczniki kocowe nie mog mie atrybutw, ta metoda nie przyjmuje adnych parametrw. 5. Po pierwsze, chcemy wykona domyln akcj dla znacznika kocowego. 6. Po drugie, zmniejszamy nasz licznik, co sygnalizuje nam zamknicie bloku <pre>. W tym momencie warto si zagbi nieco bardziej w klas SGMLParser. Wielokrotnie stwierdzalimy, e SGMLParser wyszukuje i wywouje specyczne metody dla kadego znacznika, jeli takowe istniej. Na przykad wanie zobaczylimy denicje metod start pre i end pre do obsugi <pre> i </pre>. Ale jak to si dzieje? No c, to nie jest adna magia. To jest po prostu dobry kawaek kodu w Pythonie.

184 Przykad 8.18 SGMLParser

ROZDZIA 9. PRZETWARZANIE HTML-A

def finish_starttag(self, tag, attrs): try: method = getattr(self, start_ + tag) except AttributeError: try: method = getattr(self, do_ + tag) except AttributeError: self.unknown_starttag(tag, attrs) return -1 else: self.handle_starttag(tag, method, attrs) return 0 else: self.stack.append(tag) self.handle_starttag(tag, method, attrs) return 1 def handle_starttag(self, tag, method, attrs): method(attrs)

#(1) #(2) #(3) #(4) #(5)

#(6)

#(7)

#(8)

1. W tym momencie SGMLParser znalaz ju pocztkowy znacznik i sparsowa list atrybutw. Ostatnia rzecz jaka zostaa do zrobienia, to ustalenie czy istnieje specjalna metoda obsugi dla tego znacznika lub czy powinnimy skorzysta z metody domylnej (unknown starttag). 2. Za magi klasy SGMLParser nie kryje si nic wicej ni nasz stary przyjaciel getattr. Moe jeszcze tego wczeniej nie zauwaylimy, ale getattr poszukuje metod zdeniowanych zarwno w danym obiekcie jak i w jego potomkach. Tutaj obiektem jest self, czyli bieca instancja. A wic jeli tag przyjmie warto pre, to wywoanie getattr bdzie poszukiwao metody start pre w biecej instancji, ktr jest instancja klasy Dialectizer. 3. Metoda getattr rzuca wyjtek AttributeError, jeli metoda, ktrej szuka nie istnieje w danym obiekcie (oraz w adnym z jego potomkw), ale to jest w porzdku, poniewa wywoanie getattr zostao otoczone blokiem try...except i wyjtek AttributeError zostaje przechwycony. 4. Poniewa nie znalelimy metody start xxx, sprawdzamy jeszcze metod do xxx zanim si poddamy. Ta alternatywna grupa metod generalnie suy do obsugi znacznikw samodzielnych, jak np. <br>, ktre nie maj znacznika kocowego. Jednak moemy uywa metod z obu grup. Jak wida SGMLParser sprawdza obie grupy dla kadego znacznika. (Jednak nie powiniene deniowa obu metod obsugi start xxx i do xxx dla tego samego znacznika; wtedy i tak zostanie wywoana tylko start xxx.) 5. Nastpny wyjtek AttributeError, ktry oznacza, e kolejne wywoanie getattr odnoszce si doe do xxx take zawiodo. Poniewa nie znalelimy ani metody start xxx, ani do xxx dla tego znacznika, przechwytujemy wyjtek i wycofujemy si do metody domylnej unknown starttag.

9.8. WPROWADZENIE DO DIALECT.PY

185

6. Pamitajmy, bloki try...except mog mie take klauzul else, ktra jest wywoywana jeli nie wystpi aden wyjtek wewntrz bloku try...except. Logiczne, to oznacza, e znalelimy metod do xxx dla tego znacznika, a wic wywoujemy j. 7. A tak przy okazji nie przejmuj si tymi rnymi zwracanymi wartociami; teoretycznie one co oznaczaj, ale w praktyce nie s wykorzystywane. Nie martw si take tym self.stack.append(tag); SGMLParser ledzi samodzielnie, czy znaczniki pocztkowe s zrwnowaone z odpowiednimi znacznikami kocowymi, ale jednoczenie do niczego tej informacji nie wykorzystuje. Teoretycznie moglibymy wykorzysta ten modu do sprawdzania, czy znaczniki s cakowicie zrwnowaone, ale prawdopodobnie nie warto i wykracza to poza zakres tego rozdziau. W tej chwili masz lepsze powody do zmartwienia. 8. Metody start xxx i do xxx nie s wywoywane bezporednio. Znacznik tag, metoda method i atrybuty attrs s przekazywane do tej funkcji, czyli do handle starttag, aby klasy potomne mogy j nadpisa i tym samym zmieni sposb obsugi znacznikw pocztkowych. Nie potrzebujemy a tak niskopoziomowej kontroli, a wic pozwalamy tej metodzie zrobi swoje, czyli wywoa metody (start xxx lub do xxx) z list atrybutw. Pamitajmy, argument method jest funkcj zwrcon przez getattr, a funkcje s obiektami. (Wiem, wiem, zaczynasz mie do suchania tego w kko. Przestaniemy o tym powtarza, jak tylko zabraknie sposobw na wykorzystanie tego faktu.) Tutaj obiekt funkcji method jest przekazywany do metody jako argument, a ta metoda wywouje t funkcj. W tym momencie nie istotne jest co to jest za funkcja, jak si nazywa, gdzie jest zdeniowana; jedyna rzecz jaka jest wana, to to e jest ona wywoywana z jednym argumentem, attrs. A teraz wrmy do naszego pocztkowego programu: Dialectizer. Gdy go zostawilimy, bylimy w trakcie deniowania metod obsugi dla znacznikw <pre> i </pre>. Pozostaa ju tylko jedna rzecz do zrobienia, a mianowicie przetworzenie blokw tekstu przy pomocy zdeniowanych podstawie. W tym celu musimy nadpisa metod handle data. Przykad 8.19 Nadpisanie metody handle data def handle_data(self, text): #(1) self.pieces.append(self.verbatim and text or self.process(text)) #(2) 1. Metoda handle data jest wywoywana z tylko jednym argumentem, tekstem do przetworzenia. 2. W klasie nadrzdnej BaseHTMLProcessor metoda handle data po prostu dodaje tekst do wyjciowego bufora self.pieces. Tutaj zasada dziaania jest tylko troch bardziej skomplikowana. Jeli jestemy w bloku <pre>...</pre>, self.verbatim bdzie miao jak warto wiksz od 0 i tekst tra do bufora wyjciowego nie zmieniony. W przeciwnym razie wywoujemy oddzieln metod do wykonania podstawie i rezultat umieszczamy w buforze wyjciowym. Wykorzystujemy tutaj jednolinijkowiec, ktry wykorzystuje sztuczk and-or. Ju jeste blisko cakowitego zrozumienia Dialectizer. Ostatnim brakujcym ogniwem jest sam charakter podstawie w tekcie. Jeli znasz Perla, to wiesz, e kiedy

186

ROZDZIA 9. PRZETWARZANIE HTML-A

wymagane s kompleksowe zmiany w tekcie, to jedynym prawdziwym rozwizaniem s wyraenia regularne. Klasy w dalszej czci dialect.py deniuje seri wyrae regularnych, ktre operuj na tekcie pomidzy znacznikami HTML. My ju mamy przeanalizowany cay rozdzia o wyraeniach regularnych. Zapewne nie masz ochoty znowu mozoli si z wyraeniami regularnymi, prawda? Ju wystarczajco duo si nauczylimy, jak na jeden rozdzia.

9.9. PRZETWARZANIE HTML-A - WSZYSTKO RAZEM

187

9.9

Przetwarzanie HTML-a - wszystko razem

Wszystko razem
Nadszed czas, aby poczy w cao wiedz, ktr zdobylimy do tej pory. Przykad 8.20 Funkcja translate, cz 1 def translate(url, dialectName="chef"): import urllib sock = urllib.urlopen(url) htmlSource = sock.read() sock.close() #(1) #(2) #(3)

1. Funkcja translate przyjmuje opcjonalny argument dialectName, ktry jest acuchem znakw okrelajcym uywany dialekt. Zaraz zobaczymy, jak to jest wykorzystywane. 2. Moment, tam jest wana instrukcja w tej funkcji! Jest to w peni dozwolone w Pythonie dziaanie. Instrukcja import uywalimy zwykle na samym pocztku programu, aby zaimportowany modu by dostpny w dowolnym miejscu. Ale moemy take importowa moduy w samej funkcji, przez co bd one dostpne tylko z jej poziomu. Jeeli jakiego moduy potrzebujemy uy tylko w jednej funkcji, jest to najlepszy sposb aby zachowa modularno twojego programu. (Docenisz to, gdy okae si, e twj weekendowy hack wyrs na wace 800 linii dzieo sztuki, a ty wanie zdecydujesz si podzieli to na mniejsze czci). 3. Tutaj otwieramy poczenie i do zmiennej htmlSource pobieramy rdo HTML spod wskazanego adresu URL. Przykad 8.21 Funkcja translate, cz 2: coraz ciekawiej parserName = "%sDialectizer" % dialectName.capitalize() #(1) parserClass = globals()[parserName] #(2) parser = parserClass() #(3) 1. capitalize jest metod acucha znakw, z ktr si jeszcze nie spotkalimy; zmienia ona pierwszy znak na wielk liter, a wszystkie pozostae znaki na mae litery. W poczeniu z prostym formatowaniem napisu, nazwa dialektu zamieniana jest na nazw odpowiadajcej mu klasy. Jeeli dialectName ma warto chef, parserName przyjmie warto ChefDialectizer. 2. W tym miejscu mamy nazw klasy (w zmiennej parserName) oraz dostp do globalnej przestrzeni nazw, poprzez sownik globals(). czc obie informacje dostajemy referencje do klasy o okrelonej nazwie. (Pamitajmy, e klasy s obiektami i mog by przypisane do zmiennej, jak kady inny obiekt). Jeeli parserName ma warto ChefDialectizer, parserClass bdzie klas ChefDialectizer. 3. Ostatecznie, majc obiekt klasy (parserClass) chcemy zainicjowa t klas. Wiemy ju jak zrobi po prostu wywoujemy klas w taki sposb, jakby bya to funkcja. Fakt, e klasa jest przechowywana w lokalnej zmiennej nie robi adnej

188

ROZDZIA 9. PRZETWARZANIE HTML-A rnicy, po prostu wywoujemy lokaln zmienn jak funkcj, i na wyjciu wyskakuje instancja klasy. Jeeli parserClass jest klas ChefDialectizer, parser bdzie instancj klasy ChefDialectizer.

Zastanawiasz si, poniewa istniej tylko 3 klasy Dialectizer, dlaczego by nie uy po prostu instrukcji case? (W porzdku, w Pythonie nie ma instrukcji case, ale zawsze mona uy serii instrukcji if). Z jednego powodu: elastycznoci programu. Funkcja translate nie ma pojcia, jak wiele zdeniowalimy podklas Dialectizera. Wyobramy sobie, e deniujemy jutro now klas FooDialectizer funkcja translate zadziaa bez przerbek. Nawet lepiej wyobramy sobie, e umieszczasz klas FooDialectizer w osobnym module i importujesz j poprzez from module import. Jak wczeniej moglimy si przekona, taka operacja doczy to do globals(), wic funkcja translate nadal bdzie dziaa prawidowo bez koniecznoci dokonywania modykacji, nawet wtedy, gdy FooDialectizer znajdzie si w oddzielnym pliku. Teraz wyobramy sobie, e nazwa dialektu pochodzi skd z poza programu, moe z bazy danych lub z wartoci wprowadzonej przez uytkownika. Moemy uy jakkolwiek ilo pythonowych skryptw po stronie serwera, aby dynamicznie generowa strony internetowe; taka funkcja mogaby przekaza URL i nazw dialektu (oba w postaci acucha znakw) w zapytania dania strony internetowej, i zwrci przetumaczon stron. Na koniec wyobramy sobie framework Dialectizer z wbudowan obsug pluginw. Moemy umieci kad podklas Dialectizer-a w osobnym pliku pozostawiajc jedynie w pliku dialect.py funkcj translate. Jeeli zachowasz stay schemat nazewnictwa klas, funkcja translate moe dynamicznie importowa potrzebn klas z odpowiedniego pliku, jedynie na podstawie podanej nazwy dialektu. (Dynamicznego importowanie omwimy to w dalszej czci tego podrcznika). Aby doda nowy dialekt, wystarczy, e utworzymy odpowiednio nazwany plik (np. foodialect.py zawierajcy klas FooDialectizer) w katalogu z plug-inami. Wywoujc funkcj translate z nazw dialektu foo, odnajdzie ona modu foodialect.py i automatycznie zaimportuje klas FooDialectizer. Przykad 8.22 Funkcja translate, cz 3 parser.feed(htmlSource) #(1) parser.close() #(2) return parser.output() #(3) 1. Po tym caym wyobraaniu sobie, co robio si ju nudne, mamy funkcj feed, ktra przeprowadza ca transformacj. Poniewa cae rdo HTML-a mamy w jednym acuchu znakw, wic funkcj feed wywoujemy tylko raz. Oczywicie moemy wywoywa j dowoln ilo razy, a parser za kadym razem przeprowadzi transformacj. Na przykad, jeeli obawiamy si o zuycie pamici (albo wiemy, e bdziemy parsowali naprawd wielkie strony HTML), moemy umieci t funkcj w ptli, w ktrej bdziemy odczytywa tylko kawaek HTML-a i karmi nim parser. Efekty bd takie same. 2. Poniewa funkcja feed wykorzystuje wewntrzny bufor, powinnimy zawsze po zakoczeniu operacji wywoa funkcj close() parsera (nawet jeeli przesalimy parserowi cao za jednym razem). W przeciwnym wypadku moemy stwierdzi, e w otrzymanym wyniku brakuje kilku ostatnich bajtw.

9.9. PRZETWARZANIE HTML-A - WSZYSTKO RAZEM

189

3. Pamitajmy, e funkcja output, ktr zdeniowalimy samodzielnie w klasie BaseHTMLProcessor, czy wszystkie zbuforowane kawaki i zwraca cao w postaci pojedynczego acucha znakw. I wanie w taki sposb, przetumaczylimy stron internetow, podajc jedynie jej adres URL i nazw dialektu.

190

ROZDZIA 9. PRZETWARZANIE HTML-A

9.10

Przetwarzanie HTML-a - podsumowanie

Podsumowanie
Python dostarcza potne narzdzie do operowania na HTML-u bibliotek sgmllib.py, ktra obudowuje kod HTML w model obiektowy. Moemy uywa tego narzdzia na wiele sposobw: parsujc HTML w poszukiwania specycznych informacji gromadzc wyniki, np. tak jak to robi URL lister. modykujc struktur w dowolny sposb, np. dodawa cudzysowy do atrybutw transformujc HTML w inny format, poprzez manipulowanie tekstem bez ruszania znacznikw, np. tak jak nasz Dialectizer Po tych wszystkich przykadach, powinnimy umie wykonywa wszystkie z tych operacji: Uywa odpowiednio locals() i globals(), aby dosta si do przestrzeni nazw Formatowa acuchy w oparciu o sowniki

Rozdzia 10

Przetwarzanie XML-a

191

192

ROZDZIA 10. PRZETWARZANIE XML-A

10.1

Przetwarzanie XML-a

Nurkujemy
Kolejne dwa rozdziay s na temat przetwarzania XML-a w Pythonie. Bdzie to przydatne, jeli ju wiesz, jak wygldaj dokumenty XML, a ktre s wykonane ze strukturalnych znacznikw okrelajcych hierarchi elementw itp. Jeli nic z tego nie rozumiesz, moesz przeczyta co na ten temat na Wikipedii. Nawet jeli nie interesuje Ciebie temat XML-a i tak dobrze by byo przeczyta te rozdziay, poniewa omawiaj one wiele wanych tematw jak pakiety, argumenty linii polece, a take jak wykorzystywa getattr jako porednik metod. Bycie magistrem lozoi nie jest wymagane, chocia jeli kiedy spotkalimy si z tekstami napisanymi przez Immanuel Kanta, lepiej zrozumiemy przykadowy program, ni jeli byby specjalist w czym przydatnym, jak informatyka. Mamy dwa sposoby pracy z XML-em. Jeden jest nazywany SAX (Simple API for XML), ktry dziaa w ten sposb, e czyta przez chwil dokument XML i wywouje dla kadego odnalezionego elementu odpowiednie metody. (Jeli przeczytalimy rozdzia 8, powinno to wyglda znajomo, poniewa w taki sposb pracuje modu sgmllib.) Inny jest nazywany DOM (Document Object Model ), a pracuje w ten sposb, e jednorazowo czyta cay dokument XML i tworzy wewntrzn reprezentacj, wykorzystujc klasy Pythona powizane w struktur drzewa. Python posiada standardowe moduy do obydwu sposobw parsowania, ale rozdzia ten opisze tylko, jak wykorzystywa DOM. Poniej znajduje si kompletny program Pythona, ktry generuje pseudolosowe wyjcie oparte na gramatyce bezkontekstowej zdeniowanej w formacie XML. Nie przejmujmy si, jeli nie zrozumielimy, co to znaczy. Bdziemy gbiej bada zarwno wejcie programu, jak i jego wyjcie w tym i nastpnym rozdziale. Przykad 9.1 kgp/kgp.py u"""Generator Kanta dla Pythona Generuje pseudofilozofi opart na gramatyce bezkontekstowej Uycie: python kgp.py [options] [source] Opcje: -g ..., --grammar=... -h, --help -d

uywa okrelonego pliku gramatyki lub adres URL wywietla ten komunikat pomocy wywietla informacje debugowania podczas parsowania

Przykady: kgp.py generuje kilka akapitw z filozofi Kanta kgp.py -g husserl.xml generuje kilka akapitw z filozofi Husserla kpg.py "<xref id=paragraph/>" generuje akapit Kanta kgp.py template.xml czyta template.xml, aby okreli, co ma generowa """ from xml.dom import minidom import random import toolbox

10.1. NURKOWANIE import sys import getopt debug = 0 class NoSourceError(Exception): pass

193

class KantGenerator(object): u"""generuje pseudofilozofi opart na gramatyce bezkontekstowej""" def init (self, grammar, source=None): self.loadGrammar(grammar) self.loadSource(source and source or self.getDefaultSource()) self.refresh()

def load(self, source): u"""wczytuje XML-owe rdow wejcia, zwraca sparsowany dokument XML - adres URL z plikiem XML ("http://diveintopython.org/kant.xml") - nazw lokalnego pliku XML ("~/diveintopython/common/py/kant.xml") - standardowe wejcie ("-") - biecy dokument XML w postaci acucha znakw """ sock = toolbox.openAnything(source) xmldoc = minidom.parse(sock).documentElement sock.close() return xmldoc def loadGrammar(self, grammar): u"""wczytuje gramatyk bezkontekstow""" self.grammar = self. load(grammar) self.refs = {} for ref in self.grammar.getElementsByTagName("ref"): self.refs[ref.attributes["id"].value] = ref def loadSource(self, source): u"""wczytuje rdo source""" self.source = self. load(source) def getDefaultSource(self): u"""zgaduje domylne rdo biecej gramatyki Domylnym rdem bdzie jeden z <ref>-w, do ktrego nic si nie odwouje. Moe brzmi to skomplikowanie, ale tak naprawd nie jest. Przykad: Domylnym rdem dla kant.xml jest "<ref id=section/>", poniewa section jest jednym <ref>-em, ktry nie jest nigdzie <xref>-em w gramatyce. W wielu gramatykach, domylne rdo bdzie tworzyo najdusze (i najbardziej interesujce) wyjcie. """

194

ROZDZIA 10. PRZETWARZANIE XML-A xrefs = {} for xref in self.grammar.getElementsByTagName("xref"): xrefs[xref.attributes["id"].value] = 1 xrefs = xrefs.keys() standaloneXrefs = [e for e in self.refs.keys() if e not in xrefs] if not standaloneXrefs: raise NoSourceError, "cant guess source, and no source specified" return <xref id="%s"/> % random.choice(standaloneXrefs) def reset(self): u"""resetuje parser""" self.pieces = [] self.capitalizeNextWord = 0 def refresh(self): u"""resetuje bufor wyjciowy, ponownie parsuje cay plik rdowy i zwraca wyjcie Poniewa parsowanie dosy duo korzysta z przypadkowoci, jest to atwy sposb, aby otrzyma nowe wyjcie bez potrzeby ponownego wczytywania pliku gramatyki. """ self.reset() self.parse(self.source) return self.output() def output(self): u"""wyjciowy, wygenerowany tekst""" return "".join(self.pieces) def randomChildElement(self, node): u"""wybiera przypadkowy potomek wza Jest to uyteczna funkcja wykorzystywana w do xref i do choice. """ choices = [e for e in node.childNodes if e.nodeType == e.ELEMENT NODE] chosen = random.choice(choices) if debug: sys.stderr.write(%s available choices: %s\n % \ (len(choices), [e.toxml() for e in choices])) sys.stderr.write(Chosen: %s\n % chosen.toxml()) return chosen def parse(self, node): u"""parsuje pojedynczy wze XML Parsowany dokument XML (from minidom.parse) jest drzewem wzw zoonym z rnych typw. Kady wze reprezentuje instancj odpowiadajcej jej klasy Pythona (Element dla znacznika, Text

10.1. NURKOWANIE

195

dla danych tekstowych, Document dla dokumentu). Ponisze wyraenie konstruuje nazw klasy opartej na typie wza, ktry parsujemy ("parse Element" dla wza o typie Element, "parse Text" dla wza o typie Text itp.), a nastpnie wywouje te metody. """ parseMethod = getattr(self, "parse %s" % node. class . name ) parseMethod(node) def parse Document(self, node): u"""parsuje wze dokumentu Wze dokument sam w sobie nie jest interesujcy (przynajmnie dla nas), ale jego jedyne dziecko, node.documentElement jest: jest gwnym wzem gramatyki. """ self.parse(node.documentElement) def parse Text(self, node): u"""parsuje wze tekstowy Tekst wza tekstowego jest zazwyczaj dodawany bez zmiany do wyjciowego bufora. Jedynym wyjtkiem jest to, e <p class=sentence> ustawia flag, aby pierwsz liter nastpnego sowa bya wielka. Jeli ta flaga jest ustawiona, pierwsz liter tekstu robimy wielk i resetujemy t flag. """ text = node.data if self.capitalizeNextWord: self.pieces.append(text[0].upper()) self.pieces.append(text[1:]) self.capitalizeNextWord = 0 else: self.pieces.append(text) def parse Element(self, node): u"""parsuje element XML-owy element odpowiada biecemu znacznikowi rda: <xref id=...>, <p chance=...>, <choice> itp. Kady typ elementu jest obsugiwany za pomoc odpowiedniej, wasnej metody. Podobnie jak to robilimy w parse(), konstruujemy nazw metody opartej na nazwie elementu ("do xref" dla znacznika <xref> itp.), a potem wywoujemy t metod. """ handlerMethod = getattr(self, "do %s" % node.tagName) handlerMethod(node) def parse Comment(self, node): u"""parsuje komentarz Gramatyka moe zawiera komentarze XML, ale my je pominiemy

196 """ pass

ROZDZIA 10. PRZETWARZANIE XML-A

def do xref(self, node): u"""obsuguje znacznik <xref id=...>

Znacznik <xref id=...> jest odwoaniem do znacznika <ref id=...>. Znacznik <xref id=sentence/> powoduje to, e zostaje wybrany w przypadkowy spo potomek znacznika <ref id=sentence>. """ id = node.attributes["id"].value self.parse(self.randomChildElement(self.refs[id])) def do p(self, node): u"""obsuguje znacznik <p> Znacznik <p> jest jdrem gramatyki. Moe zawiera niemal wszystko: tekst w dowolnej formie, znaczniki <choice>, znaczniki <xref>, a nawet inne znaczniki <p>. Jeli atrybut "class=sentence" zostanie znaleziony, flaga zostaje ustawiona i nastpne sowo bdzie zapisane du liter. Jeli zostanie znaleziony atrybut "chance=X", to mamy X% szansy, e znacznik zostanie wykorzystany (i mamy (100-X)% szansy, e zostanie cakowicie pominity) """ keys = node.attributes.keys() if "class" in keys: if node.attributes["class"].value == "sentence": self.capitalizeNextWord = 1 if "chance" in keys: chance = int(node.attributes["chance"].value) doit = (chance > random.randrange(100)) else: doit = 1 if doit: for child in node.childNodes: self.parse(child) def do choice(self, node): u"""obsuguje znacznik <choice> Znacznik <choice> zawiera jeden lub wicej znacznikw <p>. Jeden znacznik <p> zostaje wybrany przypadkowo i jest nastpnie wykorzystywany do generowania tekstu wyjciowego. """ self.parse(self.randomChildElement(node)) def usage(): print doc def main(argv): grammar = "kant.xml"

10.1. NURKOWANIE

197

try: opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="]) except getopt.GetoptError: usage() sys.exit(2) for opt, arg in opts: if opt in ("-h", "--help"): usage() sys.exit() elif opt == -d: global debug debug = 1 elif opt in ("-g", "--grammar"): grammar = arg source = "".join(args) k = KantGenerator(grammar, source) print k.output() if name == " main ": main(sys.argv[1:])

Przykad 9.2 kgp/toolbox.py u"""Rnorodne uyteczne funkcje""" def openAnything(source): u"""URI, nazwa pliku lub acuch znakw --> strumie Funkcja ta pozwala zdefiniowa parser, ktry przyjmuje dowolne rdo wejcia (URL, ciek do lokalnego pliku lub znajdujcego si gdzie w sieci, czy te biece dane w postaci acucha znakw) i traktuje je w odpowiedni sposb. Zwracany obiekt bdzie zawiera wszystkie podstawowe metody odczytu (read, readline, readlines). Kiedy ju obiekt nie bdzie potrzebny, naley go zamkn za pomoc metody .close(). Przykady: >>> from xml.dom import minidom >>> sock = openAnything("http://localhost/kant.xml") >>> doc = minidom.parse(sock) >>> sock.close() >>> sock = openAnything("c:\\inetpub\\wwwroot\\kant.xml") >>> doc = minidom.parse(sock) >>> sock.close() >>> sock = openAnything("<ref id=conjunction><text>and</text><text>or</text></ref>") >>> doc = minidom.parse(sock) >>> sock.close() """

198 if hasattr(source, "read"): return source if source == "-": import sys return sys.stdin

ROZDZIA 10. PRZETWARZANIE XML-A

# prbuje otworzy za pomoc moduu urllib (gdy source jest plikiem # dostpnym z http, ftp lub URL-a) import urllib try: return urllib.urlopen(source) except (IOError, OSError): pass # prbuje otworzy za pomoc wbudowanej funkcji open (jeli source jest ciek # do lokalnego pliku) try: return open(source) except (IOError, OSError): pass # traktuje source jako acuch znakw import StringIO return StringIO.StringIO(str(source)) Uruchom sam program kgp.py, ktry bdzie parsowa domyln, opart na XML gramatyk w kgp/kant.xml, a nastpnie wypisze kilka lozocznych akapitw w stylu Immanuela Kanta. Przykad 9.3 Przykadowe wyjcie kgp/kgp.py [you@localhost kgp]$ python kgp.py As is shown in the writings of Hume, our a priori concepts, in reference to ends, abstract from all content of knowledge; in the study of space, the discipline of human reason, in accordance with the principles of philosophy, is the clue to the discovery of the Transcendental Deduction. The transcendental aesthetic, in all theoretical sciences, occupies part of the sphere of human reason concerning the existence of our ideas in general; still, the never-ending regress in the series of empirical conditions constitutes the whole content for the transcendental unity of apperception. What we have alone been able to show is that, even as this relates to the architectonic of human reason, the Ideal may not contradict itself, but it is still possible that it may be in contradictions with the employment of the pure employment of our hypothetical judgements, but natural causes (and I assert that this is the case) prove the validity of the discipline of pure reason. As we have already seen, time (and it is obvious that this is true) proves the validity of time, and the architectonic of human reason, in the full sense of these terms,

10.1. NURKOWANIE

199

abstracts from all content of knowledge. I assert, in the case of the discipline of practical reason, that the Antinomies are just as necessary as natural causes, since knowledge of the phenomena is a posteriori. The discipline of human reason, as I have elsewhere shown, is by its very nature contradictory, but our ideas exclude the possibility of the Antinomies. We can deduce that, on the contrary, the pure employment of philosophy, on the contrary, is by its very nature contradictory, but our sense perceptions are a representation of, in the case of space, metaphysics. The thing in itself is a representation of philosophy. Applied logic is the clue to the discovery of natural causes. However, what we have alone been able to show is that our ideas, in other words, should only be used as a canon for the Ideal, because of our necessary ignorance of the conditions. [...ciach...] Jest to oczywicie kompletny bekot. No dobra, nie cakowity bekot. Jest skadniowo i gramatycznie poprawny (chocia bardzo wielomwny). Niektre fragmenty mog by rzeczywicie prawd (lub przy najmniej z niektrymi Kant by si zgodzi), a niektre s ewidentnie nieprawdziwe, a wiele fragmentw jest po prostu niespjnych. Lecz wszystko jest w stylu Immanuela Kanta. Interesujc rzecz w tym programie jest to, e nie ma tu nic, co okrela Kanta. Caa zawarto poprzedniego przykadu pochodzi z pliku gramatyki, kgp/kant.xml. Jeli kaemy programowi wykorzysta inny plik gramatyki (ktry moemy okreli z linii polece), wyjcie bdzie kompletnie rne. Przykad 9.4 Proste wyjcie kgp/kgp.py [you@localhost kgp]$ python kgp.py -g binary.xml 00101001 [you@localhost kgp]$ python kgp.py -g binary.xml 10110100

200

ROZDZIA 10. PRZETWARZANIE XML-A

10.2
Pakiety

Pakiety

W rzeczywistoci przetwarzanie dokumentu XML jest bardzo proste, wystarczy jedna linia kodu. Jednake, zanim dojdziemy do tej linii kodu, bdziemy musieli krtko omwi, czym s pakiety. Przykad 9.5 adowanie dokumentu XML >>> from xml.dom import minidom #(1) >>> xmldoc = minidom.parse(~/diveintopython/common/py/kgp/binary.xml) 1. Tej skadni jeszcze nie widzielimy. Wyglda to niemal, jak from module import, ktry znamy i kochamy, ale z . wyglda na co wyszego i innego ni proste import. Tak na prawd xml jest czym, co jest znane pod nazw pakiet (ang. package), dom jest zagniedonym pakietem wewntrz xml-a, a minidom jest moduem znajdujcym si wewntrz xml.dom. Brzmi to skomplikowanie, ale tak naprawd nie jest. Jeli spojrzymy na konkretn implementacj, moe nam to pomc. Pakiet to niewiele wicej ni katalog z moduami, a zagniedone pakiety s podkatalogami. Moduy wewntrz pakietu (lub zagniedonego pakietu) s nadal zwykymi plikami .py z wyjtkiem tego, e s w podkatalogu, zamiast w gwnym katalogu lib/ instalacji Pythona. Przykad 9.6 Plikowa struktura pakietu Python21/ katalog gwny instalacji Pythona (katalog domowy plikw wykonywalnych) | +lib/ katalog bibliotek (katalog domowy standardowych moduow) | + xml/ pakiet xml (w rzeczywistoci katalog z innymi rzeczami wewntrz niego) | +sax/ pakiet xml.sax (ponownie, po prostu katalog) | +dom/ pakiet xml.dom (zawiera minidom.py) | +parsers/ pakiet xml.parsers (uywany wewntrznie) Dlatego kiedy powiesz from xml.dom import minidom, Python zrozumie to jako znajd w katalogu xml katalog dom, a nastpnie szukaj tutaj moduu minidom i zaimportuj go jako minidom. Lecz Python jest nawet mdrzejszy; nie tylko moemy zaimportowa cay modu zawarty wewntrz pakietu, ale take moemy wybirczo zaimportowa wybrane klasy czy funkcje z moduu znajdujcego si wewntrz pakietu. Moemy take zaimportowa sam pakiet jako modu. Skadnia bdzie taka sama; Python wywnioskuje, co masz na myli na podstawie struktury plikw pakietu i automatycznie wykona poprawn czynno. Przykad 9.7 Pakiety take s moduami

10.2. PAKIETY

201

>>> from xml.dom import minidom #(1) >>> minidom <module xml.dom.minidom from C:\Python21\lib\xml\dom\minidom.pyc> >>> minidom.Element <class xml.dom.minidom.Element at 01095744> >>> from xml.dom.minidom import Element #(2) >>> Element <class xml.dom.minidom.Element at 01095744> >>> minidom.Element <class xml.dom.minidom.Element at 01095744> >>> from xml import dom #(3) >>> dom <module xml.dom from C:\Python21\lib\xml\dom\__init__.pyc> >>> import xml #(4) >>> xml <module xml from C:\Python21\lib\xml\__init__.pyc> 1. W tym miejscu importujemy modu (minidom) z zagniedonego pakietu (xml.dom). W wyniku tego minidom zosta zaimportowany do naszej przestrzeni nazw. Aby si odwoa do klasy wewntrz tego moduu (np. Element), bdziemy musieli nazw klasy poprzedzi nazw moduu. 2. Tutaj importujemy klas (Element) z moduu (minidom), a ten modu z zagniedonego pakietu (xml.dom). W wyniku tego Element zosta zaimportowany bezporednio do naszje przestrzeni nazw. Dodajmy, e nie koliduje to z poprzednim importem; teraz do klasy Element moemy si odwoywa na dwa sposoby (lecz nadal jest to ta sama klasa). 3. W tym miejscu importujemy pakiet dom (zagniedony pakiet xml-a) jako sam w sobie modu. Dowolny poziom pakietu moe by traktowany jako modu, co zreszt zobaczymy za moment. Moe nawet mie swoje wasne atrybuty i metody, tak jak moduy, ktre widzielimy wczeniej. 4. Tutaj importujemy jako modu gwny poziom pakietu xml. Wic jak moe pakiet (ktry na dysku jest katalogiem) zosta zaimportowany i traktowany jako modu (ktry jest zawsze plikiem na dysku)? Odpowiedzi jest magiczny plik init .py. Wiemy, e pakiety nie s po prostu katalogami, ale s one katalogami ze specycznym plikiem wewntrz, init .py. Plik ten deniuje atrybuty i metody tego pakietu. Na przykad xml.dom posiada klas Node, ktra jest zdeniowana w xml/dom/ init .py. Kiedy importujemy pakiet jako modu (np. dom z xml-a), to tak naprawd importujemy jego plik init .py. Pakiet jest katalogiem ze specjalnym plikiem init .py wewntrz niego. Plik init .py deniuje atrybuty i metody pakietu. Nie musi on deniowa niczego, moe by nawet pustym plikiem, lecz musi istnie. Jeli nie istnieje plik init .py, katalog jest tylko katalogiem, a nie pakietem, wic nie moe by zaimportowany lub zawiera moduw, czy te zagniedonych pakietw. Wic dlaczego mczy si z pakietami? Umoliwiaj one logiczne pogrupowanie powizanych ze sob moduw. Zamiast stworzenia pakietu xml z wewntrznymi pakietami sax i dom, autorzy mogliby umieci ca funkcjonalno sax w xmlsax.py, a

202

ROZDZIA 10. PRZETWARZANIE XML-A

ca funkcjonalno dom w xmldom.py, czy te nawet zamieci wszystko w pojedynczym module. Jednak byoby to niewygodne (podczas pisania tego podrcznika pakiet xml posiada prawie 6000 linii kodu) i trudne w zarzdzaniu (dziki oddzielnym plikom rdowym, wiele osb moe rwnoczenie pracowa nad rnymi czciami). Jeli kiedykolwiek bdziemy planowali napisa wielki podsystem w Pythonie (lub co bardziej prawdopodobne, kiedy zauwaymy, e nasz may podsystem rozrs si do duego), zainwestujmy troch czasu w zaprojektowanie dobrej architektury systemu pakietw. Jest to jedna z wielu rzeczy w Pythonie, w ktrych jest dobry, wic skorzystajmy z tej zalety.

10.3. PARSOWANIE XML-A

203

10.3

Parsowanie XML-a

Parsowanie XML
Jak ju mwilimy, parsowanie XML-a waciwie jest bardzo proste: jedna linijka kodu. Co z tym zrobimy dalej, to ju zaley wycznie od nas samych. Przykad 9.8 adowanie dokumentu XML (tym razem naprawd) >>> from xml.dom import minidom >>> xmldoc = minidom.parse(~/zanurkuj_w_pythonie/py/kgp/binary.xml) >>> xmldoc <xml.dom.minidom.Document instance at 010BE87C> >>> print xmldoc.toxml() <?xml version="1.0" ?> <grammar> <ref id="bit"> <p>0</p> <p>1</p> </ref> <ref id="byte"> <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\ <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p> </ref> </grammar> 1. Jak ju widzielimy w poprzednim podrozdziale, ta instrukcja importuje modu minidom z pakietu xml.dom. 2. Tutaj jest ta jedna linia kodu, ktra wykonuje ca robot: minidom.parse pobiera jeden argument i zwraca sparsowan reprezentacj dokumentu XML. Argumentem moe by wiele rzeczy; w tym wypadku jest to po prostu nazwa pliku dokumentu XML na lokalnym dysku. (Aby kontynuowa musimy zmieni ciek tak, aby wskazywaa na katalog, w ktrym przechowujemy pobrane z sieci przykad.) Moemy take jako parametr przekaza obiekt pliku lub nawet obiekt plikopodobny (ang. le-like object). Skorzystamy z tej elastycznoci pniej w tym rozdziale. 3. Obiektem zwrconym przez minidom.parse jest obiekt Document, ktry jest klas pochodn klasy Node. Ten obiekt Document jest korzeniem zoonej struktury drzewiastej poczonych ze sob obiektw Pythona, ktra w peni reprezentuje dokument XML przekazany funkcji minidom.parse. 4. toxml jest metod klasy Node (a zatem jest te dostpna w obiekcie Document otrzymanym z minidom.parse). toxml wypisuje XML reprezentowany przez dany obiekt Node. Dla wza, ktrym jest obiekt Document, wypisuje ona cay dokument XML. Skoro ju mamy dokument XML w pamici, moemy zacz po nim wdrowa. #(1) #(2) #(3) #(4)

204

ROZDZIA 10. PRZETWARZANIE XML-A

Przykad 9.9 Pobieranie wzw potomnych >>> xmldoc.childNodes #(1) [<DOM Element: grammar at 17538908>] >>> xmldoc.childNodes[0] #(2) <DOM Element: grammar at 17538908> >>> xmldoc.firstChild #(3) <DOM Element: grammar at 17538908> 1. Kady wze posiada atrybut childNodes, ktry jest list obiektw Node. Obiekt Document zawsze ma tylko jeden wze potomny, element gwny (korze) dokumentu XML (w tym przypadku element grammar). 2. Aby dosta si do pierwszego (i w tym wypadku jedynego) wza potomnego, uywamy po prostu zwykej skadni do obsugi list. Pamitajmy, tu nie dzieje si nic nadzwyczajnego; to jest po prostu zwyka lista Pythona zwykych pythonowych obiektw. 3. Poniewa pobieranie pierwszego wza potomnego danego wza jest bardzo uyteczn i czst czynnoci, klasa Node posiada atrybut firstChild, ktry jest synonimem dla childNodes[0]. (Jest te atrybut lastChild, ktry jest synonimem dla childNodes[-1].) Przykad 9.10 Metoda toxml dziaa w kadym wle >>> grammarNode = xmldoc.firstChild >>> print grammarNode.toxml() #(1) <grammar> <ref id="bit"> <p>0</p> <p>1</p> </ref> <ref id="byte"> <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\ <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p> </ref> </grammar> 1. Poniewa metoda toxml jest zdeniowana w klasie Node, jest ona dostpna w kadym wle XML-a, nie tylko w elemencie Document. Przykad 9.11 Wzami potomnymi moe by take tekst >>> grammarNode.childNodes #(1) [<DOM Text node "\n">, <DOM Element: ref at 17533332>, \ <DOM Text node "\n">, <DOM Element: ref at 17549660>, <DOM Text node "\n">] >>> print grammarNode.firstChild.toxml() #(2)

>>> print grammarNode.childNodes[1].toxml() #(3) <ref id="bit">

10.3. PARSOWANIE XML-A

205

<p>0</p> <p>1</p> </ref> >>> print grammarNode.childNodes[3].toxml() #(4) <ref id="byte"> <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\ <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p> </ref> >>> print grammarNode.lastChild.toxml() #(5) 1. Patrzc na XML w kgp/binary.xml, moglibymy pomyle, e wze grammar ma tylko dwa wzy potomne, czyli dwa elementy ref. Ale chyba o czym zapominamy: o znakach koca linii! Za elementem <grammar> i przed pierwszym <ref> jest znak koca linii i zalicza si on do wzw potomnych elementu grammar. Podobnie jest te znak koca linii po kadym </ref>; to take zalicza si do wzw potomnych. Tak wic grammar.childNodes jest waciwie list 5 obiektw: 3 obiekty Text i 2 obiekty Element. 2. Pierwszym potomkiem jest obiekt Text reprezentujcy znak koca linii za znacznikiem <grammar> i przed pierwszym <ref>. 3. Drugim potomkiem jest obiekt Element reprezentujcy pierwszy element ref. 4. Czwartym potomkiem jest obiekt Element reprezentujcy drugi element ref. 5. Ostatnim potomkiem jest obiekt Text reprezentujcy znak koca linii za znacznikiem kocowym </ref> i przed znacznikiem kocowym </grammar>. Przykad 9.12 Drenie a do tekstu >>> grammarNode <DOM Element: grammar at 19167148> >>> refNode = grammarNode.childNodes[1] >>> refNode <DOM Element: ref at 17987740> >>> refNode.childNodes [<DOM Text node "\n">, <DOM Text node " <DOM Text node "\n">, <DOM Text node " <DOM Element: p at 19462036>, <DOM Text >>> pNode = refNode.childNodes[2] >>> pNode <DOM Element: p at 19315844> >>> print pNode.toxml() <p>0</p> >>> pNode.firstChild <DOM Text node "0"> >>> pNode.firstChild.data u0

#(1)

#(2) ">, <DOM Element: p at 19315844>, \ ">, \ node "\n">]

#(3) #(4) #(5)

1. Jak ju widzielimy w poprzednim przykadzie, pierwszym elementem ref jest grammarNode.childNodes[1], poniewa childNodes[0] jest wzem typu Text dla znaku koca linii.

206

ROZDZIA 10. PRZETWARZANIE XML-A

2. Element ref posiada swj zbir wzw potomnych, jeden dla znaku koca linii, oddzielny dla znakw spacji, jeden dla elementu p i tak dalej. 3. Moesz uy metody toxml nawet tutaj, gboko wewntrz dokumentu. 4. Element p ma tylko jeden wze potomny (nie moemy tego zobaczy na tym przykadzie, ale spjrzmy na pNode.childNodes jeli nie wierzymy) i jest nim obiekt Text dla pojednyczego znaku 0. 5. Atrybut .data wza Text zawiera rzeczywisty napis, jaki ten tekstowy wze reprezentuje. Zauwamy, e wszystkie dane tekstowe przechowywane s w unikodzie.

10.4. WYSZUKIWANIE ELEMENTW

207

10.4

Wyszukiwanie elementw

Wyszukiwanie elementw
Przemierzanie dokumentu XML poprzez przechodzenie przez kady wze z osobna mogoby by nuce. Jeli poszukujesz czego szczeglnego, co jest zagrzebane gboko w dokumencie XML, istnieje skrt, ktrego moesz uy, aby znale to szybko: getElementsByTagName. W tym podrozdziale uywa bdziemy pliku gramatyki kgp/binary.xml, ktry wyglda tak: Przykad 9.20 binary.xml <span><?xml version="1.0"?> <!DOCTYPE grammar PUBLIC "-//diveintopython.org//DTD Kant Generator Pro v1.0//EN" "kgp.dtd"> <grammar> <ref id="bit"> <p>0</p> <p>1</p> </ref> <ref id="byte"> <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\ <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p> </ref> </grammar></span> Zawiera on dwa elementy ref: bit i byte. bit moe przyjmowa wartoci 0 lub 1, a byte moe si skada z omiu bitw. Przykad 9.21 Wprowadzenie do getElementsByTagName >>> from xml.dom import minidom >>> xmldoc = minidom.parse(binary.xml) >>> reflist = xmldoc.getElementsByTagName(ref) #(1) >>> reflist [<DOM Element: ref at 136138108>, <DOM Element: ref at 136144292>] >>> print reflist[0].toxml() <ref id="bit"> <p>0</p> <p>1</p> </ref> >>> print reflist[1].toxml() <ref id="byte"> <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\ <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p> </ref> 1. getElementsByTagName przyjmuje jeden argument: nazw elementu, ktry chcemy znale. Zwraca list obiektw Element, odpowiedni do znalezionych elementw XML posiadajcych podan nazw. W tym przypadku znalelimy dwa elementy ref.

208

ROZDZIA 10. PRZETWARZANIE XML-A

Przykad 9.22 Kady element moemy przeszukiwa >>> firstref = reflist[0] #(1) >>> print firstref.toxml() <ref id="bit"> <p>0</p> <p>1</p> </ref> >>> plist = firstref.getElementsByTagName("p") #(2) >>> plist [<DOM Element: p at 136140116>, <DOM Element: p at 136142172>] >>> print plist[0].toxml() #(3) <p>0</p> >>> print plist[1].toxml() <p>1</p> 1. Kontynuujc poprzedni przykad, pierwszy obiekt naszej listy reflist jest elementem ref bit. 2. Moemy uy tej samej metody getElementsByTagName na tym obiekcie klasy Element, aby znale wszystkie elementy p wewntrz tego elementu ref bit. 3. Tak jak poprzednio metoda getElementsByTagName zwraca list wszystkich elementw jakie znajdzie. W tym przypadku mamy dwa, po jednym na kady bit. Przykad 9.23 Przeszukiwanie jest waciwie rekurencyjne >>> plist = xmldoc.getElementsByTagName("p") #(1) >>> plist [<DOM Element: p at 136140116>, <DOM Element: p at 136142172>, <DOM Element: p at 136146124>] >>> plist[0].toxml() #(2) <p>0</p> >>> plist[1].toxml() <p>1</p> >>> plist[2].toxml() #(3) <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\ <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p> 1. Zauwamy rnic pomidzy tym i poprzednim przykadem. Poprzednio szukalimy elementw p wewntrz firstref, lecz teraz szukamy elementw p wewntrz xmldoc, czyli obiektu najwyszego poziomu reprezentujcego cay dokument XML. To wyszukiwanie znajduje elementy p zagniedone wewntrz elementw ref wewntrz gwnego elementu gramatyki. 2. Pierwsze dwa elementy p znajduj si wewntrz pierwszego elementu ref (element ref bit). 3. Ostatni element p, to ten wewntrz drugiego elementu ref (element ref byte).

10.5. DOSTP DO ATRYBUTW ELEMENTW

209

10.5

Dostp do atrybutw elementw

Dostp do atrybutw elementw


Elementy XML-a mog mie jeden lub wiele atrybutw i jest niewiarygodnie atwo do nich dotrze, gdy dokument XML zosta ju sparsowany. W tym podrozdziale bdziemy korzysta z pliku kgp/binary.xml, ktry ju widzielimy w poprzednim podrozdziale. Podrozdzia ta moe by lekko zagmatwany z powodu nakadajcej si terminologii. Elementy dokumentu XML maj atrybuty, a obiekty Pythona take maj atrybuty. Gdy parsujemy dokument XML, otrzymujemy jak grup obiektw Pythona, ktre reprezentuj wszystkie czci dokumentu XML, a niektre z tych obiektw Pythona reprezentuj atrybuty elementw XML-a. Jednak te obiekty (Python), ktre reprezentuj atrybuty (XML), take maj atrybuty (Python), ktre s uywane do pobierania rnych czci atrybutu (XML), ktry ten obiekt reprezentuje. Uprzedzalimy, e to jest troch zagmatwane.

Przykad 9.24 Dostp do atrybutw elementw >>> xmldoc = minidom.parse(binary.xml) >>> reflist = xmldoc.getElementsByTagName(ref) >>> bitref = reflist[0] >>> print bitref.toxml() <ref id="bit"> <p>0</p> <p>1</p> </ref> >>> bitref.attributes #(1) <xml.dom.minidom.NamedNodeMap instance at 0x81e0c9c> >>> bitref.attributes.keys() #(2) (3) [uid] >>> bitref.attributes.values() #(4) [<xml.dom.minidom.Attr instance at 0x81d5044>] >>> bitref.attributes["id"] #(5) <xml.dom.minidom.Attr instance at 0x81d5044> 1. Kady obiekt Element ma atrybut o nazwie attributes, ktry jest obiektem klasy NamedNodeMap. Brzmi gronie, ale takie nie jest, poniewa obiekt NamedNodeMap jest obiektem dziaajcym jak sownik, a wic ju wiemy, jak go uywa. 2. Traktujc obiekt NamedNodeMap jak sownik, moemy pobra list nazw atrybutw tego elementu uywajc attributes.keys(). Ten element ma tylko jeden atrybut: id. 3. Nazwy atrybutw, jak kady inny tekst w dokumencie XML, s zapisane w postaci unikodu. 4. Znowu traktujc NamedNodeMap jak sownik, moemy pobra list wartoci atrybutw uywajc attributes.values(). Wartoci same w sobie take s obiektami typu Attr. Jak wydoby uyteczne informacje z tego obiektu zobaczymy w nastpnym przykadzie.

210

ROZDZIA 10. PRZETWARZANIE XML-A

5. Nadal traktujc NamedNodeMap jak sownik, moemy dotrze do poszczeglnych atrybutw poprzez ich nazwy, uywajc normalnej skadni dla sownikw. (Szczeglnie uwani czytelnicy ju wiedz jak klasa NamedNodeMap realizuje ten fajny trik: poprzez denicj metody specjalnej o nazwie getitem . Inni czytelnicy mog pocieszy si faktem, i nie musz rozumie jak to dziaa, aby uywa tego efektywnie.) Przykad 9.25 Dostp do poszczeglnych atrybutw >>> a = bitref.attributes["id"] >>> a <xml.dom.minidom.Attr instance at 0x81d5044> >>> a.name #(1) uid >>> a.value #(2) ubit 1. Obiekt Attr w caoci reprezentuje pojedynczy atrybut XML-a pojedynczego elementu XML-a. Nazwa atrybutu (ta sama, ktrej uylimy do znalezienia tego obiektu w bitref.attributes pseudo-sownikowym obiekcie NamedNodeMap) znajduje si w a.name. 2. Waciwa warto tekstowa tego atrybutu XML-a znajduje si w a.value. Podobnie jak w sowniku atrybuty elementu XML-a nie s uporzdkowane. Moe si zdarzy, e w oryginalnym dokumencie XML atrybuty bd uoone w pewnym okrelonym porzdku i moe si zdarzy, e obiekty Attr bd rwnie uoone w takim samym porzdku po sparsowaniu dokumentu do obiektw Pythona, ale te uporzdkowania s tak naprawd przypadkowe i nie powinny mie adnego specjalnego znaczenia. Zawsze powiniennimy odwoywa si do poszczeglnych atrybutw poprzez nazw, tak jak do elementw sownika poprzez klucz.

10.6. PODSUMOWANIE

211

10.6

Przetwarzanie XML-a - podsumowanie

Podsumowanie
OK, to by byo na tyle cikich tematw o XML-u. Nastpny rozdzia bdzie nadal wykorzystywa te same przykadowe programy, ale bdzie zwraca uwag na inne aspekty, ktre sprawiaj, e program jest bardziej elastyczny: wykorzystywanie strumieni do przetwarzania wejcia, uywanie funkcji getattr jako porednika, a take korzystanie z ag w linii polece, aby pozwoli uytkownikom skongurowa program bez zmieniania kodu rdowego. Przed przejciem do nastpnego rozdziau, powinnimy nie mie problemw z: parsowaniem dokumentw XML za pomoc minidom-a, przeszukiwaniem sparsowanego dokumentu, a take z dostpem do dowolnego atrybutu uporzdkowywaniem zoonych bibliotek w pakiety

212

ROZDZIA 10. PRZETWARZANIE XML-A

Rozdzia 11

Skrypty i strumienie

213

214

ROZDZIA 11. SKRYPTY I STRUMIENIE

11.1

Abstrakcyjne rda wejcia

Abstrakcyjne rda wejcia


Jedn z najwaniejszych moliwoci Pythona jest jego dynamiczne wizanie, a jednym z najbardziej przydatnych przykadw wykorzystania tego jest obiekt plikopodobny (ang. le-like object). Wiele funkcji, ktre wymagaj jakiego rda wejcia, mogyby po prostu przyjmowa jako argument nazw pliku, nastpnie go otwiera, czyta, a na kocu go zamyka. Jednak tego nie robi. Zamiast dziaa w ten sposb, jako argument przyjmuj obiekt pliku lub obiekt plikopodobny. W najprostszym przypadku obiekt plikopodobny jest dowolnym obiektem z metod read, ktra przyjmuje opcjonalny parametr wielkoci, size, a nastpnie zwraca acuch znakw. Kiedy wywoujemy go bez parametru size, odczytuje wszystko, co jest do przeczytania ze rda wejcia, a potem zwraca te wszystkie dane jako pojedynczy acuch znakw. Natomiast kiedy wywoamy metod read z parametrem size, to odczyta ona tyle bajtw ze rda wejcia, ile wynosi warto size, a nastpnie zwrci te dane. Kiedy ponownie wywoamy t metod, zostanie odczytana i zwrcona dalsza porcja danych (czyli dane bd czytane od miejsca, w ktrym wczeniej skoczono czyta). Powyej opisalimy, w jaki sposb dziaaj prawdziwe pliki. Jednak nie musimy si ogranicza do prawdziwych plikw. rdem wejcia moe by wszystko: plik na dysku, strona internetowa, czy nawet jaki acuch znakw. Dopki przekazujemy do funkcji obiekt plikopodobny, a funkcja ta po prostu wywouje metod read, to funkcja moe obsuy dowolny rodzaj wejcia, bez posiadania jakiego specjalnego kodu dla kadego rodzaju wejcia. Moe si zastanawiamy, co ma to wsplnego z przetwarzaniem XML-a? Ot minidom.parse jest tak funkcj, do ktrej moemy przekaza obiekt plikopodobny. Przykad 10.1 Parsowanie XML-u z pliku >>> from xml.dom import minidom >>> fsock = open(binary.xml) #(1) >>> xmldoc = minidom.parse(fsock) #(2) >>> fsock.close() #(3) >>> print xmldoc.toxml() #(4) <?xml version="1.0" ?> <grammar> <ref id="bit"> <p>0</p> <p>1</p> </ref> <ref id="byte"> <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\ <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p> </ref> </grammar> 1. Najpierw otwieramy plik z dysku. Otrzymujemy przez to obiekt pliku. 2. Przekazujemy obiekt pliku do funkcji minidom.parse, ktra wywouje metod read z fsock i czyta dokument XML z tego pliku.

11.1. ABSTRAKCYJNE RDA WEJCIA

215

3. Koniecznie wywoujemy metod close obiektu pliku, jak ju skoczylimy na nim prac. minidom.parse nie zrobi tego za nas. 4. Wywoujc ze zwrconego dokumentu XML metod toxml(), wypisujemy cay dokument. Dobrze, to wszystko wyglda jak kolosalne marnotrawstwo czasu. W kocu ju wczeniej widzielimy, e minidom.parse moe przyj jako argument nazw pliku i wykona ca robot z otwieraniem i zamykaniem automatycznie. Prawd jest, e jeli chcemy sparsowa lokalny plik, moemy przekaza nazw pliku do minidom.parse, a funkcja ta bdzie umiaa mdrze to wykorzysta. Lecz zauwamy jak podobne i atwe jest take parsowanie dokumentu XML pochodzcego bezporednio z Internetu. Przykad 10.2 Parsowanie XML-a z URL-a >>> import urllib >>> usock = urllib.urlopen(http://slashdot.org/slashdot.rdf) >>> xmldoc = minidom.parse(usock) >>> usock.close() >>> print xmldoc.toxml() <?xml version="1.0" ?> <rdf:RDF xmlns="http://my.netscape.com/rdf/simple/0.9/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns\#"> <channel> <title>Slashdot</title> <link>http://slashdot.org/</link> <description>News for nerds, stuff that matters</description> </channel> <image> <title>Slashdot</title> <url>http://images.slashdot.org/topics/topicslashdot.gif</url> <link>http://slashdot.org/</link> </image> <item> <title>To HDTV or Not to HDTV?</title> <link>http://slashdot.org/article.pl?sid=01/12/28/0421241</link> </item> [...ciach...] 1. Jak ju zaobserwowalimy w poprzednim rozdziale, urlopen przyjmuje adres URL strony internetowej i zwraca obiekt plikopodobny. Ponadto, co jest bardzo wane, obiekt ten posiada metod read, ktra zwraca rdo danej strony internetowej. 2. Teraz przekazujemy ten obiekt plikopodobny do minidom.parse, ktra posusznie wywouje metod read i parsuje dane XML, ktre zostaj zwrcone przez read. Fakt, e te dane przychodz teraz bezporednio z Internetu, jest kompletnie #(1) #(2) #(3) #(4)

216

ROZDZIA 11. SKRYPTY I STRUMIENIE nieistotny. minidom.parse nie ma o stronach internetowych adnego pojcia; on tylko wie co o obiektach plikopodobnych.

3. Jak tylko obiekt plikopodobny, ktry podarowa nam urlopen, nie bdzie potrzebny, koniecznie zamykamy go. 4. Przy okazji, ten URL jest prawdziwy i on naprawd jest dokumentem XML. Reprezentuje on aktualne nagwki, techniczne newsy i plotki w Slashdocie. Przykad 10.3 Parsowanie XML-a z acucha znakw (prosty sposb, ale mao elastyczny) >>> contents = "<grammar><ref id=bit><p>0</p><p>1</p></ref></grammar>" >>> xmldoc = minidom.parseString(contents) #(1) >>> print xmldoc.toxml() <?xml version="1.0" ?> <grammar><ref id="bit"><p>0</p><p>1</p></ref></grammar> 1. minidom posiada metod parseString, ktra przyjmuje cay dokument XML w postaci acucha znakw i parsuje go. Moemy j wykorzysta zamiast minidom.parse, jeli wiemy, e posiadamy cay dokument w formie acucha znakw. OK, to moemy korzysta z funkcji minidom.parse zarwno do parsowania lokalnych plikw jak i odlegych URL-w, ale do parsowania acuchw znakw wykorzystujemy... inn funkcj. Oznacza to, e jeli chcielibymy, aby nasz program mg da wyjcie z pliku, adresu URL lub acucha znakw, potrzebujemy specjalnej logiki, aby sprawdzi czy mamy do czynienia z acuchem znakw, a jeli tak, to wywoa funkcj parseString zamiast parse. Jakie to niesatysfakcjonujce... Gdyby tylko by sposb, aby zmieni acuch znakw na obiekt plikopodobny, to moglibymy po prostu przekaza ten obiekt do minidom.parse. I rzeczywicie, istnieje modu specjalnie zaprojektowany do tego: StringIO. Przykad 10.4 Wprowadzenie do StringIO >>> contents = "<grammar><ref id=bit><p>0</p><p>1</p></ref></grammar>" >>> import StringIO >>> ssock = StringIO.StringIO(contents) #(1) >>> ssock.read() #(2) "<grammar><ref id=bit><p>0</p><p>1</p></ref></grammar>" >>> ssock.read() #(3) >>> ssock.seek(0) #(4) >>> ssock.read(15) #(5) <grammar><ref i >>> ssock.read(15) "d=bit><p>0</p" >>> ssock.read() ><p>1</p></ref></grammar> >>> ssock.close() #(6) 1. Modu StringIO zawiera tylko jedn klas, take nazwan StringIO, ktra pozwala zamieni napis w obiekt plikopodobny. Klasa StringIO podczas tworzenia instancji przyjmuje jako parametr acuch znakw.

11.1. ABSTRAKCYJNE RDA WEJCIA

217

2. Teraz ju mamy obiekt plikopodobny i moemy robi wszystkie moliwe plikopodobne operacje. Na przykad read, ktra zwraca oryginalny acuch. 3. Wywoujc ponownie read otrzymamy pusty napis. W ten sposb dziaa prawdziwy obiekt pliku; kiedy ju zostanie przeczytany cay plik, nie mona czyta wicej bez wyranego przesunicia do pocztku pliku. Obiekt StringIO pracuje w ten sam sposb. 4. Moemy jawnie przesun si do pocztku napisu, podobnie jak moemy si przesun w pliku, wykorzystujc metod seek obiektu klasy StringIO. 5. Moemy take czyta fragmentami acuch znakw, dziki przekazaniu parametr wielkoci size do metody read. 6. Za kadym razem, kiedy wywoamy read, zostanie nam zwrcona pozostaa cz napisu, ktra nie zostaa jeszcze przeczytana. W dokadnie ten sam sposb dziaa obiekt pliku. Przykad 10.5 Parsowanie XML-a z acucha znakw (sposb z obiektem plikopodobnym) >>> contents = "<grammar><ref id=bit><p>0</p><p>1</p></ref></grammar>" >>> ssock = StringIO.StringIO(contents) >>> xmldoc = minidom.parse(ssock) #(1) >>> ssock.close() >>> print xmldoc.toxml() <?xml version="1.0" ?> <grammar><ref id="bit"><p>0</p><p>1</p></ref></grammar> 1. Teraz moemy przekaza obiekt plikopodobny (w rzeczywistoci instancj StringIO) do funkcji minidom.parse, ktra z kolei wywoa metod read z tego obiektu plikopodobnego i szczliwie wszystko przeparsuje, nie zdajc sobie nawet sprawy, e wejcie to pochodzi z acucha znakw. To ju wiemy, jak za pomoc pojedynczej funkcji, minidom.parse, sparsowa dokument XML przechowywany na stronie internetowej, lokalnym pliku, czy w acuchu znakw. Dla strony internetowej wykorzystamy urlopen, aby dosta obiekt plikopodobny; dla lokalnego pliku, wykorzystamy open; a w przypadku acucha znakw skorzystamy z StringIO. Lecz teraz pjdmy troch do przodu i uoglnijmy te te rnice. Przykad 10.6 openAnything def openAnything(source): #(1) # prbuje otworzy za pomoc urllib (jeli source jest URL-em do http, ftp itp.) import urllib try: return urllib.urlopen(source) #(2) except (IOError, OSError): pass # prbuje otworzy za pomoc wbudowanej funkcji open (gdy source jest ciek do pliku)

218 try: return open(source) except (IOError, OSError): pass

ROZDZIA 11. SKRYPTY I STRUMIENIE

#(3)

# traktuje source jako acuch znakw z danymi import StringIO return StringIO.StringIO(str(source)) #(4) 1. Funkcja openAnything przyjmuje pojedynczy argument, source, i zwraca obiekt plikopodobny. source jest acuchem znakw o rnym charakterze. Moe si odnosi do adresu URL (np. http://slashdot.org/slashdot.rdf), moe by globaln lub lokaln ciek do pliku (np. binary.xml), czy te acuchem znakw przechowujcym dokument XML, ktry ma zosta sparsowany. 2. Najpierw sprawdzamy, czy source jest URL-em. Robimy to brutalnie: prbujemy otworzy to jako URL i cicho pomijamy bdy spowodowane prb otworzenia czego, co nie jest URL-em. Jest to waciwie eleganckie w tym sensie, e jeli urllib bdzie kiedy obsugiwa nowe typy URL-i, nasz program take je obsuy i to bez koniecznoci zmiany kodu. Jeli urllib jest w stanie otworzy source, to return wykopie nas bezporednio z funkcji i ponisze instrukcje try nie bd nigdy wykonywane. 3. W innym przypadku, gdy urllib wrzasn na nas i powiedzia, e source nie jest poprawnym URL-em, zakadamy, e jest to cieka do pliku znajdujcego si na dysku i prbujemy go otworzy. Ponownie, nic nie robimy, by sprawdzi, czy source jest poprawn nazw pliku (zasady okrelajce poprawno nazwy pliku s znaczco rne na rnych platformach, dlatego prawdopodobnie i tak bymy to le zrobili). Zamiast tego, na lepo otwieramy plik i cicho pomijamy wszystkie bdy. 4. W tym miejscu zakadamy, e source jest acuchem znakw, ktry przechowuje dokument XML (poniewa nic innego nie zadziaao), dlatego wykorzystujemy StringIO, aby utworzy obiekt plikopodobny i zwracamy go. (Tak naprawd, poniewa wykorzystujemy funkcj str, source nie musi by nawet acuchem znakw; moe by nawet dowolnym obiektem, a z ktrego zostanie wykorzystana jego tekstowa reprezentacja, a ktra jest zdeniowana przez specjaln metod str .) Teraz moemy wykorzysta funkcj openAnything w poczeniu z minidom.parse, aby utworzy funkcj, ktra przyjmuje rdo source, ktre w jaki sposb odwouje si do dokumentu XML (moe to robi za pomoc adresu URL, lokalnego pliku, czy te dokumentu przechowywanego jako acuch znakw), i parsuje je. Przykad 10.7 Wykorzystanie openAnything w kgp/kgp.py class KantGenerator: def _load(self, source): sock = toolbox.openAnything(source) xmldoc = minidom.parse(sock).documentElement sock.close() return xmldoc

11.2. STANDARDOWY STRUMIE WEJCIA, WYJCIA I BDW

219

11.2

Standardowy strumie wejcia, wyjcia i bdw

Standardowy strumie wejcia, wyjcia i bdw


Uytkownicy Uniksa s ju prawdopodobnie zapoznani z koncepcj standardowego wejcia, standardowego wyjcia i standardowego strumienia bdw. Ten podrozdzia jest dla pozostaych osb. Standardowe wyjcie i strumie bdw (powszechnie uywana skrcona forma to stdout i stderr ) s strumieniami danych wbudowanymi do kadego systemu Unix. Kiedy co wypisujemy, idzie to do strumienia stdout; kiedy wystpi bd w programie, a program wypisze informacje pomocne przy debugowaniu (jak traceback w Pythonie), to wszystko pjdzie do strumienia stderr. Te dwa strumienie s zwykle poczone z oknem terminala, na ktrym pracujemy, wic jeeli program co wypisuje, zobaczymy to na wyjciu, a kiedy program spowoduje bd, zobaczymy informacje debugujce. (Jeli pracujemy w systemie z okienkowym IDE Pythona, stdout i stderr domylnie bd poczone z interaktywnym oknem.) Przykad 10.8 Wprowadzenie do stdout i stderr >>> for i in range(3): ... print Nurkujemy #(1) Nurkujemy Nurkujemy Nurkujemy >>> import sys >>> for i in range(3): ... sys.stdout.write(Nurkujemy) #(2) NurkujemyNurkujemyNurkujemy >>> for i in range(3): ... sys.stderr.write(Nurkujemy) #(3) NurkujemyNurkujemyNurkujemy 1. Jak zobaczylimy w przykadzie 6.9, Prosty licznik, moemy wykorzysta wbudowan funkcje range, aby zbudowa prost ptl licznikow, ktra powtarza pewn operacj okrelon liczb razy. 2. stdout jest obiektem plikopodobnym; wywoujc jego funkcj write bdziemy wypisywa na wyjcie napis, ktry przekazalimy. W rzeczywisto, to wanie funkcja print naprawd robi; dodaje ona znak nowej linii do wypisywanego napisu, a nastpnie wywouje sys.stdout.write. 3. W tym prostym przypadku stdout i stderr wysyaj wyjcie do tego samego miejsca: do IDE Pythona (jeli jestemy w nim) lub do terminala (jeli mamy uruchomionego Pythona z linii polece). Podobnie jak stdout, stderr nie dodaje znaku nowej linii za nas; jeli chcemy, aby ten znak zosta dodany, musimy to zrobi sami. Zarwno stdout i stderr s obiektami plikopodobnymi, a ktre omawialimy w podrozdziale 10.1, Abstrakcyjne rda wejcia, lecz te s tylko do zapisu. Nie posiadaj one metody read, tylko write. Jednak nadal s one obiektami plikopodobnymi i

220

ROZDZIA 11. SKRYPTY I STRUMIENIE

moemy do nich przypisa inny obiekt pliku lub obiekt plikopodobny, aby przekierowa ich wyjcie. Przykad 10.9 Przekierowywanie wyjcia [you@localhost kgp]$ python stdout.py Nurkujemy [you@localhost kgp]$ cat out.log Ta wiadomo bdzie logowana i nie zostanie wypisana na wyjcie (W Windowsie moemy wykorzysta polecenie type, zamiast cat, aby wywietli zawarto pliku.) #-*- coding: utf-8 -*#stdout.py import sys print Nurkujemy saveout = sys.stdout fsock = open(out.log, w) sys.stdout = fsock print Ta wiadomo bdzie logowana i nie zostanie wypisana na wyjcie sys.stdout = saveout fsock.close() 1. To zostanie wypisane w interaktywnym oknie IDE (lub w terminalu, jeli skrypt zosta uruchomiony z linii polece). 2. Zawsze, zanim przekierujemy standardowe wyjcie, przypisujemy gdzie stdout, dziki temu, bdziemy potem mogli do niego normalnie wrci. 3. Otwieramy plik do zapisu. Jeli plik nie istnieje, zostanie utworzony. Jeli istnieje, zostanie nadpisany. 4. Cae pniejsze wyjcie zostanie przekierowane do pliku, ktry wanie otworzylimy. 5. Zostanie to wypisane tylko do pliku out.log; nie bdzie widoczne w oknie IDE lub w terminalu. 6. Przywracamy stdout do pocztkowej, oryginalnej postaci. 7. Zamykamy plik out.log. Dodajmy, e w wypisywanym acuchu znakw uylimy polskich znakw, a poniewa nie skorzystalimy z unikodu, wic napis ten zostanie wypisany w takiej samej postaci, w jakiej zosta zapisany w pliku Pythona (czyli wiadomo zostanie zapisana w kodowaniu utf-8). Gdybymy skorzystali z unikodu, musielibymy wyranie zakodowa ten napis do jakiego kodowania za pomoc metody encode, poniewa Python nie wie, z jakiego kodowania chce korzysta utworzony przez nas plik (plik out.log przypisany do zmiennej stdout). Przekierowywanie standardowego strumienia bdw (stderr ) dziaa w ten sam sposb, wykorzystujc sys.stderr, zamiast sys.stdout. #(1) #(2) #(3) #(4) #(5) #(6) #(7)

11.2. STANDARDOWY STRUMIE WEJCIA, WYJCIA I BDW Przykad 10.10 Przekierowywanie informacji o bdach [you@localhost kgp]$ python stderr.py [you@localhost kgp]$ cat error.log Traceback (most recent line last): File "stderr.py", line 6, in ? raise Exception(ten bd bdzie logowany) Exception: ten bd bdzie logowany #stderr.py #-*- coding: utf-8 -*import sys fsock = open(error.log, w) #(1) sys.stderr = fsock #(2) raise Exception(ten bd bdzie logowany) #(3) (4)

221

1. Otwieramy plik error.log, gdzie chcemy przechowywa informacje debugujce. 2. Przekierowujemy standardowy strumie bdw, dziki przypisaniu obiektu nowo otwartego pliku do sys.stderr. 3. Rzucamy wyjtek. Zauwamy, e na ekranie wyjciowym nic nie zostanie wypisane. Wszystkie informacje traceback zostay zapisane w error.log. 4. Zauwamy take, e nie zamknlimy jawnie pliku error.log, a nawet nie przypisalimy do sys.stderr jego pierwotnej wartoci. To jest wspaniae, e kiedy program si rozwali (z powodu wyjtku), Python wyczyci i zamknie wszystkie pliki za nas. Nie ma adnej rnicy, czy stderr zostanie przywrcony, czy te nie, poniewa program si rozwala, a Python koczy dziaanie. Przywrcenie wartoci do oryginalnej, jest bardziej wane dla stdout, jeli zamierzasz pniej wykonywa jakie inne operacje w tym samym skrypcie. Poniewa powszechnie wypisuje si informacje o bdach na standardowy strumie bdw, Python posiada skrtow skadnie, ktra mona wykorzysta do bezporedniego przekierowywania wyjcia. Przykad 10.11 Wypisywanie do stderr >>> print wchodzimy do funkcji wchodzimy do funkcji >>> import sys >>> print >> sys.stderr, wchodzimy do funkcji #(1) wchodzimy do funkcji 1. Ta skrtowa skadnia wyraenia print moe by wykorzystywana do pisania do dowolnego, otwartego pliku, lub do obiektu plikopodobnego. W tym przypadku, moemy przekierowa pojedyncz instrukcj print do stderr bez wpywu na nastpne instrukcje print. Z innej strony, standardowe wejcia jest obiektem pliku tylko do odczytu i reprezentuje dane przechodzce z niektrych wczeniejszych programw. Prawdopodobnie

222

ROZDZIA 11. SKRYPTY I STRUMIENIE

nie jest to zrozumiae dla klasycznych uytkownikw Mac OS-a lub nawet dla uytkownikw Windows, ktrzy nie mieli za wiele do czynienia z lini polece MS-DOS-a. Dziaa to w ten sposb, e konstruujemy cig polece w jednej linii, w taki sposb, e to co jeden program wypisuje na wyjcie, nastpny w tym cigu traktuje jako wejcie. Pierwszy program prosto wypisuje wszystko na standardowe wyjcie (bez korzystania ze specjalnych przekierowa, wykorzystuje normaln instrukcj print itp.), a nastpny program czyta ze standardowego wejcia, a system operacyjny udostpnia poczenie pomidzy wyjciem pierwszego programu, a wyjciem kolejnego. Przykad 10.12 Cig polece [you@localhost kgp]$ python kgp.py -g binary.xml #(1) 01100111 [you@localhost kgp]$ cat binary.xml #(2) <?xml version="1.0"?> <!DOCTYPE grammar PUBLIC "-//diveintopython.org//DTD Kant Generator Pro v1.0//EN" "kgp.dtd"> <grammar> <ref id="bit"> <p>0</p> <p>1</p> </ref> <ref id="byte"> <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\ <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p> </ref> </grammar> [you@localhost kgp]$ cat binary.xml | python kgp.py -g - #(3) (4) 10110001 1. Jak zobaczylimy w podrozdziale 9.1, Nurkujemy, polecenie to wywietli cig omiu przypadkowych bitw, 0 i 1. 2. Dziki temu po prostu wypiszemy ca zawarto pliku binary.xml. (Uytkownicy Windowsa powinni wykorzysta polecenie type zamiast cat.) 3. Polecenie to wypisuje zawarto pliku kgp/binary.xml, ale znak | (ang. pipe), oznacza, e standardowe wyjcie nie zostanie wypisana na ekran. Zamiast tego, zawarto standardowego wyjcia zostanie wykorzystane jako standardowe wejcie nastpnego programu, ktry w tym przypadku jest skryptem Pythona. 4. Zamiast okrela moduu (np. binary.xml), dajemy -, ktry kae naszemu skryptowi wczyta gramatyk ze standardowego wejcia, zamiast z okrelonego pliku na dysku. (Wicej o tym, w jaki sposb to si dzieje w nastpnym przykadzie.) Zatem efekt bdzie taki sam, jak w pierwszym poleceniu, gdzie bezporednio okrelamy plik gramatyki, ale tutaj zwrmy uwag na rozszerzone moliwoci. Zamiast wywoywa cat binary.xml, moglibymy uruchomi skrypt, ktry by dynamicznie generowa gramatyk, a nastpnie mgby j doprowadzi do naszego skryptu. Dane mogyby przyj skdkolwiek: z bazy danych, innego skryptu generujcego gramatyk lub jeszcze inaczej. Zalet tego jest to, e nie musimy zmienia w aden sposb kgp/kgp.py, aby doczy jak funkcjonalno.

11.2. STANDARDOWY STRUMIE WEJCIA, WYJCIA I BDW

223

Jedynie, co potrzebujemy, to moliwo wczytania gramatyki ze standardowego wejcia, a ca logik dodatkowej funkcjonalnoci moemy rozdzieli wewntrz innego programu. Wic w jaki sposb skrypt wie, eby czyta ze standardowego wejcia, gdy plik gramatyki to -? To nie jest adna magia; to tylko wanie prosty kod. Przykad 10.13 Czytanie ze standardowego wejcia w kgp/kgp.py def openAnything(source): if source == "-": #(1) import sys return sys.stdin # try to open with urllib (if source is http, ftp, or file URL) import urllib try: [... ciach ...] 1. Jest to funkcja openAnything z kgp/toolbox.py, ktr wczeniej badalimy w podrozdziale 10.1, Abstrakcyjne rda wejcia. Wszystko, co musimy zrobi, to dodanie trzech linii kodu na pocztku, aby sprawdzi, czy rdem nie jest -"; jeli tak, to zwracamy sys.stdin. Naprawd, to tylko tyle! Pamitasz, stdin jest obiektem plikopodobnym z metod read, wic pozosta cz kodu (w kgp/kgp.py, gdzie wywoujemy funkcj openAnything) w aden sposb nie zmieniamy.

224

ROZDZIA 11. SKRYPTY I STRUMIENIE

11.3

Buforowanie odszukanego wza

Buforowanie odszukanego wza


kgp/kgp.py stosuje kilka sztuczek, ktre mog, lecz nie musz, by uyteczne przy przetwarzaniu XML-a. Pierwsza z nich wykorzystuje spjn struktur dokumentw wejciowych do utworzenia bufora wzw. Plik gramatyki deniuje szereg elementw ref. Kady z nich zawiera jeden lub wicej elementw p, ktre mog zawiera wiele rnych rzeczy, wcznie z elementami xref. Gdy napotykamy element xref, wyszukujemy odpowiedni element ref z tym samym atrybutem id i wybieramy jeden z elementw potomnych elementu ref i parsujemy go. (W nastpnym podrozdziale zobaczymy jak dokonywany jest ten losowy wybr.) W taki sposb rozwijamy gramatyk: deniujemy elementy ref dla najmniejszych czci, nastpnie deniujemy elementy ref, ktre zawieraj te pierwsze elementy ref poprzez uycie xref itd. Potem parsujemy najwiksz referencj, przechodzimy po kolei do kadego elementu xref i ostatecznie generujemy prawdziwy tekst. Ten wygenerowany tekst zaley od tej (losowej) decyzji podjtej przy wypenianiu elementu xref, a wic efekt moe by inny za kadym razem. To wszystko jest bardzo elastyczne, ale ma jedn wad: wydajno. Gdy napotkamy element xref i potrzebujemy odszuka odpowiedniego dla niego elementu ref, to pojawia si problem. Element xref ma atrybut id i chcemy odszuka element ref, ktry ma taki sam atrybut id, ale nie ma prostego sposobu aby to zrobi. Powolnym sposobem na zrobienie tego byoby pobranie penej listy elementw ref za kadym razem, a nastpnie przeszukanie jej w ptli pod ktem atrybutu id. Szybkim sposobem jest utworzenie takiej listy raz, a nastpnie utworzenie bufora w postaci sownika. Przykad 10.14 loadGrammar def loadGrammar(self, grammar): self.grammar = self._load(grammar) self.refs = {} #(1) for ref in self.grammar.getElementsByTagName("ref"): #(2) self.refs[ref.attributes["id"].value] = ref #(3) (4) 1. Rozpoczynamy od utworzenia pustego sownika self.refs. 2. Jak ju widzielimy w podrozdziale 9.5, Wyszukiwanie elementw, getElementsByTagName zwraca list wszystkich elementw o podanej nazwie. Take atwo moemy uzyska list wszystkich elementw ref, a nastpnie po prostu przeszuka j w ptli. 3. Jak ju widzielimy w podrozdziale 9.6, Dostp do atrybutw elementw, moemy pobra atrybut elementu poprzez nazw uywajc standardowej skadni sownikowej. Take kluczami sownika self.refs bd wartoci atrybutu id kadego elementu ref. 4. Wartociami sownika self.refs bd elementy ref jako takie. Jak ju widzielimy w podrozdziale 9.3, Parsowanie XML-a, kady element, kady wze, kady komentarz, kady kawaek tekstu w parsowanym dokumencie XML jest obiektem.

11.3. BUFOROWANIE ODSZUKANEGO WZA

225

Gdy tylko bufor (cache) zostanie utworzony, po napotkaniu elementu xref, aby odnale element ref z takim samym atrybutem id, moemy po prostu sign do sownika self.refs. Przykad 10.15 Uycie bufora elementw ref def do_xref(self, node): id = node.attributes["id"].value self.parse(self.randomChildElement(self.refs[id])) Funkcj randomChildElement zgbimy w nastpnym podrozdziale.

226

ROZDZIA 11. SKRYPTY I STRUMIENIE

11.4

Wyszukanie bezporednich elementw potomnych

Wyszukanie bezporednich elementw potomnych


Inn przydatn technik przy parsowaniu dokumentw XML jest odnajdywanie wszystkich bezporednich elementw potomnych (dzieci) danego elementu. Na przykad w pliku gramatyki element ref moe mie szereg elementw p, a kady z nich moe zawiera wiele rzeczy, wcznie z innymi elementami p. Chcemy wyszuka tylko te elementy p, ktre s potomkami elementu ref, a nie elementy p, ktre s potomkami innych elementw p. Pewnie mylisz, e moesz do tego celu po prostu uy funkcji getElementsByTagName, ale niestety nie moesz. Funkcja getElementsByTagName przeszukuje rekurencyjnie i zwraca pojedyncza list wszystkich elementw jakie znajdzie. Poniewa elementy p mog zawiera inne elementy p, nie moemy uy funkcji getElementsByTagName. Zwrciaby ona zagniedone elementy p, a tego nie chcemy. Aby znale tylko bezporednie elementy potomne, musimy to wykona samodzielnie. Przykad 10.16 Wyszukanie bezporednich elementw potomnych def randomChildElement(self, node): choices = [e for e in node.childNodes if e.nodeType == e.ELEMENT_NODE] #(1) (2) (3) chosen = random.choice(choices) #(4) return chosen 1. Jak ju widzielimy w przykadzie 9.9, Pobieranie wzw potomnych, atrybut childNodes zwraca list wszystkich elementw potomnych danego elementu. 2. Jednake, jak ju widziae w przykadzie 9.11, Wzami potomnymi moe by take tekst, lista zwrcona przez childNodes zawiera ca rnorodno typw wzw, wczajc to wzy tekstowe. W tym wypadku szukamy jednak tylko potomkw, ktre s elementami. 3. Kady wze posiada atrybut nodeType, ktry moe przyjmowa wartoci ELEMENT NODE, TEXT NODE, COMMENT NODE i wiele innych. Pena lista moliwych wartoci znajduje si w pliku init .py w pakiecie xml.dom. (Zajrzyj do podrozdziau 9.2, Pakiety, aby si wicej dowiedzie o pakietach.) Ale my jestemy zainteresowani wzami, ktre s elementami, a wic moemy odltrowa z listy tylko te elementy, ktrych atrybut nodeType jest rwny ELEMENT NODE. 4. Gdy tylko mamy ju list waciwych elementw, wybr losowego elementu jest atwy. Python udostpnia modu o nazwie random, ktry zawiera kilka funkcji. Funkcja random.choice pobiera list z dowoln iloci elementw i zwraca losowy element. Np. jeli element ref zawiera kilka elementw p, to choices bdzie list elementw p, a do chosen zostanie przypisany dokadnie jeden z nich, wybrany losowo.

11.5. TWORZENIE ODDZIELNYCH FUNKCJI OBSUGI WZGLDEM TYPU WZA227

11.5

Tworzenie oddzielnych funkcji obsugi wzgldem typu wza

Tworzenie oddzielnych funkcji obsugi wzgldem typu wza


Trzecim uytecznym chwytem podczas przetwarzania XML-a jest podzielenie kodu w logiczny sposb na funkcje oparte na typie wza i nazwie elementu. Parsujc dokument przetwarzamy rozmaite typy wzw, ktre s reprezentowane przez obiekty Pythona. Poziom gwny dokumentu jest bezporednio reprezentowany przez obiekt klasy Document. Z kolei Document zawiera jeden lub wicej obiektw klasy Element (reprezentujce znaczniki XML-a), a kady z nich moe zawiera inne obiekty klasy Element, obiekty klasy Text (fragmenty tekstu), czy obiektw Comment (osadzone komentarze w dokumencie). Python pozwala w atwy sposb napisa funkcj poredniczc, ktra rozdziela logik dla kadego rodzaju wza. Przykad 10.17 Nazwy klas parsowanych obiektw XML >>> from xml.dom import minidom >>> xmldoc = minidom.parse(kant.xml) #(1) >>> xmldoc <xml.dom.minidom.Document instance at 0x01359DE8> >>> xmldoc.__class__ #(2) <class xml.dom.minidom.Document at 0x01105D40> >>> xmldoc.__class__.__name__ #(3) Document 1. Zamy na moment, e kgp/kant.xml jest w biecym katalogu. 2. Jak powiedzielimy w podrozdziale Pakiety, obiekt zwrcony przez parsowany dokument jest instancj klasy Document, ktra zostaa zdeniowana w minidom.py w pakiecie xml.dom. Jak zobaczylimy w podrozdziale Tworzenie instancji klasy, class jest wbudowanym atrybutem kadego obiektu Pythona. 3. Ponadto name jest wbudowanym atrybutem kadej klasy Pythona. Atrybut ten przechowuje napis, a napis ten nie jest niczym tajemniczym, jest po prostu nazw danej klasy. (Zobacz podrozdzia Deniowanie klas.) To fajnie, moemy pobra nazw klasy dowolnego wza XML-a (poniewa wzy s reprezentowane przez Pythonowe obiekty). Jak mona wykorzysta t zalet, aby rozdzieli logik parsowania dla kadego typu wza? Odpowiedzi jest getattr, ktry pierwszy raz zobaczylimy w podrozdziale Funkcja getattr. Przykad 10.18 parse, oglna funkcja poredniczca dla wza XML def parse(self, node): parseMethod = getattr(self, "parse_%s" % node.__class__.__name__) #(1) (2) parseMethod(node) #(3) 1. Od razu, zauwamy, e konstruujemy duszy napis oparty na nazwie klasy przekazanego wza (jako argument node). Zatem, jeli przekaemy wze Documentu, konstruujemy napis parse Document itd.

228

ROZDZIA 11. SKRYPTY I STRUMIENIE

2. Teraz, jeli potraktujemy t nazw jako nazw funkcji, otrzymamy dziki getattr referencj do funkcji. 3. Ostatecznie, moemy wywoa t funkcj, przekazujc sam node jako argument. Nastpny przykad przedstawia denicj tych funkcji. Przykad 10.19 Funkcje wywoywane przez funkcj poredniczc parse def parse_Document(self, node): #(1) self.parse(node.documentElement) def parse_Text(self, node): #(2) text = node.data if self.capitalizeNextWord: self.pieces.append(text[0].upper()) self.pieces.append(text[1:]) self.capitalizeNextWord = 0 else: self.pieces.append(text) def parse_Comment(self, node): #(3) pass def parse_Element(self, node): #(4) handlerMethod = getattr(self, "do_%s" % node.tagName) handlerMethod(node) 1. parse Document jest wywoany tylko raz, poniewa jest tylko jeden wze klasy Document w dokumencie XML i tylko jeden obiekt klasy Document w przeparsowanej reprezentacji XML-a. Tu po prostu idziemy dalej i parsujemy cz gwn pliku gramatyki. 2. parse Text jest wywoywany tylko na wzach reprezentujcych fragmenty tekstu. Funkcja wykonuje kilka specjalnych operacji zwizanych z automatycznym wstawianiem duej litery na pocztku sowa pierwszego zdania, ale w innym wypadku po prostu dodaje reprezentowany tekst do listy. 3. parse Comment jest tylko przejadk; metoda ta nic nie robi, poniewa nie musimy si troszczy o komentarze wstawione w plikach deniujcym gramatyk. Pomimo tego, zauwamy, e nadal musimy zdeniowa funkcj i wyranie stwierdzi, eby nic nie robia. Jeli funkcja nie bdzie istniaa, funkcja parse nawali tak szybko, jak napotka si na komentarz, poniewa bdzie prbowaa znale nieistniejc funkcj parse Comment. Deniujc oddzieln funkcj dla kadego typu wza, nawet jeli nam ta funkcja nie jest potrzebna, pozwalamy oglnej funkcji parsujcej by prost i krtk. 4. Metoda parse Element jest w rzeczywistoci funkcj poredniczc, opart na nazwie znacznika elementu. Idea jest taka sama: we odrniajce si od siebie elementy (elementy, ktre rni si nazw znacznika) i wylij je do odpowiedniej, odrbnej funkcji. Konstruujemy napis typu do xref (dla znacznika <xref>), znajdujemy funkcj o takiej nazwie i wywoujemy j. I robimy podobnie dla

11.5. TWORZENIE ODDZIELNYCH FUNKCJI OBSUGI WZGLDEM TYPU WZA229 kadej innej nazwy znacznika, ktra zostanie znaleziona, oczywicie w pliku gramatyki (czyli znaczniki <p>, czy te <choice>). W tym przykadzie funkcja poredniczca parse i parse Element po prostu znajduj inne metody w tej samej klasie. Jeli przetwarzanie jest bardzo zoone (lub mamy bardzo duo nazw znacznikw), powinnimy rozdzieli swj kod na kilka oddzielnych moduw i wykorzysta dynamiczne importowanie, aby zaimportowa kady modu, a nastpnie wywoa wszystkie potrzebne nam funkcje. Dynamiczne importowanie zostanie omwione w rozdziale Programowanie funkcyjne.

230

ROZDZIA 11. SKRYPTY I STRUMIENIE

11.6

Obsuga argumentw linii polece

Obsuga argumentw linii polece


Python cakowicie wspomaga tworzenie programw, ktre mog zosta uruchomione z linii polece, cznie z argumentami linii polece, czy zarwno z krtkim lub dugim stylem ag, ktre okrelaj opcje. Nie ma to nic wsplnego z XML-em, ale omawiany skrypt wykorzystuje w dobry sposb lini polece, dlatego te nadesza odpowiednia pora, aby to omwi. Ciko mwi o linii polece bez wiedzy, w jaki sposb argumenty linii polece s ujawniane do programu, dlatego te napiszmy prosty program, aby to zobaczy. Przykad 10.20 Wprowadzenie do sys.argv #argecho.py import sys for arg in sys.argv: #(1) print arg 1. Kady argument linii polece przekazany do programu, zostanie umieszczony w sys.argv, ktry jest waciwie list. W tym miejscu wypisujemy kady argument w oddzielnej linii. Przykad 10.21 Zawarto sys.argv [you@localhost argecho.py [you@localhost argecho.py abc def [you@localhost argecho.py help [you@localhost argecho.py m kant.xml py]$ python argecho.py py]$ python argecho.py abc def #(1) #(2)

py]$ python argecho.py help

#(3)

py]$ python argecho.py m kant.xml #(4)

1. Najpierw musimy sobie uwiadomi, e sys.argv przechowuje nazw uruchomionego skryptu. Wiedz t wykorzystamy pniej, w rozdziale Programowanie funkcyjne. Na razie nie zamartwiaj si tym. 2. Argumenty linii polece s oddzielane przez spacje i kady z nich ukazuje si w licie sys.argv jako oddzielny argument. 3. Flagi linii polece np. help, take poka si jako osobne elementy w sys.argv. 4. eby byo ciekawiej, niektre z ag linii polece same przyjmuj, wymagaj argumentw. Na przykad, tutaj mamy jedn ag (m), ktra na dodatek take przyjmuje argument (w przykadzie kant.xml). Zarwno aga sama w sobie, a

11.6. OBSUGA ARGUMENTW LINII POLECE

231

take argument agi s kolejnymi elementami w licie sys.argv. Python w aden sposb nie bdzie prbowa ich powiza; otrzymamy sam list. Jak moemy zobaczy, z pewnoci mamy wszystkie informacje przekazane do linii polece, ale nie wygldaj na tak proste, aby z nich faktycznie skorzysta. Dla nieskomplikowanych programw, ktre przyjmuj tylko jeden argument bez adnych ag, moemy po prostu wykorzysta sys.argv[1], aby si do niego dosta. Nie ma si czym wstydzi. Dla bardziej zoonych programw bdzie potrzebny modu getopt. Przykad 10.22 Wprowadzenie do getopt def main(argv): grammar = "kant.xml" try: opts, args = getopt.getopt(argv, except getopt.GetoptError: usage() sys.exit(2) [...] if __name__ == "__main__": main(sys.argv[1:]) 1. Od razu zobacz na sam d przykadu. Wywoujemy funkcj main z argumentem sys.argv[1:]. Zapamitaj, sys.argv[0] jest nazw skryptu, ktry uruchomilimy; nie martwimy si o to, w jaki sposb jest przetwarzana linia polece, wic odcinamy i przekazujemy reszt listy. 2. To najciekawsze miejsce w tym przykadzie. Funkcja getopt moduu getopt przyjmuje trzy parametry: list argumentw (ktr otrzymalimy z sys.argv[1:]), napis zawierajcy wszystkie moliwe jedno-znakowe agi, ktre program akceptuje, a take list duszych ag, ktre s odpowiednikami krtszych, jednoznakowych wersji. Na pierwszy rzut oka wydaje si to troch zamotane, ale w dalszej czci szerzej to omwimy. 3. Jeli co nie poszo pomylnie podczas parsowania ag linii polece, getopt rzuca wyjtek, ktry nastpnie przechwytujemy. Informujemy funkcj getopt o wszystkich agach, ktre rozumie nasz program, zatem ostatecznie oznacza, e uytkownik przekaza niektre niezrozumiae przez nas agi. 4. Jest to praktycznie standard wykorzystywany w wiecie Uniksa, kiedy do skryptu zostan przekazane niezrozumiae agi, wypisujemy streszczon pomoc dotyczc uycia programu i wdzicznie zakaczamy go. Dodajmy, e nie przedstawilimy tutaj funkcji usage. Jeszcze trzeba bdzie j gdzie zaimplementowa, aby wypisywaa streszczenie pomocy; nie dzieje si to automatycznie. Wic czym s te wszystkie parametry przekazane do funkcji getopt? A wic, pierwszy jest po prostu surow list argumentw i ag przekazanych do linii polece (bez pierwszego elementu, czyli nazwy skryptu, ktry wycilimy przed wywoaniem funkcji main). Drugi parametr jest list krtkich ag linii polece, ktre akceptuje skrypt. #(1) "hg:d", ["help", "grammar="]) #(2) #(3) #(4)

232 "hg:d" -h

ROZDZIA 11. SKRYPTY I STRUMIENIE

wywietla streszczon pomoc -g ... korzysta z okrelonego pliku gramatyki lub URL-a -d pokazuje informacje debugujce podczas parsowania Pierwsza i trzecia aga s zwykymi, samodzielnymi agami; moemy je okreli lub nie. Flagi te wykonuj pewne czynnoci (wypisuj pomoc) lub zmieniaj stan (wczaj debugowanie). Jakkolwiek, za drug ag (-g) musi si znale pewien argument, ktry bdzie nazw pliku gramatyki, ktry ma zosta przeczytany. W rzeczywistoci moe by nazw pliku lub adresem strony strony web, ale my jeszcze nie wiemy, czym jest (bdziemy z tym kombinowa pniej), ale wiemy, e ma by czym. Poinformowalimy getopt o tym, e ma by co za t ag, poprzez wstawienie w drugim parametrze dwukropka po literze g. eby to bardziej skomplikowa, skrypt akceptuje zarwno krtkie agi (np. -h, jak i dugie agi (jak --help), a my chcemy, eby suyy one do tego samego. I po to jest trzeci parametr w getopt. Okrela on list dugich ag, ktre odpowiadaj krtkim agom zdeniowanym w drugim parametrze. ["help", "grammar="] --help wywietla streszczon pomoc --grammar ... korzysta z okrelonego pliku gramatyki lub URL-a Zwrmy uwag na trzy sprawy: 1. Wszystkie dugie agi w linii polece s poprzedzone dwoma mylnikami, ale podczas wywoywania getopt nie doczamy tych mylnikw. 2. Po adze --grammar musi zawsze wystpi dodatkowy argument, identycznie jak z ag -g. Informujemy o tym poprzez znak rwnoci w "grammar=". 3. Lista dugich ag jest krtsza ni lista krtkich ag, poniewa aga -d nie ma swojego duszego odpowiednika. Jedynie -d bdzie wcza debugowanie. Jednak porzdek krtkich i dugich ag musi by ten sam, dlatego te najpierw musimy okreli wszystkie krtkie agi odpowiadajce duszym agom, a nastpnie pozosta cz krtszych ag, ktre nie maj swojego duszego odpowiednika. Jeszcze si nie pogubie? To spjrz na waciwy kod i zobacz, czy nie staje si dla ciebie zrozumiay. Przykad 10.23 Obsuga argumentw linii polece w kgp/kgp.py def main(argv): #(1) grammar = "kant.xml" try: opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="])

11.6. OBSUGA ARGUMENTW LINII POLECE except getopt.GetoptError: usage() sys.exit(2) for opt, arg in opts: if opt in ("-h", "--help"): usage() sys.exit() elif opt == -d: global _debug _debug = 1 elif opt in ("-g", "--grammar"): grammar = arg source = "".join(args) k = KantGenerator(grammar, source) print k.output()

233

#(2) #(3)

#(4)

#(5)

#(6)

1. Zmienna grammar bdzie przechowywa ciek do pliku gramatyki, z ktrego bdziemy korzysta. W tym miejscu inicjalizujemy j tak, aby w przypadku, gdy nie zostanie okrelona w linii polece (za pomoc agi -g lub --grammar) miaa jak domyln warto. 2. Zmienn opts, ktr otrzymujemy z warto zwrconej przez getopt, przechowuje list krotek: ag i argument. Jeli aga nie przyjmuje argumentu, to argument bdzie mia warto None. Uatwia to wykonywanie ptli na agach. 3. getopt kontroluje, czy agi linii polece s akceptowalne, ale nie wykonuje adnej konwersji midzy dugimi, a krtkimi agami. Jeli okrelimy ag -h, opt bdzie zawiera -h", natomiast jeli okrelimy ag --help, opt bdzie zawiera --help". Zatem musimy kontrolowa obydwa warianty. 4. Pamitamy, e adze -d nie odpowiada adna dusza wersja, dlatego te kontrolujemy tylko t krtk ag. Jeli zostanie ona odnaleziona, ustawiamy globaln zmienn, do ktrej pniej bdziemy si odwoywa, aby wypisywa informacje debugujce. (Flaga ta bya wykorzystywana podczas projektowania skryptu. A co, mylisz, e wszystkie przedstawione przykady dziaay od razu??) 5. Jeli znajdziemy plik gramatyki spotykajc ag -g lub grammar, zapisujemy argument, ktry nastpuje po tej adze (przechowywany w zmiennej arg), do zmiennej grammar, nadpisujc przy tym domyln warto, zainicjalizowan na pocztku funkcji main. 6. Ok. Wykonalimy ptl przez wszystkie agi i przetworzylimy je. Oznacza to, e pozostaa cz musi by argumentami linii polece, a zostay one zwrcone przez funkcje getopt do zmiennej args. W tym przypadku traktujemy je jako materia rdowy dla parsera. Jeli nie zostay okrelone adne argumenty linii polece, args bdzie pust list, wic source w wyniku tego bdzie pustym napisem.

234

ROZDZIA 11. SKRYPTY I STRUMIENIE

11.7

Skrypty i strumienie - wszystko razem

Wszystko razem
Przemierzylimy kawa drogi. Zatrzymajmy si na chwil i zobaczmy jak te wszystkie elementy do siebie pasuj. Zaczniemy od skryptu, ktry pobiera argumenty z linii polece uywajc moduu getopt. def main(argv): ... try: opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="]) except getopt.GetoptError: ... for opt, arg in opts: ... Tworzymy now instancj klasy KantGenerator i przekazujemy jej plik z gramatyk oraz rdo, ktre moe by, ale nie musi, podane w linii polece. k = KantGenerator(grammar, source) Instancja klasy KantGenerator automatycznie wczytuje gramatyk, ktra jest plikiem XML. Wykorzystujemy nasz funkcj openAnything do otwarcia pliku (ktry moe by ulokowany lokalnie lub na zdalnym serwerze), nastpnie uywamy wbudowanego zestawu funkcji parsujcych minidom do sparsowania XML-a do postaci drzewa obiektw Pythona. def _load(self, source): sock = toolbox.openAnything(source) xmldoc = minidom.parse(sock).documentElement sock.close() Ach i po drodze wykorzystujemy nasz wiedz o strukturze dokumentu XML do utworzenia maego bufora referencji, ktrymi s po prostu elementy dokumentu XML. def loadGrammar(self, grammar): for ref in self.grammar.getElementsByTagName("ref"): self.refs[ref.attributes["id"].value] = ref Jeli zosta podany jaki materia rdowy w linii polece, uywamy go. W przeciwnym razie na podstawie gramatyki wyszukujemy referencj na najwyszym poziomie (t do ktrej nie maj odnonikw adne inne elementy) i uywamy jej jako punktu startowego. def getDefaultSource(self): xrefs = {} for xref in self.grammar.getElementsByTagName("xref"): xrefs[xref.attributes["id"].value] = 1 xrefs = xrefs.keys() standaloneXrefs = [e for e in self.refs.keys() if e not in xrefs] return <xref id="%s"/> % random.choice(standaloneXrefs)

11.7. SKRYPTY I STRUMIENIE - WSZYSTKO RAZEM

235

Teraz przedzieramy si przez materia rdowy. Ten materia to take XML i parsujemy go wze po wle. Aby podzieli nieco kod i uczyni go atwiejszym w utrzymaniu, uywamy oddzielnych funkcji obsugi (ang. handlers) dla kadego typu wza. def parse_Element(self, node): handlerMethod = getattr(self, "do_%s" % node.tagName) handlerMethod(node) Przelatujemy przez gramatyk, parsujc wszystkie elementy potomne kadego elementu p, def do_p(self, node): ... if doit: for child in node.childNodes: self.parse(child) zastpujc elementy choice losowym elementem potomnym, def do_choice(self, node): self.parse(self.randomChildElement(node)) i zastpujc elementy xref losowym elementem potomnym odpowiedniego elementu ref, ktry wczeniej zosta zachowany w buforze. def do_xref(self, node): id = node.attributes["id"].value self.parse(self.randomChildElement(self.refs[id])) W kocu parsujemy wszystko do zwykego tekstu, def parse_Text(self, node): text = node.data ... self.pieces.append(text) ktry wypisujemy. def main(argv): ... k = KantGenerator(grammar, source) print k.output()

236

ROZDZIA 11. SKRYPTY I STRUMIENIE

11.8

Skrypty i strumienie - podsumowanie

Wszystko razem
Python posiada zestaw potnych bibliotek do parsowania i manipulacji dokumentami XML. Modu minidom parsuje plik XML zamieniajc go w obiekt Pythona i pozwalajc na swobodny dostp do dowolnych jego elementw. Idc dalej, w rozdziale tym pokazalimy, w jaki sposb uy Pythona do tworzenia prawdziwych skryptw linii polece przyjmujcych argumenty, obsugujcych rne bdy, a nawet potracych pobra dane z wyjcia innego programu. Zanim przejdziemy do nastpnego rozdziau, powinnimy z atwoci: posugiwa si standardowym strumie wejcia, wyjcia i bdw. deniowa dynamiczne funkcje poredniczce za pomoc funkcji getattr. korzysta z argumentw linii polece i sprawdza ich poprawno za pomoc moduu getopt.

Rozdzia 12

HTTP

237

238

ROZDZIA 12. HTTP

12.1

HTTP

Nurkujemy
Do tej pory nauczylimy si ju przetwarza HTML i XML, zobaczylimy jak pobra stron internetow, a take jak parsowa dane XML pobrane poprzez URL. Zagbmy si teraz nieco bardziej w tematyk usug sieciowych HTTP. W uproszczeniu, usugi sieciowe HTTP s programistycznym sposobem wysyania i odbierania danych ze zdalnych serwerw, wykorzystujc do tego bezporednio transmisje po HTTP. Jeeli chcesz pobra dane z serwera, uyj po prostu metod GET protokou HTTP; jeeli chcesz wysa dane do serwera, uyj POST. (Niektre, bardziej zaawansowane API serwisw HTTP deniuj take sposb modykacji i usuwania istniejcych danych za pomoc metod HTTP PUT i DELETE). Innymi sowy, czasowniki wbudowane w protok HTTP (GET, POST, PUT i DELETE) porednio przeksztacaj operacje HTTP na operacje na poziomie aplikacji: odbierania, wysyania, modykacji i usuwania danych. Gwn zalet tego podejcia jest jego prostota. Popularno tego rozwizania zostaa udowodniona poprzez ogromn liczb rnych witryn. Dane najczciej w formacie XML mog by wygenerowane i przechowane statycznie, albo te generowane dynamicznie poprzez skrypty po stronie serwera, i inne popularne jzyki, wczajc w to bibliotek HTTP. atwiejsze jest take debugowanie, poniewa moemy wywoa dowoln usug sieciow w dowolnej przegldarce internetowej i obserwowa zwracane surowe dane. Wspczesne przegldarki take czytelnie sformatuj otrzymane dane XML, i pozwol na szybk nawigacj wrd nich. Przykady uycia czystych usug sieciowych typu XML poprzez HTTP: Amazon API (http://www.amazon.com/webservices) pozwala na pobieranie informacji o produktach oferowanych w sklepie Amazon.com National Weather Service (http://www.nws.noaa.gov/alerts/) (United States) i Hong Kong Observatory (http://demo.xml.weather.gov.hk/) (Hong Kong) oferuje informowanie o pogodzie w formie usugi sieciowej Atom API (http://atomenabled.org/) zarzdzanie zawartoci stron www W kolejnych rozdziaach zapoznamy si z rnymi API, ktre wykorzystuj protok HTTP jako nonik do wysyania i odbierania danych, ale ktre nie przeksztacaj operacji na poziomie aplikacji na operacje w HTTP (zamiast tego tuneluj wszystko poprzez HTTP POST). Ale ten rozdzia koncentruje si na wykorzystywaniu metody GET protokou HTTP do pobierania danych z serwera poznamy kilka cech HTTP, ktre pozwol nam jak najlepiej wykorzysta moliwoci czystych usug sieciowych HTTP. Poniej jest bardziej zaawansowana wersja moduu openanything.py, ktry przedstawilimy w poprzednim rozdziale: import urllib2, urlparse, gzip from StringIO import StringIO USER_AGENT = OpenAnything/%s +http://diveintopython.org/http_web_services/ % __version__ class SmartRedirectHandler(urllib2.HTTPRedirectHandler):

12.1. NURKUJEMY def http_error_301(self, req, fp, code, msg, headers): result = urllib2.HTTPRedirectHandler.http_error_301( self, req, fp, code, msg, headers) result.status = code return result def http_error_302(self, req, fp, code, msg, headers): result = urllib2.HTTPRedirectHandler.http_error_302( self, req, fp, code, msg, headers) result.status = code return result class DefaultErrorHandler(urllib2.HTTPDefaultErrorHandler): def http_error_default(self, req, fp, code, msg, headers): result = urllib2.HTTPError( req.get_full_url(), code, msg, headers, fp) result.status = code return result

239

def openAnything(source, etag=None, lastmodified=None, agent=USER_AGENT): u"""URL, nazwa pliku lub acuch znakw --> strumie Funkcja ta pozwala tworzy parsery, ktre przyjmuj jakie rdo wejcia (URL, ciek do pliku lokalnego lub gdzie w sieci lub dane w postaci acucha znakw), a nastpnie zaznajamia si z nim w odpowiedni sposb. Zwracany obiekt bdzie posiada wszystkie podstawowe metody czytania wejcia (read, readline, readlines). Ponadto korzystamy z .close(), gdy obiekt ju nam nie bdzie potrzebny. Kiedy zostanie podany argument etag, zostanie on wykorzystany jako warto nagwka dania URL-a If-None-Match. Jeli argument lastmodified zostanie podany, musi by on formie acucha znakw okrelajcego czas i dat w GMT. Data i czas sformatowana w tym acuchu zostanie wykorzystana jako warto nagwka dania If-Modified-Since. Jeli argument agent zostanie okrelony, bdzie on wykorzystany w nagwku dania User-Agent. """ if hasattr(source, read): return source if source == -: return sys.stdin if urlparse.urlparse(source)[0] == http: # otwiera URL za pomoc urllib2 request = urllib2.Request(source) request.add_header(User-Agent, agent)

240

ROZDZIA 12. HTTP if lastmodified: request.add_header(If-Modified-Since, lastmodified) if etag: request.add_header(If-None-Match, etag) request.add_header(Accept-encoding, gzip) opener = urllib2.build_opener(SmartRedirectHandler(), DefaultErrorHandler()) return opener.open(request) # prbuje otworzy za pomoc wbudowanej funkcji open (jeli source to nazwa pliku) try: return open(source) except (IOError, OSError): pass # traktuje source jak acuch znakw return StringIO(str(source))

def fetch(source, etag=None, lastmodified=None, agent=USER_AGENT): u"""Pobiera dane z URL, pliku, strumienia lub acucha znakw""" result = {} f = openAnything(source, etag, lastmodified, agent) result[data] = f.read() if hasattr(f, headers): # zapisuje ETag, jeli go wysa do nas serwer result[etag] = f.headers.get(ETag) # zapisuje nagwek Last-Modified, jeli zosta do nas wysany result[lastmodified] = f.headers.get(Last-Modified) if f.headers.get(content-encoding) == gzip: # odkompresowuje otrzymane dane, poniewa s one zakompresowane jako gzip result[data] = gzip.GzipFile(fileobj=StringIO(result[data])).read() if hasattr(f, url): result[url] = f.url result[status] = 200 if hasattr(f, status): result[status] = f.status f.close() return result

12.2. JAK NIE POBIERA DANYCH POPRZEZ HTTP

241

12.2

Jak nie pobiera danych poprzez HTTP

Jak nie pobiera danych poprzez HTTP


Zamy, e chcemy pobra jaki zasb poprzez HTTP, jak np. RSS (Really Simple Syndication). Jednak nie chcemy pobra go tylko jednorazowo, lecz cyklicznie, np. co godzin, aby mie najwiesze informacje ze strony, ktra udostpnia RSS. Zrbmy to najpierw w bardzo prosty i szybki sposb, a potem zobaczymy jak mona to zrobi lepiej. Przykad 11.2 Pobranie RSS w szybki i prosty sposb >>> import urllib >>> data = urllib.urlopen(http://diveintomark.org/xml/atom.xml).read() >>> print data <?xml version="1.0" encoding="iso-8859-1"?> <feed version="0.3" xmlns="http://purl.org/atom/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:lang="en"> <title mode="escaped">dive into mark</title> <link rel="alternate" type="text/html" href="http://diveintomark.org/"/> [... ciach ...] 1. Pobranie czegokolwiek poprzez HTTP jest w Pythonie niesamowicie atwe; waciwie jest to jedna linijka kodu. Modu urllib ma bardzo porczn funkcj urlopen, ktra przyjmuje na wejciu adres strony, a zwraca obiekt pliko-podobny, z ktrego mona odczyta ca zawarto strony przy pomocy metody read. Prociej ju nie moe by. A wic co jest z tym nie tak? C, do szybkiej prby podczas testowania, czy programowania, to w zupenoci wystarczy. Chcielimy zawarto RSS-a i mamy j. Ta sama technika dziaa dla kadej strony internetowej. Jednak gdy zaczniemy myle w kategoriach serwisw internetowych, z ktrych chcemy korzysta regularnie pamitajmy, e chcielimy pobiera tego RSS-a co godzin wtedy takie dziaanie staje si niewydajne i prymitywne. Porozmawiajmy o podstawowych cechach HTTP. #(1)

242

ROZDZIA 12. HTTP

12.3

Waciwoci HTTP

Moemy wyrni pi wanych waciwoci HTTP, z ktrych powinnimy korzysta.

User-Agent User-Agent jest to po prostu sposb w jaki klient moe poinformowa serwer kim jest w trakcie dania strony internetowej, RSS (Really Simple Syndication) lub jakiejkolwiek usugi internetowej poprzez HTTP. Gdy klient zgasza danie do danego zasobu, to zawsze powinien informowa kim jest tak szczegowo, jak to tylko moliwe. Pozwala to administratorowi serwera na skontaktowanie si z programist, twrc aplikacji klienckiej, jeli co idzie nie tak. Domylnie Python wysya standardowy nagwek postaci User-Agent: Python-urllib/1.15. W nastpnej sekcji zobaczymy jak to zmieni na co bardziej szczegowego.

Przekierowania Czasami zasoby zmieniaj pooenie. Witryny internetowe s reorganizowane a strony przesuwane pod nowe adresy. Nawet usugi internetowe ulegaj reorganizacji. RSS spod adresu http://example.com/index.xml moe si przesun do http://example.com/xml/atom.xml. Moe si take przesun caa domena, gdy reorganizacja jest przeprowadzana na wiksz skal, np. http://www.example.com/index.xml moe zosta przekierowana do http://serverfarm-1.example.com/index.xml. Zawsze gdy damy jakiego zasobu od serwera HTTP, serwer ten docza do swojej odpowiedzi kod statusu. Kod statusu 200 oznacza wszystko w porzdku, oto strona o ktr prosilimy. Kod statusu 404 oznacza strona nieznaleziona. (Prawdopodobnie spotkalimy si z bdami 404 podczas surfowania w sieci.) Protok HTTP ma dwa rne sposoby, aby da do zrozumienia, e dany zasb zosta przesunity. Kod statusu 302 jest to tymczasowe przekierowanie i oznacza on ups, to zostao tymczasowo przesunite tutaj (a ten tymczasowy adres umieszczany jest w nagwku Location:). Kod statusu 301 jest to przekierowanie trwae i oznacza ups, to zostao przesunite na stae (a nowy adres jest podawany w nagwku Location:). Gdy otrzymamy kod statusu 302 i nowy adres, to specykacja HTTP mwi, e powinnimy uy nowego adresu, aby pobra to czego dotyczyo danie, ale nastpnym razem przy prbie dostpu do tego samego zasobu powinnimy sprbowa ponownie starego adresu. Natomiast gdy dostaniemy kod statusu 301 i nowy adres, to powinnimy ju od tego momentu uywa tylko tego nowego adresu. urllib.urlopen automatycznie ledzi przekierowania, jeli otrzyma stosowny kod statusu od serwera HTTP, ale niestety nie informuje o tym fakcie. Ostatecznie otrzymujemy dane, o ktre prosilimy, ale nigdy nie wiadomo, czy biblioteka nie podya samodzielnie za przekierowaniem. Tak wic niewiadom niczego dalej moemy prbowa korzysta ze starego adresu i za kadym razem nastpi przekierowanie pod nowy adres. To powoduje wyduenie drogi, co nie jest zbyt wydajne! W dalszej czci tego rozdziau zobaczymy, jak sobie radzi z trwaymi przekierowaniami waciwie i wydajnie.

12.3. WACIWOCI HTTP Last-Modified/If-Modified-Since

243

Niektre dane zmieniaj si bez przerwy. Strona domowa cnn.com jest aktualizowana co pi minut. Z drugiej strony strona domowa google.com zmienia si raz na kilka tygodni (gdy wrzucaj jakie witeczne logo lub reklamuj jak now usug). Usugi internetowe nie rni si pod tym wzgldem. Serwer zwykle wie kiedy dane, ktre pobieramy si ostatnio zmieniy, a protok HTTP pozwala serwerowi na doczenie tej daty ostatniej modykacji do danych danych. Gdy poprosimy o te same dane po raz drugi (lub trzeci, lub czwarty), moemy poda serwerowi dat ostatniej modykacji (ang. last-modied date), ktr dostalimy ostatnim razem. Wysyamy serwerowi nagwek If-Modified-Since wraz ze swoim daniem, wraz z dat otrzyman od serwera ostatnim razem. Jeli dane nie ulegy zmianie od tamtego czasu, to serwer odsya specjalny kod statusu 304, ktry oznacza dane nie zmieniy si od czasu, gdy o nie ostatnio pytae. Dlaczego jest to lepsze rozwizanie? Bo gdy serwer odsya kod 304, to nie wysya ponownie danych. Wszystko co otrzymujemy to kod statusu. Tak wic nie musimy cigle pobiera tych samych danych w kko, jeli nie ulegy zmianie. Serwer zakada, e ju mamy te dane zachowane gdzie lokalnie. Wszystkie nowoczesne przegldarki internetowe wspieraj sprawdzanie daty ostatniej modykacji. By moe kiedy odwiedzilimy jak stron jednego dnia, a potem odwiedzilimy j ponownie nastpnego dnia i zauwaylimy, e nie ulega ona zmianie, a jednoczenie zadziwiajco szybko si zaadowaa. Przegldarka zachowaa zawarto tej strony w lokalnym buforze podczas pierwszej wizyty, a podczas drugiej automatycznie wysaa dat ostatniej modykacji otrzyman za pierwszym razem. Serwer po prostu odpowiedzia kodem 304: Not Modified, a wic przegldarka wiedziaa, e musi zaadowa stron z lokalnego bufora. Usugi internetowe mog by take takie sprytne. Biblioteka URL Pythona nie ma wbudowanego wsparcia dla kontroli daty ostatniej modykacji, ale poniewa moemy dodawa dowolne nagwki do kadego dania i czyta dowolne nagwki z kadej odpowiedzi, to moemy doda tak kontrol samodzielnie.

ETag/If-None-Match Znaczniki ETag s alternatywnym sposobem na osignicie tego samego celu, co poprzez kontrol daty ostatniej modykacji: nie pobieramy ponownie danych, ktre si nie zmieniy. A dziaa to tak: serwer wysya jak sum kontroln danych (w nagwku ETag) razem z danymi danymi. Jak ta suma kontrolna jest ustalana, to zaley wycznie od serwera. Gdy po raz drugi chcemy pobra te same dane, doczamy sum kontroln z nagwka ETag w nagwku If-None-Match: i jeli dane si nie zmieniy serwer odele kod statusu 304. Tak jak w przypadku kontroli daty ostatniej modykacji, serwer odsya tylko kod 304; nie wysya po raz drugi tych samych danych. Poprzez doczenie sumy kontrolnej ETag do drugiego dania mwimy serwerowi, i nie ma potrzeby, aby wysya po raz drugi tych samych danych, jeli nadal odpowiadaj one tej sumie kontrolnej, poniewa cay czas mamy dane pobrane ostatnio. Biblioteka URL Pythona nie ma wbudowanego wsparcia dla znacznikw ETag, ale zobaczymy, jak mona je doda w dalszej czci tego rozdziau.

244 Kompresja

ROZDZIA 12. HTTP

Ostatni istotn waciwoci HTTP, ktr moemy ustawi, jest kompresja gzip. Gdy mwimy o usugach sieciowych za porednictwem HTTP, to zwykle mwimy o przesyaniu XML-i tam i z powrotem. XML jest tekstem i to zwykle cakiem rozwlekym tekstem, a tekst dobrze si kompresuje. Gdy damy jakiego zasobu poprzez HTTP, to moemy poprosi serwer, jeli ma jakie nowe dane do wysania, aby wysa je w formie skompresowanej. Doczamy wtedy nagwek Accept-encoding: gzip do dania i jeli serwer wspiera kompresj, odele on dane skompresowane w formacie gzip i oznaczy je nagwkiem Content-encoding: gzip. Biblioteka URL Pythona nie ma wbudowanego wsparcia dla kompresji gzip jako takiej, ale moemy dodawa dowolne nagwki do dania a Python posiada oddzielny modu gzip, ktry zawiera funkcje, ktrych mona uy do dekompresji danych samodzielnie. Zauwamy, e nasz jednolinijkowy skrypt pobierajcy RSS nie uwzgldnia adnej z tych waciwoci HTTP. Zobaczmy, jak moemy go udoskonali.

12.4. DEBUGOWANIE SERWISW HTTP

245

12.4

Debugowanie serwisw HTTP

Debugowanie serwisw HTTP


Na pocztek wczmy debugowanie w pythonowej bibliotece HTTP i zobaczmy co zostanie przesane. Wiadomoci zdobyte poprzez przeanalizowanie wypisanych informacji debugujcych, bd przydatne w tym rozdziale, gdy bdziemy chcieli doda nowe moliwoci do naszego programu. Przykad 11.3 Debugowanie HTTP >>> import httplib >>> httplib.HTTPConnection.debuglevel = 1 #(1) >>> import urllib >>> feeddata = urllib.urlopen(http://diveintomark.org/xml/atom.xml).read() connect: (diveintomark.org, 80) #(2) send: GET /xml/atom.xml HTTP/1.0 #(3) Host: diveintomark.org #(4) User-agent: Python-urllib/1.15 #(5) reply: HTTP/1.1 200 OK\r\n #(6) header: Date: Wed, 14 Apr 2004 22:27:30 GMT header: Server: Apache/2.0.49 (Debian GNU/Linux) header: Content-Type: application/atom+xml header: Last-Modified: Wed, 14 Apr 2004 22:14:38 GMT #(7) header: ETag: "e8284-68e0-4de30f80" #(8) header: Accept-Ranges: bytes header: Content-Length: 26848 header: Connection: close 1. urllib jest zaleny od innej standardowej biblioteki Pythona: httplib. Zwykle nie musimy importowa moduu httplib bezporednio (urllib robi to automatycznie), ale w tym wypadku to zrobilimy, a wic moemy ustawi ag trybu debugowania w klasie HTTPConnection, ktr modu urllib wykorzystuje wewntrznie do nawizania poczenia z serwerem HTTP. To jest niezwykle przydatna technika. Kilka innych bibliotek Pythona ma podobne agi trybu debugowania, ale nie ma jakiego szczeglnego standardu nazywania ich i ustawiania; trzeba przeczyta dokumentacj kadej biblioteki, aby zobaczy, czy taka aga jest dostpna. 2. Teraz gdy ju ag debugowania mamy ustawion, informacje na temat da HTTP i odpowiedzi s wywietlane w czasie rzeczywistym. Pierwsz rzecz jak moemy zauway jest to, i czymy si z serwerem diveintomark.org na porcie 80, ktry jest standardowym portem dla HTTP. 3. Gdy zgaszamy danie po zasobw RSS, urllib wysya trzy linie do serwera. Pierwsza linia zawiera polecenie HTTP i ciek do zasobu (bez nazwy domeny). Wszystkie dania w tym rozdziale bd uyway polecenia GET, ale w nastpnym rozdziale o SOAP zobaczymy, e tam do wszystkiego uywane jest polecenie POST. Podstawowa skadnia jest jednak taka sama niezalenie od polecenia.

246

ROZDZIA 12. HTTP

4. Druga linia odnosi si do nagwka Host, ktry zawiera nazw domeny serwisu, do ktrego kierujemy danie. Jest to istotne, poniewa pojedynczy serwer HTTP moe obsugiwa wiele oddzielnych domen. Na tym serwerze jest aktualnie obsugiwanych 12 domen; inne serwery mog obsugiwa setki lub nawet tysice. 5. Trzecia linia to nagwek User-Agent. To co tu wida, to jest standardowy nagwek User-Agent dodany domylnie przez bibliotek urllib. W nastpnej sekcji pokaemy jak zmieni to na co bardziej konkretnego. 6. Serwer odpowiada kodem statusu i kilkoma nagwkami (by moe z jakimi danymi, ktre zostay zachowane w zmiennej feeddata). Kodem statusu jest tutaj liczba 200, ktra oznacza wszystko w porzdku, prosz to dane o ktre prosie. Serwer take podaje dat odpowiedzi na danie, troch informacji na temat samego serwera i typ zawartoci (ang. content type) zwracanych danych. W zalenoci od aplikacji, moe by to przydatne lub te nie. Zauwamy, e zadalimy RSS-a i faktycznie otrzymalimy RSS-a (application/atom+xml jest to zarejestrowany typ zawartoci dla zasobw RSS). 7. Serwer podaje, kiedy ten RSS by ostatnio modykowany (w tym wypadku okoo 13 minut temu). Moemy odesa t dat serwerowi z powrotem nastpnym razem, gdy zadamy tego samego zasobu, a serwer bdzie mg wykona sprawdzenie daty ostatniej modykacji. 8. Serwer podaje take, e ten RSS ma sum kontroln ETag o wartoci e828468e0-4de30f80. Ta suma kontrolna sama w sobie nie ma adnego znaczenia; nie mona z ni zrobi nic poza wysaniem jej z powrotem do serwera przy nastpnej prbie dostpu do tego zasobu. Wtedy serwer moe jej uy do sprawdzenia, czy dane si zmieniy od ostatniego razu czy nie.

12.5. USTAWIANIE USER-AGENT

247

12.5

Ustawianie User-Agent

Ustawianie User-Agent
Pierwszym krokiem, aby udoskonali swj klient serwisu HTTP jest waciwe zidentykowanie siebie za pomoc nagwka User-Agent. Aby to zrobi, potrzebujemy wyj poza prosty urllib i zanurkowa w urllib2. Przykad 11.4 Wprowadzenie do urllib2 >>> import httplib >>> httplib.HTTPConnection.debuglevel = 1 >>> import urllib2 >>> request = urllib2.Request(http://diveintomark.org/xml/atom.xml) >>> opener = urllib2.build_opener() >>> feeddata = opener.open(request).read() connect: (diveintomark.org, 80) send: GET /xml/atom.xml HTTP/1.0 Host: diveintomark.org User-agent: Python-urllib/2.1 reply: HTTP/1.1 200 OK\r\n header: Date: Wed, 14 Apr 2004 23:23:12 GMT header: Server: Apache/2.0.49 (Debian GNU/Linux) header: Content-Type: application/atom+xml header: Last-Modified: Wed, 14 Apr 2004 22:14:38 GMT header: ETag: "e8284-68e0-4de30f80" header: Accept-Ranges: bytes header: Content-Length: 26848 header: Connection: close #(1) #(2) #(3) #(4)

1. Jeli w dalszym cigu masz otwarty IDE na przykadzie z poprzedniego podrozdziau, moesz ten punkt pomin. Polecenie to wcza debugowanie HTTP, dziki ktrej moemy zobaczy, co rzeczywicie wysyamy i co zostaje przysane do nas. 2. Pobieranie zasobw HTTP za pomoc urllib2 skada si z trzech krtkich etapw. Pierwszy krok to utworzenie obiektu dania (instancji klasy Request). Klasa Request przyjmuje jako parametr URL zasobw, z ktrych bdziemy ewentualnie pobiera dane. Dodajmy, e za pomoc tego kroku jeszcze nic nie pobieramy. 3. Drugim krokiem jest zbudowanie otwieracza (ang. opener ) URL-a. Moe on przyj dowoln liczb funkcji obsugi, ktre kontroluj, w jaki sposb obsugiwa odpowiedzi. Ale moemy take zbudowa otwieracz bez podawania adnych funkcji obsugi, a co robimy w tym fragmencie. Pniej, kiedy bdziemy omawia przekierowania, zobaczymy w jaki sposb zdeniowa wasne funkcje obsugi. 4. Ostatnim krokiem jest kazanie otwieraczowi, aby korzystajc z utworzonego obiektu dania otworzy URL. Widzimy, e wszystkie otrzymane informacje

248

ROZDZIA 12. HTTP debugujce zostay wypisane. W tym kroku tak pobieramy zasoby, a zwrcone dane przechowujemy w feeddata.

Przykad 11.5 Dodawanie nagwkw do dania >>> request <urllib2.Request instance at 0x00250AA8> >>> request.get_full_url() http://diveintomark.org/xml/atom.xml >>> request.add_header(User-Agent, ... OpenAnything/1.0 +http://diveintopython.org/) >>> feeddata = opener.open(request).read() connect: (diveintomark.org, 80) send: GET /xml/atom.xml HTTP/1.0 Host: diveintomark.org User-agent: OpenAnything/1.0 +http://diveintopython.org/ reply: HTTP/1.1 200 OK\r\n header: Date: Wed, 14 Apr 2004 23:45:17 GMT header: Server: Apache/2.0.49 (Debian GNU/Linux) header: Content-Type: application/atom+xml header: Last-Modified: Wed, 14 Apr 2004 22:14:38 GMT header: ETag: "e8284-68e0-4de30f80" header: Accept-Ranges: bytes header: Content-Length: 26848 header: Connection: close #(1)

#(2) #(3)

#(4)

1. Kontynuujemy z poprzedniego przykadu. Mamy ju utworzony obiekt Request z URL-em, do ktrego chcemy si dosta. 2. Za pomoc metody add header obiektu klasy Request, moemy doda do dania dowolny nagwek HTTP. Pierwszy argument jest nagwkiem, a drugi jest wartoci dostarczon do tego nagwka. Konwencja dyktuje, aby User-Agent powinien by w takim formacie: nazwa aplikacji, nastpnie slash, a potem numer wersji. Pozostaa cz moe mie dowoln form, jednak znale mnstwo wariacji wykorzystania tego nagwka, ale gdzie powinno si umieci URL aplikacji. User-Agent jest zazwyczaj logowany przez serwer wraz z innymi szczegami na temat twojego dania. Wczajc URL twojej aplikacji, pozwalasz administratorom danego serwera skontaktowa si z tob, jeli jest co le. 3. Obiekt opener utworzony wczeniej moe by take ponownie wykorzystany. Ponownie wysyamy te same dane, ale tym razem ze zmienionym nagwkiem User-Agent. 4. Tutaj wysyamy ustawiony przez nas nagwek User-Agent, w miejsce domylnego, wysyanego przez Pythona. Jeli bdziesz uwanie patrzy, zobaczysz, e zdeniowalimy nagwek User-Agent, ale tak naprawd wysalimy nagwek User-agent. Widzisz rnic? urllib2 zmienia litery w ten sposb, e tylko pierwsza litera jest wielka. W rzeczywistoci nie ma to adnego znaczenia. Specykacja HTTP mwi, e wielko liter nazwy pola nagwka nie jest wana.

12.6. KORZYSTANIE Z LAST-MODIFIED I ETAG

249

12.6

Korzystanie z Last-Modied i ETag

Korzystanie z Last-Modied i ETag


Teraz gdy ju wiesz jak dodawa wasne nagwki do swoich da HTTP, zobaczmy jak wykorzysta nagwki Last-Modied i ETag. Ponisze przykady pokazuj wyjcie z wyczonym debugowaniem. Jeli nadal masz je wczone (tak jak w poprzedniej sekcji), moesz je wyczy poprzez takie ustawienie: httplib.HTTPConnection.debuglevel = 0. Albo moesz pozostawi debugowanie wczone, jeli to Ci pomoe. Przykad 11.6 Testowanie Last-Modified >>> import urllib2 >>> request = urllib2.Request(http://diveintomark.org/xml/atom.xml) >>> opener = urllib2.build_opener() >>> firstdatastream = opener.open(request) >>> firstdatastream.headers.dict #(1) {date: Thu, 15 Apr 2004 20:42:41 GMT, server: Apache/2.0.49 (Debian GNU/Linux), content-type: application/atom+xml, last-modified: Thu, 15 Apr 2004 19:45:21 GMT, etag: "e842a-3e53-55d97640", content-length: 15955, accept-ranges: bytes, connection: close} >>> request.add_header(If-Modified-Since, ... firstdatastream.headers.get(Last-Modified)) #(2) >>> seconddatastream = opener.open(request) #(3) Traceback (most recent call last): File "<stdin>", line 1, in ? File "c:\python23\lib\urllib2.py", line 326, in open _open, req) File "c:\python23\lib\urllib2.py", line 306, in _call_chain result = func(*args) File "c:\python23\lib\urllib2.py", line 901, in http_open return self.do_open(httplib.HTTP, req) File "c:\python23\lib\urllib2.py", line 895, in do_open return self.parent.error(http, req, fp, code, msg, hdrs) File "c:\python23\lib\urllib2.py", line 352, in error return self._call_chain(*args) File "c:\python23\lib\urllib2.py", line 306, in _call_chain result = func(*args) File "c:\python23\lib\urllib2.py", line 412, in http_error_default raise HTTPError(req.get_full_url(), code, msg, hdrs, fp) urllib2.HTTPError: HTTP Error 304: Not Modified 1. Pamitasz wszystkie te nagwki HTTP, ktre byy wywietlane przy wczonym debugowaniu? To jest sposb na dotarcie do nich programowo: firstdatastream.headers jest obiektem dziaajcym jak sownik i pozwala on na pobranie kadego nagwka zwracanego przez serwer HTTP.

250

ROZDZIA 12. HTTP

2. Przy drugim daniu dodajemy nagwek If-Modified-Since z dat ostatniej modykacji z pierwszego dania. Jeli data nie ulega zmianie, serwer powinien zwrci kod statusu 304. 3. To wystarczy do stwierdzenia, e dane nie ulegy zmianie. Moemy zobaczy w zrzucie bdw, e urllib2 rzuci wyjtek specjalny: HTTPError w odpowiedzi na kod statusu 304. To jest troch niezwyke i nie cakiem pomocne. W kocu to nie jest bd; specjalnie poprosilimy serwer o nie przesyanie adnych danych, jeli nie ulegy one zmianie i dane nie ulegy zmianie, a wic serwer powiedzia, i nie wysa adnych danych. To nie jest bd; to jest dokadnie to czego oczekiwalimy. urllib2 rzuca wyjtek HTTPError take w sytuacjach, ktre mona traktowa jak bdy, np. 404 (strona nieznaleziona). Waciwie to rzuca on wyjtek HTTPError dla kadego kodu statusu innego ni 200 (OK), 301 (stae przekierowanie), lub 302 (tymczasowe przekierowanie). Do naszych celw byoby przydatne przechwycenie tych kodw statusw i po prostu zwrcenie ich bez rzucania adnych wyjtkw. Aby to zrobi, musimy zdeniowa wasn klas obsugi URL-i (ang. URL handler). Ponisza klasa obsugi URL-i jest czci moduu openanything.py. Przykad 11.7 Deniowanie klas obsugi URL-i class DefaultErrorHandler(urllib2.HTTPDefaultErrorHandler): #(1) def http_error_default(self, req, fp, code, msg, headers): #(2) result = urllib2.HTTPError( req.get_full_url(), code, msg, headers, fp) result.status = code #(3) return result 1. urllib2 jest zaprojektowana jako zbir klas obsugi URL-i. Kada z tych klas moe deniowa dowoln liczb metod. Gdy co si wydarzy jak np. bd HTTP lub nawet kod 304 urllib2 uywa introspekcji do odnalezienia w licie zdeniowanych klas obsugi URL-i metody, ktra moe obsuy to zdarzenie. Uywalimy podobnej introspekcji w rozdziale 9-tym, Przetwarzanie XML-a do zdeniowania metod obsugi dla rnych typw wzw, ale urllib2 jest bardziej elastyczny i przeszukuje tyle klas obsugi ile jest zdeniowanych dla biecego dania. 2. urllib2 przeszukuje zdeniowane klasy obsugi i wywouje metod http error default, gdy otrzyma kod statusu 304 od serwera. Deniujc wasn klas obsugi bdw, moemy zapobiec rzucaniu wyjtkw przez urllib2. Zamiast tego tworzymy obiekt HTTPError, ale zwracamy go, zamiast rzucania go jako wyjtek. 3. To jest kluczowa cz: przed zwrceniem zachowujemy kod statusu zwrcony przez serwer HTTP. To pozwala na dostp do niego programowi wywoujcemu. Przykad 11.8 Uywanie wasnych klas obsugi URL-i >>> request.headers #(1) {If-modified-since: Thu, 15 Apr 2004 19:45:21 GMT} >>> import openanything >>> opener = urllib2.build_opener( ... openanything.DefaultErrorHandler()) #(2)

12.6. KORZYSTANIE Z LAST-MODIFIED I ETAG >>> seconddatastream = opener.open(request) >>> seconddatastream.status 304 >>> seconddatastream.read()

251

#(3) #(4)

1. Kontynuujemy poprzedni przykad, a wic obiekt Request jest ju utworzony i nagwek If-Modied-Since zosta ju dodany. 2. To jest klucz: poniewa mamy zdeniowan wasn klas obsugi URL-i, musimy powiedzie urllib2, aby teraz jej uywa. Pamitasz jak mwiem, i urllib2 podzieli proces dostpu do zasobw HTTP na trzy etapy i to nie bez powodu? Oto dlaczego wywoanie funkcji build opener jest odrbnym etapem. Poniewa na jej wejciu moesz poda wasne klasy obsugi URL-i, ktre powoduj zmian domylnego dziaania urllib2. 3. Teraz moemy zasb otworzy po cichu a z powrotem otrzymujemy obiekt, ktry wraz z nagwkami (uyj seconddatastream.headers.dict, aby je pobra), zawiera take kod statusu HTTP. W tym wypadku, tak jak oczekiwalimy, tym statusem jest 304, ktry oznacza e dane nie zmieniy si od ostatniego razu, gdy o nie prosilimy. 4. Zauwa, e gdy serwer odsya kod statusu 304, to nie przesya ponownie danych. I o to w tym wszystkim chodzi: aby oszczdzi przepustowo poprzez niepobieranie ponowne danych, ktre nie ulegy zmianie. A wic jeli potrzebujesz tych danych, to musisz zachowa je w lokalnym buforze po pierwszym pobraniu. Z nagwka ETag korzystamy bardzo podobnie. Jednak zamiast sprawdzania nagwka Last-Modied i przesyania If-Modied-Since, sprawdzamy nagwek ETag a przesyamy If-None-Match. Zacznijmy cakiem now sesj w naszym IDE. Przykad 11.9 Uycie ETag/If-None-Match >>> import urllib2, openanything >>> request = urllib2.Request(http://diveintomark.org/xml/atom.xml) >>> opener = urllib2.build_opener( ... openanything.DefaultErrorHandler()) >>> firstdatastream = opener.open(request) >>> firstdatastream.headers.get(ETag) #(1) "e842a-3e53-55d97640" >>> firstdata = firstdatastream.read() >>> print firstdata #(2) <?xml version="1.0" encoding="iso-8859-1"?> <feed version="0.3" xmlns="http://purl.org/atom/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:lang="en"> <title mode="escaped">dive into mark</title> <link rel="alternate" type="text/html" href="http://diveintomark.org/"/> [.. ciach ..] >>> request.add_header(If-None-Match,

252 ... firstdatastream.headers.get(ETag)) >>> seconddatastream = opener.open(request) >>> seconddatastream.status 304 >>> seconddatastream.read() #(3) #(4) #(5)

ROZDZIA 12. HTTP

1. Uywajc pseudo-sownika firstdatastream.headers moemy pobra nagwek ETag zwrcony przez serwer. (Co si stanie, jeli serwer nie zwrci nagwka ETag? Wtedy ta linia powinna zwrci None.) 2. OK, mamy dane. 3. Teraz przy drugim wywoaniu ustawiamy w nagwku If-None-Match warto sumy kontrolnej z ETag otrzymanego przy pierwszym wywoaniu. 4. Drugie wywoanie dziaa prawidowo bez adnych zakce (bez rzucania adnych wyjtkw) i ponownie widzimy, e serwer odesa status kodu 304. Bazujc na sumie kontrolnej nagwka ETag, ktr wysalimy za drugim razem, wie on e dane nie zmieniy si. 5. Niezalenie od tego, czy kod 304 jest rezultatem sprawdzania daty Last-Modied czy sumy kontrolnej ETag, nigdy nie otrzymamy z powrotem ponownie tych samych danych, a jedynie kod statusu 304. I o to chodzio. W tych przykadach serwer HTTP obsugiwa zarwno nagwek Last-Modied jak i ETag, ale nie wszystkie serwery to potra. Jako uytkownik serwisw internetowych powiniene by przygotowany do uywania ich obu, ale musisz programowa defensywnie na wypadek gdyby serwer obsugiwa tylko jeden z tych nagwkw lub aden.

12.7. OBSUGA PRZEKIEROWA

253

12.7

Obsuga przekierowa

Obsuga przekierowa
Moemy obsugiwa trwae i tymczasowe przekierowania uywajc rnego rodzaju wasnych klas obsugi URL-i. Po pierwsze zobaczmy dlaczego obsuga przekierowa jest konieczna. Przykad 11.10 Dostp do usugi internetowej bez obsugi przekierowa >>> import urllib2, httplib >>> httplib.HTTPConnection.debuglevel = 1 #(1) >>> request = urllib2.Request( ... http://diveintomark.org/redir/example301.xml) #(2) >>> opener = urllib2.build_opener() >>> f = opener.open(request) connect: (diveintomark.org, 80) send: GET /redir/example301.xml HTTP/1.0 Host: diveintomark.org User-agent: Python-urllib/2.1 reply: HTTP/1.1 301 Moved Permanently\r\n #(3) header: Date: Thu, 15 Apr 2004 22:06:25 GMT header: Server: Apache/2.0.49 (Debian GNU/Linux) header: Location: http://diveintomark.org/xml/atom.xml #(4) header: Content-Length: 338 header: Connection: close header: Content-Type: text/html; charset=iso-8859-1 connect: (diveintomark.org, 80) send: GET /xml/atom.xml HTTP/1.0 #(5) Host: diveintomark.org User-agent: Python-urllib/2.1 reply: HTTP/1.1 200 OK\r\n header: Date: Thu, 15 Apr 2004 22:06:25 GMT header: Server: Apache/2.0.49 (Debian GNU/Linux) header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT header: ETag: "e842a-3e53-55d97640" header: Accept-Ranges: bytes header: Content-Length: 15955 header: Connection: close header: Content-Type: application/atom+xml >>> f.url #(6) http://diveintomark.org/xml/atom.xml >>> f.headers.dict {content-length: 15955, accept-ranges: bytes, server: Apache/2.0.49 (Debian GNU/Linux),

254

ROZDZIA 12. HTTP

last-modified: Thu, 15 Apr 2004 19:45:21 GMT, connection: close, etag: "e842a-3e53-55d97640", date: Thu, 15 Apr 2004 22:06:25 GMT, content-type: application/atom+xml} >>> f.status Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: addinfourl instance has no attribute status 1. Lepiej bdziesz mg zobaczy co si dzieje, gdy wczysz tryb debugowania. 2. To jest URL, ktry ma ustawione trwae przekierowanie do RSS-a pod adresem http://diveintomark.org/xml/atom.xml. 3. Gdy prbujemy pobra dane z tego adresu, to serwer odsya kod statusu 301 informujcy o tym, e ten zasb zosta przeniesiony na stae. 4. Serwer przesya take nagwek Location:, ktry zawiera nowy adres tych danych. 5. urllib2 zauwaa ten kod statusu dotyczcy przekierowania i automatycznie prbuje pobra dane spod nowej lokalizacji podanej w nagwku Location:. 6. Obiekt zwrcony przez opener zawiera ju nowy adres (po przekierowaniu) i wszystkie nagwki zwrcone po drugim daniu (zwrcone z nowego adresu). Jednak brakuje kodu statusu, a wic nie mamy moliwoci programowego stwierdzenia, czy to przekierowanie byo trwae, czy tylko tymczasowe. A to ma wielkie znaczenie: jeli to byo przekierowanie tymczasowe, wtedy musimy ponowne dania kierowa pod stary adres, ale jeli to byo trwae przekierowanie (jak w tym przypadku), to nowe dania od tego momentu powinny by kierowane do nowej lokalizacji. To nie jest optymalne, ale na szczcie atwe do naprawienia. urllib2 nie zachowuje si dokadnie tak, jak tego chcemy, gdy napotyka na kody 301 i 302, a wic zmiemy to zachowanie. Jak? Przy pomocy wasnej klasy obsugi URL-i, tak jak to zrobilimy w przypadku kodu 304. Ponisza klasa jest zdeniowana w openanything.py. Przykad 11.11 Deniowanie klasy obsugi przekierowa class SmartRedirectHandler(urllib2.HTTPRedirectHandler): #(1) def http_error_301(self, req, fp, code, msg, headers): result = urllib2.HTTPRedirectHandler.http_error_301( #(2) self, req, fp, code, msg, headers) result.status = code #(3) return result def http_error_302(self, req, fp, code, msg, headers): #(4) result = urllib2.HTTPRedirectHandler.http_error_302( self, req, fp, code, msg, headers) result.status = code

12.7. OBSUGA PRZEKIEROWA return result

255

1. Obsuga przekierowa w urllib2 jest zdeniowana w klasie o nazwie HTTPRedirectHandler. Nie chcemy cakowicie zmienia dziaania tej klasy, chcemy je tylko lekko rozszerzy, a wic dziedziczymy po niej, a wtedy bdziemy mogli wywoywa metody klasy nadrzdnej do wykonania caej cikiej roboty. 2. Gdy napotkany zostanie status kodu 301 przesany przez serwer, urllib2 przeszuka list klas obsugi i wywoa metod http error 301. Pierwsz rzecz, jak wykona nasza wersja, jest po prostu wywoanie metody http error 301 przodka, ktra wykona ca robot zwizan ze znalezieniem nagwka Location: i przekierowaniem dania pod nowy adres. 3. Tu jest kluczowa sprawa: zanim wykonamy return, zachowujemy kod statusu (301), aby program wywoujcy mg go pniej odczyta. 4. Przekierowania tymczasowe (kod statusu 302) dziaaj w ten sam sposb: nadpisujemy metod http error 302, wywoujemy metod przodka i zachowujemy kod statusu przed powrotem z metody. A wic jak mamy z tego korzy? Moemy teraz utworzy klas pozwalajc na dostp do zasobw internetowych wraz z nasz wasn klas obsugi przekierowa i bdzie ona nadal dokonywaa przekierowa automatycznie, ale tym razem bdzie ona take udostpniaa kod statusu przekierowania. Przykad 11.12 Uycie klasy obsugi przekierowa do wykrycia przekierowa trwaych >>> request = urllib2.Request(http://diveintomark.org/redir/example301.xml) >>> import openanything, httplib >>> httplib.HTTPConnection.debuglevel = 1 >>> opener = urllib2.build_opener( ... openanything.SmartRedirectHandler()) #(1) >>> f = opener.open(request) connect: (diveintomark.org, 80) send: GET /redir/example301.xml HTTP/1.0 Host: diveintomark.org User-agent: Python-urllib/2.1 reply: HTTP/1.1 301 Moved Permanently\r\n #(2) header: Date: Thu, 15 Apr 2004 22:13:21 GMT header: Server: Apache/2.0.49 (Debian GNU/Linux) header: Location: http://diveintomark.org/xml/atom.xml header: Content-Length: 338 header: Connection: close header: Content-Type: text/html; charset=iso-8859-1 connect: (diveintomark.org, 80) send: GET /xml/atom.xml HTTP/1.0 Host: diveintomark.org

256

ROZDZIA 12. HTTP

User-agent: Python-urllib/2.1 reply: HTTP/1.1 200 OK\r\n header: Date: Thu, 15 Apr 2004 22:13:21 GMT header: Server: Apache/2.0.49 (Debian GNU/Linux) header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT header: ETag: "e842a-3e53-55d97640" header: Accept-Ranges: bytes header: Content-Length: 15955 header: Connection: close header: Content-Type: application/atom+xml >>> f.status 301 >>> f.url http://diveintomark.org/xml/atom.xml

#(3)

1. Po pierwsze tworzymy opener z przed chwil zdeniowan klas obsugi przekierowa. 2. Wysalimy danie i otrzymalimy w odpowiedzi kod statusu 301. W tym momencie woana jest metoda http error 301. Wywoujemy metod przodka, ktra odnajduje przekierowanie i wysya danie pod now lokalizacj (http://diveintomark.org/xml/atom.xml). 3. Tu jest nasza korzy: teraz nie tylko mamy dostp do nowego URL-a, ale take do kodu statusu przekierowania, a wic moemy stwierdzi, e byo to przekierowanie trwae. Przy nastpnym daniu tych danych, powinnimy uy nowego adresu (http://diveintomark.org/xml/atom.xml, jak wida w f.url). Jeli mamy zachowan dan lokalizacj w pliku konguracyjnym lub w bazie danych, to powinnimy zaktualizowa j, aby nie odwoywa si ponownie do starego adresu. To jest pora do aktualizacji ksiki adresowej. Ta sama klasa obsugi przekierowa moe take pokaza, e nie powinnimy aktualizowa naszej ksiki adresowej. Przykad 11.13 Uycie klasy obsugi przekierowa do wykrycia przekierowa tymczasowych >>> request = urllib2.Request( ... http://diveintomark.org/redir/example302.xml) >>> f = opener.open(request) connect: (diveintomark.org, 80) send: GET /redir/example302.xml HTTP/1.0 Host: diveintomark.org User-agent: Python-urllib/2.1 reply: HTTP/1.1 302 Found\r\n header: Date: Thu, 15 Apr 2004 22:18:21 GMT header: Server: Apache/2.0.49 (Debian GNU/Linux) #(1)

#(2)

12.7. OBSUGA PRZEKIEROWA header: Location: http://diveintomark.org/xml/atom.xml header: Content-Length: 314 header: Connection: close header: Content-Type: text/html; charset=iso-8859-1 connect: (diveintomark.org, 80) send: GET /xml/atom.xml HTTP/1.0 Host: diveintomark.org User-agent: Python-urllib/2.1 reply: HTTP/1.1 200 OK\r\n header: Date: Thu, 15 Apr 2004 22:18:21 GMT header: Server: Apache/2.0.49 (Debian GNU/Linux) header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT header: ETag: "e842a-3e53-55d97640" header: Accept-Ranges: bytes header: Content-Length: 15955 header: Connection: close header: Content-Type: application/atom+xml >>> f.status 302 >>> f.url http://diveintomark.org/xml/atom.xml

257

#(3)

#(4)

1. To jest przykadowy URL skongurowany tak, aby powiadamia klientw o tymczasowym przekierowaniu do http://diveintomark.org/xml/atom.xml. 2. Serwer odsya z powrotem kod statusu 302 wskazujcy na tymczasowe przekierowanie. Tymczasowa lokalizacja danych jest podana w nagwku Location:. 3. urllib2 wywouje nasz metod http error 302, ktra wywouje metod przodka o tej samej nazwie w urllib2.HTTPRedirectHandler, ktra wykonuje przekierowanie do nowej lokalizacji. Wtedy nasza metoda http error 302 zachowuje kod statusu (302), a wic wywoujca aplikacja moe go pniej odczyta. 4. I oto mamy prawidowo wykonane przekierowanie do http://diveintomark.org/xml/atom.xml. f.status informuje, i byo to przekierowanie tymczasowe, co oznacza, e ponowne dania powinnimy kierowa pod stary adres (http://diveintomark.org/redir/example302.xml). Moe nastpnym razem znowu nastpi przekierowanie, a moe nie. Moe nastpi przekierowanie pod cakiem inny adres. Nie do nas naley ta decyzja. Serwer powiedzia, e to przekierowanie byo tylko tymczasowe, a wic powinnimy to uszanowa. Teraz dostarczamy wystarczajc ilo informacji, aby aplikacja wywoujca bya w stanie to uszanowa.

258

ROZDZIA 12. HTTP

12.8

Obsuga skompresowanych danych

Obsuga skompresowanych danych


Ostatni wan waciwoci HTTP, ktr bdziemy chcieli obsuy, bdzie kompresja. Wiele serwisw sieciowych posiada zdolno wysyania skompresowanych danych, dziki czemu wielko wysyanych danych moe zmale nawet o 60% lub wicej. Sprawdza si to w szczeglnoci w XML-owych serwisach sieciowych, poniewa dane XML kompresuj si bardzo dobrze. Serwery nie dadz nam skompresowanych danych, jeli im nie powiemy, e potramy je obsuy. Przykad 11.14. Informowanie serwera, e chcielibymy otrzyma skompresowane dane >>> import urllib2, httplib >>> httplib.HTTPConnection.debuglevel = 1 >>> request = urllib2.Request(http://diveintomark.org/xml/atom.xml) >>> request.add_header(Accept-encoding, gzip) #(1) >>> opener = urllib2.build_opener() >>> f = opener.open(request) connect: (diveintomark.org, 80) send: GET /xml/atom.xml HTTP/1.0 Host: diveintomark.org User-agent: Python-urllib/2.1 Accept-encoding: gzip #(2) reply: HTTP/1.1 200 OK\r\n header: Date: Thu, 15 Apr 2004 22:24:39 GMT header: Server: Apache/2.0.49 (Debian GNU/Linux) header: Last-Modified: Thu, 15 Apr 2004 19:45:21 GMT header: ETag: "e842a-3e53-55d97640" header: Accept-Ranges: bytes header: Vary: Accept-Encoding header: Content-Encoding: gzip #(3) header: Content-Length: 6289 #(4) header: Connection: close header: Content-Type: application/atom+xml 1. To jest najwaniejsza cz: kiedy utworzymy obiekt Request, dodajemy nagwek Accept-encoding, aby powiedzie serwerowi, e akceptujemy dane zakodowane jako gzip. gzip jest nazw wykorzystanego algorytmu kompresji. Teoretycznie powinny by dostpne inne algorytmy kompresji, ale gzip jest algorytmem kompresji wykorzystywanym przez 99% serwisw sieciowych. 2. W tym miejscu nagwek idzie przez linie. 3. I w tym miejscu otrzymujemy informacje o tym, co serwer przesya nam z powrotem: nagwek Content-Encoding: gzip oznacza, e dane ktre otrzymalimy zostay skompresowane jako gzip.

12.8. OBSUGA SKOMPRESOWANYCH DANYCH

259

4. Nagwek Content-Length oznacza dugo danych skompresowanych, a nie zdekompresowanych. Jak zobaczymy za minutk, rzeczywista wielko zdekompresowanych danych wynosi 15955, zatem dane zostay skompresowane o ponad 60%. Przykad 11.15. Dekompresowanie danych >>> compresseddata = f.read() #(1) >>> len(compresseddata) 6289 >>> import StringIO >>> compressedstream = StringIO.StringIO(compresseddata) #(2) >>> import gzip >>> gzipper = gzip.GzipFile(fileobj=compressedstream) #(3) >>> data = gzipper.read() #(4) >>> print data #(5) <?xml version="1.0" encoding="iso-8859-1"?> <feed version="0.3" xmlns="http://purl.org/atom/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:lang="en"> <title mode="escaped">dive into mark</title> <link rel="alternate" type="text/html" href="http://diveintomark.org/"/> <-- rest of feed omitted for brevity --> >>> len(data) 15955 1. Kontynuujc z poprzedniego przykadu, f jest obiektem plikopodobnym zwrconym przez otwieracz URL-a. Korzystajc z jego metody read() zazwyczaj dostaniemy nieskompresowane dane, ale poniewa te dane bd skompresowane gzipem, to jest dopiero pierwszy krok, aby otrzyma dane, ktre naprawd chcemy. 2. OK, ten krok jest troszeczk okrny. Python posiada modu gzip, ktry czyta (i waciwie take zapisuje) pliki skompresowane jako gzip. Jednak my nie mamy pliku na dysku, mamy skompresowany bufor w pamici, a nie chcemy tworzy tymczasowego pliku, aby te dane dekompresowa. Zatem tworzymy obiekt plikopodobny przechowujcy w pamici skompresowane dane (compresseddata) korzystajc z moduu StringIO. Pierwszy raz wspomnielimy o StringIO w poprzednim rozdziale, ale teraz znalelimy kolejny sposb, aby go wykorzysta. 3. Teraz tworzymy instancj klasy GzipFile. Ten plik jest obiektem plikopodobnym compressedstream. 4. To jest linia, ktra wykonuje ca waciw prac: czyta z GzipFile zdekompresowane dane. Ten plik nie jest prawdziwym plikiem na dysku. gzipper w rzeczywistoci czyta z obiektu plikopodobnego, ktry stworzylimy za pomoc StringIO, aby opakowa skompresowane dane znajdujce si tylko w pamici w zmiennej compresseddata w obiekt plikopodobny. Ale skd przyszy te skompresowane dane? Oryginalnie pobralimy je z odlegego serwera HTTP dziki odczytaniu obiektu plikopodobnego, ktry utworzylimy za pomoc urllib2.build opener. I fantastycznie, to wszystko po prostu dziaa. aden element w tym acuchu nie ma pojcia, e jego poprzednik tylko udaje, e jest tym za co si podaje.

260

ROZDZIA 12. HTTP

5. Zobaczmy, s to prawdziwe dane. (tak naprawd 15955 bajtw) Lecz czekaj! usyszaem Twj krzyk, Mona to nawet zrobi prociej. Mylisz, e skoro opener.open zwraca obiekt plikopodobny, wic dlaczego nie wyci porednika StringIO i po prostu przekaza f bezporednio do GzipFile? OK, moe tak nie mylae, ale tak czy inaczej nie przejmuj si tym, poniewa to nie dziaa. Przykad 11.16. Dekompresowanie danych bezporednio z serwera >>> f = opener.open(request) #(1) >>> f.headers.get(Content-Encoding) #(2) gzip >>> data = gzip.GzipFile(fileobj=f).read() #(3) Traceback (most recent call last): File "<stdin>", line 1, in ? File "c:\python23\lib\gzip.py", line 217, in read self._read(readsize) File "c:\python23\lib\gzip.py", line 252, in _read pos = self.fileobj.tell() # Save current position AttributeError: addinfourl instance has no attribute tell 1. Kontynuujc z poprzedniego przykadu, mamy ju obiekt dania Request z ustawionym nagwkiem Accept-encoding: gzip header. 2. Zaraz po otworzeniu dania otrzymujemy nagwki z serwera (ale nie pobieramy jeszcze adnych danych). Jak moemy zobaczy ze zwrconego nagwka Content-Encoding, dane te zostay skompresowane na gzip. 3. Poniewa opener.open zwraca obiekt plikopodobny, a z dopiero co odczytanego nagwka wynika, e otrzymane dane bd skompresowane na gzip-a, to dlaczego nie przekaza prosto otrzymany obiekt plikopodobny bezporednio do GzipFile? Kiedy czytamy z instancji GzipFile-a, bdziemy czytali skompresowane dane z serwera HTTP i dekompresowali je w locie. Jest to dobry pomys, ale niestety nie dziaa. GzipFile potrzebuje zapisywa swoj pozycj i przesuwa si bliej lub dalej po skompresowanym pliku, poniewa w taki sposb dziaa kompresja gzip. Nie dziaa to, kiedy plikiem jest strumie bajtw przychodzcych z zewntrznego serwera; jedynie co moemy z tym zrobi, to jednorazowo pobiera dane bajty nie przesuwajc si wstecz lub do przodu w strumieniu danych. Zatem nieelegancki sposb z wykorzystaniem StringIO jest najlepszym rozwizaniem: pobieramy skompresowane dane, tworzymy z nich obiekt plikopodobny za pomoc StringIO, a nastpnie dekompresujemy dane wewntrz niego.

12.9. HTTP - WSZYSTKO RAZEM

261

12.9

HTTP - wszystko razem

Python/HTTP wszystko razem


Widzielimy ju wszystkie elementy potrzebne do utworzenia inteligentnego klienta usugi internetowej. Teraz zobaczmy jak to wszystko do siebie pasuje. Przykad 11.17. Funkcja openanything Ta funkcja jest zdeniowana w pliku openanything.py. def openAnything(source, etag=None, lastmodified=None, agent=USER_AGENT): # non-HTTP code omitted for brevity if urlparse.urlparse(source)[0] == http: # open URL with urllib2 request = urllib2.Request(source) request.add_header(User-Agent, agent) if etag: request.add_header(If-None-Match, etag) if lastmodified: request.add_header(If-Modified-Since, lastmodified) request.add_header(Accept-encoding, gzip) opener = urllib2.build_opener(SmartRedirectHandler(), DefaultErrorHandler()) return opener.open(request) 1. urlparse to bardzo porczny modu do, na pewno zgade, parsowania URL-i. Jego podstawowa funkcja, take nazywajca si urlparse, przyjmuje na wejciu URL-a i dzieli go na tak krotk (schemat, domena, cieka, parametry, parametry w daniu i identykator fragmentu). Jednyn wsrd tych rzeczy jak musimy si przejmowa jest schemat, ktry decyduje o tym czy mamy do czynienie z URL-em HTTP (ktry to modu urllib2 moe obsuy). 2. Przedstawiamy si serwerowi HTTP przy pomocy nagwka User-Agent przesanego przez funkcj wywoujc. Jeli nie zostaaby podana warto User-Agent, uylibysmy wczeniej zdeniowanej wartoi w openanything.py. Nigdy nie naley uywa domylnej wartoci zdeniowanej w urllib2. 3. Jeli zostaa podana suma kontrolna dla ETag, wysyamy j w nagwku If-None-Match. 4. Jeli zostaa podana data ostatniej modykacji, wysyamy j w nagwku If-Modified-Since. 5. Powiadamiamy serwer, e chcemy dane skompresowane, jeli to jest tylko moliwe. 6. Wywoujemy funkcj build opener, ktra wykorzystuje nasze wasne klasy obsugi URL-i: SmartRedirectHandler do obsugi przekierowa 301 i 302 i DefaultErrorHandler do taktownej obsugi 304, 404, i innych bdnych sytuacji. 7. I to wszystko! Otwieramy URL-a i zwracamy pliko-podobny (ang. le-like) obiekt do funkcji wywoujcej. Przykad 11.18. Funkcja fetch Ta funkcja jest zdeniowana w pliku openanything.py.

#(1)

#(2) #(3) #(4) #(5) #(6) #(7)

262

ROZDZIA 12. HTTP

def fetch(source, etag=None, last_modified=None, agent=USER_AGENT): Fetch data and metadata from a URL, file, stream, or string result = {} f = openAnything(source, etag, last_modified, agent) #(1) result[data] = f.read() #(2) if hasattr(f, headers): # save ETag, if the server sent one result[etag] = f.headers.get(ETag) #(3) # save Last-Modified header, if the server sent one result[lastmodified] = f.headers.get(Last-Modified) #(4) if f.headers.get(content-encoding, ) == gzip: #(5) # data came back gzip-compressed, decompress it result[data] = gzip.GzipFile(fileobj=StringIO(result[data]])).read() if hasattr(f, url): #(6) result[url] = f.url result[status] = 200 if hasattr(f, status): #(7) result[status] = f.status f.close() return result 1. Po pierwsze wywoujemy funkcj openAnything z URL-em, sum kontroln ETag, dat ostatniej modykacji (ang. Last-Modied date) i wartoci User-Agent. 2. Czytamy aktualne dane zwrcone przez serwer. Mog one by spakowane; jeli tak, to pniej je rozpakowujemy. 3. Zachowujemy sum kontroln ETag wrcon przez serwer, take aplikacja wywoujca moe j przesa ponownie nastpnym razem i moemy j przekaza dalej do openAnything, ktra moe j wetkn do nagwka If-None-Match i przesa do zdalnego serwera. 4. Zachowujemy take dat ostatniej modykacji. 5. Jeli serwer powiedzia, e wysa spakowane dane, rozpakowujemy je. 6. Jeli dostalimy URL-a z powrotem od serwera, zachowujemy go i zakadamy, e kod statusu wynosi 200, dopki nie przekonamy si, e jest inaczej. 7. Jeli ktra z naszych klas obsugi URL-i przechwyci jaki kod statusu, zachowujemy go take. Przykad 11.19. Uycie openanything.py >>> import openanything >>> useragent = MyHTTPWebServicesApp/1.0 >>> url = http://diveintopython.org/redir/example301.xml >>> params = openanything.fetch(url, agent=useragent) >>> params {url: http://diveintomark.org/xml/atom.xml, lastmodified: Thu, 15 Apr 2004 19:45:21 GMT, etag: "e842a-3e53-55d97640",

#(1) #(2)

12.9. HTTP - WSZYSTKO RAZEM status: 301, data: <?xml version="1.0" encoding="iso-8859-1"?> <feed version="0.3" <-- rest of data omitted for brevity -->} >>> if params[status] == 301: ... url = params[url] >>> newparams = openanything.fetch( ... url, params[etag], params[lastmodified], useragent) >>> newparams {url: http://diveintomark.org/xml/atom.xml, lastmodified: None, etag: "e842a-3e53-55d97640", status: 304, data: }

263

#(3)

#(4)

#(5)

1. Za pierwszym razem, gdy pobieramy jaki zasb, nie mamy adnej sumy kontrolnej ETag ani daty ostatniej modykacji, a wic opuszczamy te parametry. (To s parametry opcjonalne.) 2. Z powrotem otrzymujemy sownik kilku uytecznych nagwkw, kod statusu HTTP i aktualne dane zwrcone przez serwer. Funkcja openanything zajmuje si samodzielnie rozpakowaniem archiwum gzip; nie zajmujemy si tym na tym poziomie. 3. Jeli kiedykolwiek otrzymamy kod statusu 301, czyli trwae przekierowanie, to musimy zaktualizowa naszego URL-a na nowy adresu. 4. Gdy po raz drugi pobieramy ten sam zasb, to mamy wiele informacji, ktre moemy przekaza: (by moe zaktualizowany) URL, ETag z ostatniego razu, data ostatniej modykacji i oczywicie nasz User-Agent. 5. Z powrotem ponownie otrzymujemy sownik, ale dane nie ulegy zmianie, a wic wszystko co dostalimy to by kod statusu 304 i adnych danych.

264

ROZDZIA 12. HTTP

12.10

HTTP - podsumowanie

Podsumowanie
Teraz modu openanything.py i jego funkcje powinny mie dla Ciebie sens. Moemy wyrni 5 wanych cech usug internetowych na bazie HTTP, ktre kady klient powinien uwzgldnia: Identykacja aplikacji poprzez waciwe ustawienie nagwka User-Agent. Waciwa obsuga trwaych przekierowa. Uwzgldnienie sprawdzania daty ostatniej modykacji (ang. Last-Modied), aby unikn ponownego pobierania danych, ktre nie ulegy zmianie. Uwzgldnienie sprawdzania sum kontrolnych z nagwka ETag, aby unikn ponownego pobierania danych, ktre nie ulegy zmianie. Uwzgldnienie kompresji gzip, aby zredukowa wielko przesyanych danych, nawet gdy dane ulegy zmianie.

Rozdzia 13

SOAP

265

266

ROZDZIA 13. SOAP

13.1
SOAP

SOAP

Rozdzia 11 przybliy temat serwisw sieciowych HTTP zorientowanych na dokumenty. Wejciowym parametrem by URL, a zwracan wartoci by konkretny dokument XML, ktry mona byo sparsowa. Ten rozdzia przybliy serwis sieciowy SOAP, ktry jest bardziej strukturalnym podejciem do problemu. Zamiast zajmowa si bezporednio daniami HTTP i dokumentami XML, SOAP pozwala nam symulowa wywoywanie funkcji, ktre zwracaj natywne typy danych. Jak zobaczymy, zudzenie to jest niemal perfekcyjne: wywoujemy funkcj za pomoc biblioteki SOAP korzystajc ze standardowej, wywoujcej skadni Pythona a funkcja zdaje si zwraca obiekty i wartoci Pythona. Jednak pod t przykrywk, biblioteka SOAP w rzeczywistoci wykonuje zoon transakcj wymagajc wielu dokumentw XML i zdalnego serwera. SOAP jest zoon specykacj i powiedzenie, e SOAP suy wycznie do zdalnego wywoywania funkcji bdzie troch wprowadzao w bd. Niektrzy mogliby stwierdzi, e SOAP pozwala na jednostronne, asynchroniczne przekazywanie komunikatw i zorientowane na dokumenty serwisy sieciowe. I Ci ludzie take mieliby racj; SOAP moe by wykorzystywany w ten sposb, a take na wiele innych. Jednak ten rozdzia przybliy tak zwany styl RPC (Remote Procedure Call), czyli wywoywanie zewntrznych funkcji i otrzymywanie z nich wynikw.

Nurkujemy
Korzystasz z Google, prawda? Jest to popularna wyszukiwarka. Chciae kiedy mie programowy dostp do wynikw wyszukiwania za pomoc Google? Teraz moesz. Poniej mamy program, ktry poszukuje w Google za pomoc Pythona. Przykad 12.1. search.py from SOAPpy import WSDL # youll need to configure these two values; # see http://www.google.com/apis/ WSDLFILE = /path/to/copy/of/GoogleSearch.wsdl APIKEY = YOUR_GOOGLE_API_KEY _server = WSDL.Proxy(WSDLFILE) def search(q): """Search Google and return list of {title, link, description}""" results = _server.doGoogleSearch( APIKEY, q, 0, 10, False, "", False, "", "utf-8", "utf-8") return [{"title": r.title.encode("utf-8"), "link": r.URL.encode("utf-8"), "description": r.snippet.encode("utf-8")} for r in results.resultElements] if __name__ == __main__: import sys for r in search(sys.argv[1])[:5]: print r[title]

13.1. NURKUJEMY print r[link] print r[description] print

267

Moesz importowa to jako modu i wykorzystywa to w wikszych programach, a take moesz uruchomi ten skrypt z linii polece. W linii polece przekazujemy zapytanie szukania jako argument linii polece, a program wypisuje nam URL, tytu i opis z piciu pierwszych wynikw wyszukiwania. Tutaj mamy przykadowe wyjcie, gdy wyszkujemy sowo python. Przykad 12.2. Przykadowe uycie search.py C:\diveintopython\common\py> python search.py "python" <b>Python</b> Programming Language http://www.python.org/ Home page for <b>Python</b>, an interpreted, interactive, object-oriented, extensible<br> programming language. <b>...</b> <b>Python</b> is OSI Certified Open Source: OSI Certified. <b>Python</b> Documentation Index http://www.python.org/doc/ <b>...</b> New-style classes (aka descrintro). Regular expressions. Database API. Email Us.<br> docs@<b>python</b>.org. (c) 2004. <b>Python</b> Software Foundation. <b>Python</b> Documentation. <b>...</b> Download <b>Python</b> Software http://www.python.org/download/ Download Standard <b>Python</b> Software. <b>Python</b> 2.3.3 is the current production<br> version of <b>Python</b>. <b>...</b> <b>Python</b> is OSI Certified Open Source: Pythonline http://www.pythonline.com/

Dive Into <b>Python</b> http://diveintopython.org/ Dive Into <b>Python</b>. <b>Python</b> from novice to pro. Find: <b>...</b> It is also available in multiple<br> languages. Read Dive Into <b>Python</b>. This book is still being written. <b>...</b>

268

ROZDZIA 13. SOAP

13.2

Instalowanie odpowiednich bibliotek

Instalowanie odpowiednich bibliotek


W odrnieniu od pozostaego kodu w tej ksice, ten rozdzia wymaga bibliotek, ktre nie s instalowane wraz z Pythonem. Zanim zanurkujemy w usugi SOAP, musisz doinstalowa trzy biblioteki: PyXML, fpconst i SOAPpy. 12.2.1. Instalacja PyXML Pierwsz bibliotek jakiej potrzebujemy jest PyXML, zbir bibliotek do obsugi XML, ktre dostarczaj wiksz funkcjonalno ni wbudowane biblioteki XML, ktre omawialimy w rozdziale 9. Procedura 12.1. Oto sposb instalacji PyXML: 1. Wejd na http://pyxml.sourceforge.net/, kliknij Downloads i pobierz ostatni wersj dla Twojego systemu operacyjnego. 2. Jeli uzywasz Windowsa, to masz kilka moliwoci. Upewnij si, e pobierasz wersj PyXML, ktra odpowiada wersji Pythona, ktrego uywasz. 3. Kliknij dwukrotnie na pliku instalatora. Jeli pobrae PyXML 0.8.3 dla Windowsa i Pythona 2.3, to programem instalatora bdzie plik PyXML-0.8.3.win32py2.3.exe. 4. Wykonaj wszystkie kroki instalatora. 5. Po zakoczeniu instalacji zamknij instalator. Nie bdzie adnych widocznych skutkw tego, i instalacja zakoczya si powodzeniem (adnych programw zaistalowanych w menu Start lub nowych skrtw na pulpicie). PyXML jest po prostu zbiorem bibliotek XML uywanych przez inne programy. Aby zwerykowa czy PyXML zainstalowa si poprawnie, uruchom IDE Pythona i sprawd wersj zainstalowanych bibliotek XML, tak jak w tym przykadzie. Przykad 12.3. Werykacja instalacji PyXML >>> import xml >>> xml.__version__ 0.8.3 Ta wersja powinna odpowiada numerowi wersji instalatora PyXML, ktry pobrae i uruchomie. 12.2.2. Instalacja fpconst Drug bibliotek jak potrzebujemy jest fpconst, zbir staych i funkcji do obsugi wartoci zmienno-przecinkowych IEEE754. Dostarcza ona wartoci specjalne To-NieLiczba (ang. Not-a-Number) (NaN), Dodatnia Nieskoczono (ang. Positive Innity) (Inf) i Ujemna Nieskoczonos (ang. Negative Innity) (-Inf), ktre s czci specykacji typw danych SOAP. Procedura 12.2. A oto procedura instalacji fpconst: 1. Pobierz ostatni wersj fpconst z http://www.analytics.washington.edu/ statcomp/projects/rzope/fpconst/ lub http://www.python.org/pypi/fpconst/.

13.2. INSTALOWANIE ODPOWIEDNICH BIBLIOTEK

269

2. S tam dwa pliki do pobrania, jeden w formacie .tar.gz, a drugi w formacie .zip. Jeli uywasz Windowsa, pobierz ten w formacie .zip; w przeciwnym razie ten w formacie .tar.gz. 3. Rozpakuj pobrany plik. W Windows XP moesz klikn prawym przyciskiem na pliku i wybra pozycj Extract All; na wczeniejszych wersjach Windowsa bdzie potrzebny dodatkowy program, np. WinZip. Na Mac OS X moesz klikn dwukrotnie na spakowanym pliku, aby go rozpakowa przy pomocy Stuffit Expander. 4. Otwrz okno linii polece i przejd do katalogu, w ktrym rozpakowae pliki fpconst. 5. Wpisz python setup.py install, aby uruchomi program instalujcy. Aby zwerykowa, czy fpconst zainstalowa si prawidowo, uruchom IDE Pythona i sprawd numer wersji. Przykad 12.4. Werykacja instalacji fpconst >>> import fpconst >>> fpconst.__version__ 0.6.0 Ten numer wersji powinien odpowiada wersji archiwum fpconst, ktre pobrae i zainstalowae. 12.2.3. Instalacja SOAPpy Trzecim i ostatnim wymogiem jest sama biblioteka: SOAPpy. Procedura 12.3. A oto procedura instalacji SOAPpy: 1. Wejd na http://pywebsvcs.sourceforge.net/ i wybierz Ostatnie Ocjalne Wydanie (ang. Latest Ocial Release) w sekcji SOAPpy. 2. S tam dwa pliki do wyboru. Jeli uywasz Windowsa, pobierz plik .zip; w przeciwnym wypadku pobierz plik .tar.gz. 3. Rozpakuj pobrany plik, tak jak to zrobie z fpconst. 4. Otwrz okno linii polece i przejd do katalogu, w ktrym rozpakowae pliki SOAPpy. 5. Wpisz python setup.py install, aby uruchomi program instalujcy. Aby zwerykowa, czy SOAPpy zostao zainstalowane poprawnie, uruchom IDE Pythona i sprawd numer wersji. Przykad 12.5. Werykacja instalacji SOAPpy >>> import SOAPpy >>> SOAPpy.__version__ 0.11.4 Ten numer wersji powinien odpowiada wersji archiwum SOAPpy, ktre pobrae i zainstalowae.

270

ROZDZIA 13. SOAP

13.3

Pierwsze kroki z SOAP

Pierwsze kroki z SOAP


Sercem SOAP jest zdolno wywoywania zdalnych funkcji. Jest wiele publicznie dostpnych serwerw SOAP, ktre udostpniaj proste funkcje do celw demonstracyjnych. Najbardziej popularnym publicznie dostpnym serwerem SOAP jest http://www. xmethods.net/. Poniszy przykad wykorzystuje funkcj demostracyjn, ktra pobiera kod pocztowy w USA i zwraca aktualn temperatur w tym regionie. Przykad 12.6. Pobranie aktualnej temperatury >>> from SOAPpy import SOAPProxy #(1) >>> url = http://services.xmethods.net:80/soap/servlet/rpcrouter >>> namespace = urn:xmethods-Temperature #(2) >>> server = SOAPProxy(url, namespace) #(3) >>> server.getTemp(27502) #(4) 80.0 1. Dostp do zdalnego serwera SOAP moemy uzyska poprzez klas proxy SOAPProxy. Proxy wykonuje ca wewntrzn robot zwizan z SOAP, wcznie z utworzeniem dokumentu XML dania z nazwy funkcji i listy jej argumentw, wysaniem dania za porednictwem HTTP do zdalnego serwera SOAP, sparsowaniem dokumentu XML odpowiedzi i utworzeniem wbudowanych wartoci Pythona, ktre zwraca. Zobaczymy jak wygldaj te dokumenty XML w nastpnej sekcji. 2. Kada usuga SOAP posiada URL, ktry obsuguje wszystkie dania. Ten sam URL jest uywany do wywoywania wszystkich funkcji. Ta konkretna usuga ma tylko jedn funkcj, ale pniej w tym rozdziale zobaczymy przykady API Googlea, ktre ma kilka funkcji. URL usugi jest wspdzielony przez wszystkie funkcje. Kada usuga SOAP ma take przestrze nazw, ktra jest deniowana przez serwer i jest zupenie dowolna. Jest ona po prostu czci konguracji wymagan do wywoywania metod SOAP. Pozwala ona serwerowi na wykorzystywanie jednego URL-a dla usugi i odpowiednie przekierowywanie da pomidzy kilkoma niepowizanymi ze sob usugami. To jest podobne do podziau moduw Pythona na pakiety. 3. Tworzymy instancj SOAPProxy podajc URL usugi i przestrze nazw usugi. Ta operacja nie wykonuje jeszcze adnego poczenia z serwerem SOAP; ona po prostu tworzy lokalny obiekt Pythona. 4. Teraz, gdy wszystko jest odpowiednio skongurowane, moemy waciwie wywoa zdalne metody SOAP tak jakby to byy lokalne funkcje. Przekazujemy argumenty tak jak do normalnych funkcji i pobieramy wartoci zwrotne te tak jak od normalnych funcji. Ale pod spodem tak naprawd dzieje si niezwykle duo. A wic zajrzyjmy pod spd.

13.4. DEBUGOWANIE SERWISU SIECIOWEGO SOAP

271

13.4

Debugowanie serwisu sieciowego SOAP

Debugowanie serwisu sieciowego SOAP


Biblioteki SOAP dostarczaj atwego sposobu na zobaczenie co si tak naprawd dzieje za kulisami. Wczenie debugowania to jest po prostu kwestia ustawienia dwch ag w konguracji SOAPProxy. Przykad 12.7. Debugowanie serwisw SOAP >>> from SOAPpy import SOAPProxy >>> url = http://services.xmethods.net:80/soap/servlet/rpcrouter >>> n = urn:xmethods-Temperature >>> server = SOAPProxy(url, namespace=n) #(1) >>> server.config.dumpSOAPOut = 1 #(2) >>> server.config.dumpSOAPIn = 1 >>> temperature = server.getTemp(27502) #(3) *** Outgoing SOAP ****************************************************** <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/1999/XMLSchema"> <SOAP-ENV:Body> <ns1:getTemp xmlns:ns1="urn:xmethods-Temperature" SOAP-ENC:root="1"> <v1 xsi:type="xsd:string">27502</v1> </ns1:getTemp> </SOAP-ENV:Body> </SOAP-ENV:Envelope> ************************************************************************ *** Incoming SOAP ****************************************************** <?xml version=1.0 encoding=UTF-8?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SOAP-ENV:Body> <ns1:getTempResponse xmlns:ns1="urn:xmethods-Temperature" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <return xsi:type="xsd:float">80.0</return> </ns1:getTempResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> ************************************************************************ >>> temperature 80.0 1. Po pierwsze tworzymy normalnie SOAPProxy podajc URL serwisu i przestrze nazw.

272

ROZDZIA 13. SOAP

2. Po drugie wczamy debugowanie poprzez ustawienie server.config.dumpSOAPIn i server.config.dumpSOAPOut. 3. Po trzecie wywoujemy jak zwykle zdaln metod SOAP. Biblioteka SOAP wywietli zarwno wychodzcy dokument XML dania, jak i przychodzcy dokument XML odpowiedzi. To jest caa cika praca jak SOAPProxy wykonuje dla Ciebie. Przeraajce, nie prawda? Rozbiemy to na czynniki. Wikszo dokumentu XML dania, ktry jest wysyany do serwera, to s elementy stae. Zignoruj wszystkie te deklaracje przestrzeni nazw; one nie ulegaj zmianie (lub s bardzo podobne) w trakcie wszystkich wywoa SOAP. Sercem wywoania funkcji jest ten fragment w elemencie <Body>: <ns1:getTemp xmlns:ns1="urn:xmethods-Temperature" SOAP-ENC:root="1"> <v1 xsi:type="xsd:string">27502</v1> </ns1:getTemp> #(1) #(2) #(3)

1. Nazw elementu jest nazwa funkcji: getTemp. SOAPProxy uywa getattr jako dyspozytora. Zamiast wywoywania poszczeglnych metod lokalnych bazujc na nazwie metody, uywa on nazwy metody do skonstruowania dokumentu XML dania. 2. Element XML-a dotyczcy funkcji zawarty jest w konkretnej przestrzeni nazw, ktra to jest ta podana podczas tworzenia instancji klasy SOAPProxy. Nie przejmuj si tym SOAP-ENC:root; to te jest stay element. 3. Argumenty funkcji take zostay przeksztacone na XML-a. SOAPProxy uywajc introspekcji analizuje kady argument, aby okresli jego typ (w tym wypadku jest to string). Typ argumentu traa do atrybutu xsi:type, a zaraz za nim podana jest jego warto. Zwracany dokument XML jest rwnie prosty do zrozumienia, jeli tylko wiesz co naley zignorowa. Skup si na tym fragmencie wewntrz elementu <Body>: <ns1:getTempResponse #(1) xmlns:ns1="urn:xmethods-Temperature" #(2) SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <return xsi:type="xsd:float">80.0</return> #(3) </ns1:getTempResponse> 1. Serwer zawar warto zwracan przez funkcj w elemencie <getTempResponse>. Zgodnie z konwencj ten element jest nazw funkcji plus sowo Response. Ale tak naprawd to moe by prawie cokolwiek; wan rzecz jak SOAPProxy rozpatruje nie jest nazwa elementu, ale przestrze nazw. 2. Serwer zwraca odpowied w tej samej przestrzeni nazw, ktrej uylimy w daniu, tej samej przestrzeni nazw, ktr podalimy, gdy po raz pierwszy tworzylimy obiekt klasy SOAPProxy. Dalej w tym rozdziale zobaczymy co si stanie, jeli zapomnimy poda przestrze nazw podczas tworzenia obiektu SOAPProxy. 3. Zwracana warto jest podana wraz z jej typem (czyli oat). SOAPProxy korzysta z tego typu danych do utworzenia waciwego wbudowanego typu danych Pythona i zwraca go.

13.5. WPROWADZENIE DO WSDL

273

13.5

Wprowadzenie do WSDL

Wprowadzenie do WSDL
Klasa SOAPProxy przeroczycie przeksztaca wywoania lokalnych metod na wywoania zdalnych metod SOAP. Jak moglimy zobaczy, jest z tym duo roboty, ale SOAPProxy wykonuje to szybko i niewidocznie. Jednak nie dostarcza on adnych rodkw sucych do introspekcji metod. Rozwamy to: dwa poprzednie podrozdziay pokazay przykady wywoywania prostych zdalnych metod SOAP z jednym argumentem i jedn zwracan wartoci, a obydwa byy prostym typem danych. Wymagao to znajomoci URL-a serwisu, przestrzeni nazw serwisu, nazwy funkcji, liczby argumentw i typu danych kadego argumentu. Jeli co z tego pominiemy lub popenimy w czym bd, wszystko nawali. Jednak nie powinno to by wielk niespodziank. Jeli chcemy wywoa lokaln funkcj, musimy zna pakiet lub modu, w ktrym ona si znajduje (odpowiednik URL-a serwisu i przestrzeni nazw). Potrzebujemy take zna poprawn nazw funkcji i poprawn liczb argumentw. Python doskonale radzi sobie z typami danych bez wyranego ich okrelenia, jednak my nadal musimy wiedzie, ile argumentw mamy przekaza i na ile zwrconych wartoci bdziemy oczekiwa. Ogromna rnica tkwi w introspekcji. Jak zobaczylimy w Rozdziale 4, Python pozwala Tobie odkrywa moduy i funkcje podczas wykonywania programu. Moemy wypisa wszystkie funkcje dostpne w danym module, a take gdy troch popracujemy dokopa si do deklaracji i argumentw pojedynczych funkcji. WSDL pozwala robi to samo z serwisami internetowymi SOAP. W jzyku angielskim WSDL jest skrtem od Web Services Description Language. Mimo e zosta elastycznie napisany, aby opisywa wiele rodzajw rnych serwisw sieciowych, jest czsto wykorzystywany do opisywania serwisw SOAP. Plik WSDL jest wanie... plikiem. A dokadniej, jest plikiem XML. Zazwyczaj znajduje si na tym samym serwerze, ktry wykorzystujemy do uycia serwisu SOAP. Pniej w tym rozdziale, pobierzemy plik opisujcy API Google i wykorzystamy go lokalnie. Nie oznacza to, e bdziemy wywoywa Google lokalnie, poniewa plik WSDL nadal bdzie opisywa zewntrzne funkcje rezydujce gdzie na serwerze Google. Plik WSDL przechowuje opis wszystkiego, co jest zwizane z wywoywaniem serwisu SOAP, czyli: URL serwisu i przestrze nazw typ serwisu sieciowego (prawdopodobnie wywoania funkcji s wykonywane za pomoc SOAP, jednak, jak byo powiedziane wczeniej, WSDL jest wystarczajco elastyczny, aby opisa ca gamm rnych serwisw) list dostpnych funkcji argumenty kadej funkcji typy danych kadego argumentu zwracane wartoci kadej funkcji i ich typy danych Innymi sowy, plik WSDL mwi o wszystkim, co potrzebujemy wiedzie, aby mc wywoywa serwisy SOAP.

274

ROZDZIA 13. SOAP

13.6

Introspekcja SOAP z uyciem WSDL

Introspekcja SOAP z uyciem WSDL


Podobnie jak wiele rzeczy w obszarze serwisw sieciowych, WSDL posiada burzliw histori, pen politycznych sporw i intryg. Jednak przeskoczymy ten wtek historyczny, poniewa moe wydawa si nudny. Istnieje take troch innych standardw, ktre peni podobn funkcj, jednak WDSL jest najbardziej popularny, zatem nauczmy si go uywa. Najbardziej fundamentaln rzecz, na ktr nam pozwala WSDL jest odkrywanie dostpnych metod oferowanych przez serwer SOAP. Przykad 12.8. Odkrywanie dostpnych metod >>> from SOAPpy import WSDL #(1) >>> wsdlFile = http://www.xmethods.net/sd/2001/TemperatureService.wsdl) >>> server = WSDL.Proxy(wsdlFile) #(2) >>> server.methods.keys() #(3) [ugetTemp] 1. SOAPpy zawiera parser WSDL. W czasie pisania tego podrozdziau, parser ten okrelany by jako modu na wczesnym etapie rozwoju, jednak nie byo problemw podczas testowania z parsowanymi plikami WSDL. 2. Aby skorzysta z pliku WSDL, ponownie korzystamy z klasy poredniczcej (ang. proxy), WSDL.Proxy, ktra przyjmuje pojedynczy argument: plik WSDL. Zauwamy, e w tym przypadku przekazalimy adres URL pliku WSDL, ktry jest przechowywany gdzie na zdalnym serwerze, ale klasa poredniczca rwnie dobrze sobie z nim radzi jak z lokaln kopi pliku WSDL. Podczas tworzenia porednika WSDL, plik WSDL zostanie pobrany i sparsowany, wic jeli wystpi jakie bdy w pliku WSDL (lub gdy bdziemy mieli problemy z sieci), bdziemy o tym wiedzie natychmiast. 3. Klasa poredniczca WSDL przechowuje dostpne funkcje w postaci pythonowego sownika, server.methods. Zatem, aby pobra list dostpnych metod, wystarczy wywoa metod keys nalec do sownika. OK, wic ju wiemy, e ten serwer SOAP oferuje jedn metod: getTemp. Jednak w jaki sposb j wywoa? Obiekt poredniczcy WSDL moe nam take o tym powiedzie. Przykad 12.9. Odkrywanie argumentw metody >>> callInfo = server.methods[getTemp] #(1) >>> callInfo.inparams #(2) [<SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AD0>] >>> callInfo.inparams[0].name #(3) uzipcode >>> callInfo.inparams[0].type #(4) (uhttp://www.w3.org/2001/XMLSchema, ustring) 1. Sownik server.methods jest wypeniony okrelon przez SOAPpy struktur nazwan CallInfo. Obiekt CallInfo zawiera informacje na temat jednej okrelonej funkcji, wczajc w to argumenty funkcji.

13.6. INTROSPEKCJA SOAP Z UYCIEM WSDL

275

2. Argumenty funkcji s przechowywane w callInfo.inparams, ktra jest pythonow list obiektw ParameterInfo, ktre z kolei zawieraj informacje na temat kadego parametru. 3. Kady obiekt ParameterInfo przechowuje atrybut name, ktry jest nazw argumentu. Nie trzeba zna nazwy argumentu, aby wywoa funkcje poprzez SOAP, jednak SOAP obsuguje argumenty nazwane w wywoaniach funkcji (podobnie jak Python), a za pomoc WSDL.Proxy bdziemy mogli poprawnie obsugiwa nazywane argumenty, ktre zostaj przekazywane do zewntrznej funkcji (oczywicie, jeli to wczymy). 4. Ponadto kady parametr ma wyranie okrelony typ, a korzysta tu z typw zdeniowanych w XML Schema. Widzielimy to ju wczeniej; przestrze nazw XML Schema bya czci formularza umowy, jednak to zignorowalimy i nadal moemy to ignorowa, poniewa tutaj do niczego nie jest nam to potrzebne. Parametr zipcode jest acuchem znakw i jeli przekaemy pythonowy acuch znakw do obiektu WSDL.Proxy, zostanie on poprawnie zmapowany i wysany na serwer. WSDL take nas informuje o zwracanych przez funkcj wartociach. Przykad 12.10. Odkrywanie zwracanych wartoci metody >>> callInfo.outparams #(1) [<SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AF8>] >>> callInfo.outparams[0].name #(2) ureturn >>> callInfo.outparams[0].type (uhttp://www.w3.org/2001/XMLSchema, ufloat) 1. Uzupenieniem do argumentw funkcji callInfo.inparams jest callInfo.outparams, ktry odnosi si do zwracanej wartoci. Jest to take lista, poniewa funkcje wywoywane poprzez SOAP mog zwraca wiele wartoci, podobnie zreszt jak funkcje Pythona. 2. Kady obiekt ParameterInfo zawiera atrybuty name i type. Funkcja ta zwraca pojedyncz wartoci nazwan return, ktra jest liczb zmiennoprzecinkow (czyli float).. Teraz poczmy zdobyt wiedz i wywoajmy serwis sieciowy SOAP poprzez porednika WSDL. Przykad 12.11. Wywoywanie usugi sieciowej poprzez WSDL.Proxy >>> from SOAPpy import WSDL >>> wsdlFile = http://www.xmethods.net/sd/2001/TemperatureService.wsdl) >>> server = WSDL.Proxy(wsdlFile) #(1) >>> server.getTemp(90210) #(2) 66.0 >>> server.soapproxy.config.dumpSOAPOut = 1 #(3) >>> server.soapproxy.config.dumpSOAPIn = 1 >>> temperature = server.getTemp(90210) *** Outgoing SOAP ****************************************************** <?xml version="1.0" encoding="UTF-8"?>

276

ROZDZIA 13. SOAP

<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/1999/XMLSchema"> <SOAP-ENV:Body> <ns1:getTemp xmlns:ns1="urn:xmethods-Temperature" SOAP-ENC:root="1"> <v1 xsi:type="xsd:string">90210</v1> </ns1:getTemp> </SOAP-ENV:Body> </SOAP-ENV:Envelope> ************************************************************************ *** Incoming SOAP ****************************************************** <?xml version=1.0 encoding=UTF-8?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SOAP-ENV:Body> <ns1:getTempResponse xmlns:ns1="urn:xmethods-Temperature" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <return xsi:type="xsd:float">66.0</return> </ns1:getTempResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> ************************************************************************ >>> temperature 66.0 1. Widzimy, e konguracja jest prostsza ni wywoywanie serwisu SOAP bezporednio, poniewa plik WSDL zawiera informacj zarwno o URL serwisu, jak i o przestrzeni nazw, ktr potrzebujemy, aby wywoa serwis. Tworzony obiekt WSDL.Proxy pobiera plik WSDL, parsuje go i konguruje obiekt SOAPProxy, ktry bdzie wykorzystywa do wywoywania konkretnego serwisu SOAP. 2. Po utworzeniu obiektu WSDL.Proxy, moemy wywoywa funkcje rwnie prosto jak za pomoc obiektu SOAPProxy. Nie jest to zaskakujce; WSDL.Proxy jest wanie otoczk (ang. wrapper ) dla SOAPProxy z kilkoma dodanymi metodami, a wic skadnia wywoywania funkcji jest taka sama. 3. Moemy dosta si do obiektu SOAPProxy w WSDL.Proxy za pomoc server.soapproxy. Ta opcja jest przydatna, aby wczy debugowanie, dlatego te kiedy wywoujemy funkcj poprzez porednika WSDL, jego SOAPProxy bdzie pokazywa przychodzce i wychodzce przez cze dokumenty XML.

13.7. WYSZUKIWANIE W GOOGLE

277

13.7

Wyszukiwanie w Google

Wyszukiwanie w Google
Powrmy wreszcie do przykadu zamieszczonego na pocztku rozdziau, ktry robi co bardziej uytecznego i interesujcego ni mierzenie obecnej temperatury. Google dostarcza API SOAP dla korzystania z wynikw wyszukiwania wewntrz programw. By mc z niego korzysta musisz zarejestrowa konto w Google Web Services. Procedura 12.4. Zakadanie konta w Google Web Services 1. Wejd na stron http://www.google.com/apis/ i stwrz konto Google. Potrzebny jest Ci do tego tylko adres email. Po rejestracji poczt elektroniczn dostaniesz swj klucz licencyjny Google API. Bdziesz z niego korzysta przy kadym wywoaniu funkcji wyszukiwarki Google. 2. Rwnie ze strony http://www.google.com/apis/ pobierz zestaw dewelopera Google Web API. Zawiera on przykadowy kod w kilku jzykach programowania (ale nie w Pythonie) i, co istotniejsze, plik WSDL. 3. Rozpakuj tene zestaw i odnajd w nim plik GoogleSearch.wsdl. Skopiuj go w bezpieczne miejsce na swoim dysku. Przyda si w dalszej czci rozdziau. Gdy bdziesz ju mie klucz dewelopera i plik WSDL Google w jakim pewnym miejscu moesz zacz zabaw z Google Web Services. Przykad 12.12. Wgld w gb Google Web Services >>> from SOAPpy import WSDL >>> server = WSDL.Proxy(/path/to/your/GoogleSearch.wsdl) >>> server.methods.keys() [udoGoogleSearch, udoGetCachedPage, udoSpellingSuggestion] >>> callInfo = server.methods[doGoogleSearch] >>> for arg in callInfo.inparams: ... print arg.name.ljust(15), arg.type key (uhttp://www.w3.org/2001/XMLSchema, ustring) q (uhttp://www.w3.org/2001/XMLSchema, ustring) start (uhttp://www.w3.org/2001/XMLSchema, uint) maxResults (uhttp://www.w3.org/2001/XMLSchema, uint) filter (uhttp://www.w3.org/2001/XMLSchema, uboolean) restrict (uhttp://www.w3.org/2001/XMLSchema, ustring) safeSearch (uhttp://www.w3.org/2001/XMLSchema, uboolean) lr (uhttp://www.w3.org/2001/XMLSchema, ustring) ie (uhttp://www.w3.org/2001/XMLSchema, ustring) oe (uhttp://www.w3.org/2001/XMLSchema, ustring) #(1) #(2)

#(3)

1. Rozpoczcie korzystania z Google Web Services jest proste: utwrz obiekt WSDL.Proxy i wska mu miejsce, gdzie znajduje si Twoja lokalna kopia pliku WSDL Google. 2. Wedle zawartoci pliku WSDL, Google udostpnia trzy funkcje: doGoogleSearch, doGetCachedPage i doSpellingSuggestion. Robi dokadnie to, co sugeruj ich nazwy. Pierwsza z nich wykonuje wyszukiwanie i zwraca jego wyniki, druga daje dostp do kopii strony na serwerach Google (z okresu, kiedy bya ostatnio odwiedzona przez googlebota), a trzecia sugeruje popraw bdw literowych we wpisywanych hasach.

278

ROZDZIA 13. SOAP

3. Funkcja doGoogleSearch ma kilka parametrw rnego typu. Zauwa, e o ile z zawartoci pliku WSDL mona wywnioskowa rodzaj i typ argumentw, o tyle niemoliwe jest stwierdzenie jak je wykorzysta. Teoretycznie mogyby by take okrelone przedziay, do ktrych musz nalee argumenty, jednak plik WSDL Google nie jest tak szczegowy. WSDL.Proxy nie czyni cudw moe dostarczy Ci tylko informacji zawartych w pliku WSDL. Poniej znajduje si zestawienie parametrw funkcji doGoogleSearch: key Twj klucz licencyjny otrzymany po rejestracji konta Google Web Services. q Sowo lub wyraenie, ktrego szukasz. Skadnia jest dokadnie taka sama jak formularza wyszukiwania na stronie www Google, wic zadziaaj tutaj wszelkie znane Ci sztuczki lub zaawansowana skadnia wyszukiwarki. start Indeks wyniku wyszukiwania, od ktrego bd liczone zwrcone wyniki. Podobnie do wersji interaktywnej wyszukiwarki, funkcja ta zwraca 10 wynikw na raz. Chcc uzyska drug stron wynikw wyszukiwania podajemy tutaj 10. maxResults Liczba wynikw do zwrcenia. Ograniczona z gry do 10, aczkolwiek, gdy interesuje ci tylko kilka wynikw, w celu oszczdzenia transferu mona poda warto mniejsz. filter Podana warto True spowoduje, i Google odltruje duplikaty stron z wynikw wyszukiwania. restrict Ustawienie countryXX, gdzie XX to kod pastwa spowoduje wywietlenie wynikw tylko dla danego pastwa, np. countryUK spowoduje wyszukiwanie tylko dla Zjednoczonego Krlestwa. Dopuszczalnymi wartociami s te linux, mac i bsd, ktre spowoduj wyszukiwanie w zdeniowanych przez Google zbiorach stron o tematyce technicznej, lub unclesam, ktre spowoduje wyszukiwanie w materiaach dotyczcych rzdu i administracji Stanw Zjednoczonych. safeSearch Dla wartoci True Google odltruje z wynikw strony pornograczne. lr (ang. language restrict ograniczenie jzykowe) Ustawienie konkretnego kodu jzyka spowoduje wywietlenie tylko stron w podanym jzyku. ie and oe (ang. input encoding kodowanie wejciowe, ang. output encoding kodowanie wyjciowe) Parametry przestarzae. Oba musz przyj warto utf-8. Przykad 12.13. Wyszukiwanie w Google >>> >>> >>> >>> ... >>> 10 from SOAPpy import WSDL server = WSDL.Proxy(/path/to/your/GoogleSearch.wsdl) key = YOUR_GOOGLE_API_KEY results = server.doGoogleSearch(key, mark, 0, 10, False, "", False, "", "utf-8", "utf-8") len(results.resultElements)

#(1) #(2)

13.7. WYSZUKIWANIE W GOOGLE >>> results.resultElements[0].URL http://diveintomark.org/ >>> results.resultElements[0].title dive into <b>mark</b>

279 #(3)

1. Po przygotowaniu obiektu WSDL.Proxy moemy wywoa server.doGoogleSearch z wszystkimi dziesicioma parametrami. Pamitaj o korzystaniu z wasnego klucza licencyjnego Google API otrzymanego podczas rejestracji w Google Web Services. 2. Funkcja zwraca mnstwo informacji, ale wpierw spjrzmy wanie na wyniki wyszukiwania. S przechowywane w results.resultElements, a dosta si do nich moemy tak jak do elementw zwykej pythonowej listy. 3. Kady ze skadnikw resultElements jest obiektem zawierajcym adres URL (URL), tytu (title), urywek tekstu strony (snippet) oraz inne uyteczne atrybuty. W tym momencie moesz ju korzysta z normalnych technik introspekcji Pythona do podejrzenia zawartoci tego obiektu (np. dir(results.resultElements[0])). Moesz take t zawarto podejrze przy pomocy obiektu WSDL proxy i atrybutu outparams samej funkcji. Obie techniki daj ten sam rezultat. Obiekt wynikowy zawiera wicej ni tylko wyniki wyszukiwania. Na przykad: informacje na temat procesu szukania (ile trwa, ile wynikw znaleziono pomimo tego, e zwrcono tylko 10). Interfejs www wyszukiwarki pokazuje te informacje, wic s te dostpne metodami programistycznymi. Przykad 12.14. Pobieranie z Google informacji pomocniczych >>> results.searchTime #(1) 0.224919 >>> results.estimatedTotalResultsCount #(2) 29800000 >>> results.directoryCategories #(3) [<SOAPpy.Types.structType item at 14367400>: {fullViewableName: Top/Arts/Literature/World_Literature/American/19th_Century/Twain,_Mark, specialEncoding: }] >>> results.directoryCategories[0].fullViewableName Top/Arts/Literature/World_Literature/American/19th_Century/Twain,_Mark 1. To wyszukiwanie zajo 0.224919 sekund. Wynik ten nie uwzgldnia czasu powiconego na przesy oraz odbir dokumentw XML protokou SOAP. Jest to wycznie czas powicony przez silnik Google na przetworzenie zapytania, ju po otrzymaniu go. 2. Znaleziono okoo 30 milionw pasujcych stron. Dostp do kolejnych dziesitek z tego zbioru uzyskamy za pomoc zmiany argumentu start metody server.doGoogleSearch i kolejnych jej wywoa. 3. Dla niektrych zapyta Google zwraca take list powizanych kategorii z katalogu Google. Doczajc zwrcone w ten sposb URL-e do przedrostka http://directory.google.com/ uzyskamy adresy odpowiednich stron katalogu.

280

ROZDZIA 13. SOAP

13.8

Rozwizywanie problemw

Rozwizywanie problemw
Oczywicie wiat serwisw SOAP to nie jest tylko kraina mlekiem i miodem pynca. Czasami co idzie nie tak. Jak ju widziae w tym rozdziale na SOAP skada si kilka warstw. Jest tam warstwa HTTP, poniewa SOAP wysya dokumenty XML do i odbiera te dokumenty od serwera HTTP. A wic wszystkie techniki dotyczce debugowania, ktrych nauczye si w rozdziale 11 HTTP, maj zastosowanie take tutaj. Moesz zaimportowa httplib i ustawi httplib.HTTPConnection.debuglevel = 1, aby zobaczy cay ruch odbywajcy si poprzez HTTP. Poza t warstw HTTP jest wiele rzeczy, ktr mog sie nie powie. SOAPpy wykonuje godn podziwu robot ukrywajc przed Tob skadni SOAP, ale to oznacza take, e moe by trudne zdiagnozowanie problemu, gdy takowy si pojawi. Oto kilka przykadw pomyek, ktre robiem uywajc serwisw SOAP i komunikaty bdw jakie one spowodoway. Przykad 12.15. Wywoywanie metod z niewaciwie skongurowanym Proxy >>> from SOAPpy import SOAPProxy >>> url = http://services.xmethods.net:80/soap/servlet/rpcrouter >>> server = SOAPProxy(url) #(1) >>> server.getTemp(27502) #(2) <Fault SOAP-ENV:Server.BadTargetObjectURI: Unable to determine object id from call: is the method element namespaced?> Traceback (most recent call last): File "<stdin>", line 1, in ? File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 453, in __call__ return self.__r_call(*args, **kw) File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 475, in __r_call self.__hd, self.__ma) File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 389, in __call raise p SOAPpy.Types.faultType: <Fault SOAP-ENV:Server.BadTargetObjectURI: Unable to determine object id from call: is the method element namespaced?> 1. Zauwaye pomyk? Tworzymy rcznie SOAPProxy i prawidowo podajemy URL serwisu, ale nie podalimy przestrzeni nazw. Poniewa wiele serwisw moe dziaa na tym samym URL-u, przestrze nazw jest bardzo istotna, aby ustali do ktrego serwisu prbujemy si odwoa, a nastpnie jak metod waciwie wywoujemy. 2. Serwer odpowiada poprzez wysanie SOAP Fault, ktry SOAPpy zamienia na pythonowy wyjtek typu SOAPpy.Types.faultType. Wszystkie bdy zwracane przez serwer SOAP zawsze bd obiektami SOAP Fault, a wic atwo moemy te wyjtki przechwyci. W tym przypadku, ta czytelna dla czowieka cz SOAP Fault daje wskazwk do tego jaki jest problem: element metoda nie jest zawarty w przestrzeni nazw, poniewa oryginalny obiekt SOAPProxy nie zosta skongurowany z przestrzeni nazw serwisu.

13.8. ROZWIZYWANIE PROBLEMW

281

Bdna konguracja podstawowych elementw serwisu SOAP jest jednym z problemw, ktre ma za zadanie rozwiza WSDL. Plik WSDL zawiera URL serwisu i przestrze nazw, a wic nie mona ich poda bdnie. Oczywicie nadal s inne rzeczy, ktre mog zosta podane bdnie. Przykad 12.16. Wywoanie metody z nieprawidowymi argumentami >>> wsdlFile = http://www.xmethods.net/sd/2001/TemperatureService.wsdl >>> server = WSDL.Proxy(wsdlFile) >>> temperature = server.getTemp(27502) #(1) <Fault SOAP-ENV:Server: Exception while handling service request: services.temperature.TempService.getTemp(int) -- no signature match> #(2) Traceback (most recent call last): File "<stdin>", line 1, in ? File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 453, in __call__ return self.__r_call(*args, **kw) File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 475, in __r_call self.__hd, self.__ma) File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 389, in __call raise p SOAPpy.Types.faultType: <Fault SOAP-ENV:Server: Exception while handling service request: services.temperature.TempService.getTemp(int) -- no signature match> 1. Zauwaye pomyk? To jest subtelna pomyka: wywoujemy server.getTemp z liczb cakowit (ang. integer) zamiast z acuchem znakw (ang. string). Jak ju widziae w pliku WSDL, funkcja getTemp() SOAP przyjmuje pojedyczy argument, kod pocztowy, ktry musi by acuchem znakw. WSDL.Proxy nie bdzie konwertowa typw danych; musimy poda dokadnie te typy danych jakich serwer oczekuje. 2. I znowu, serwer zwraca SOAP Fault i czytelna dla czowieka cz komunikatu bdu daje wskazwk do tego, gdzie ley problem: wywoujemy funkcj getTemp z liczb cakowit, ale nie ma zdeniowanej funkcji o tej nazwie, ktra przyjmowaaby liczb cakowit. W teorii SOAP pozwala na przecianie funkcji, a wic jeden serwis SOAP mgby posiada dwie funkcje o tej samej nazwie i z tak sam liczb argumentw, ale z argumentami o rnych typach. O to dlaczego tak wane jest podawanie waciwych typw. i dlaczego WSDL.Proxy nie konwertuje typw danych. Gdyby to robi, to mogoby si zdarzy, e wywoalibymy zupenie inn funkcj! Powodzenia w debugowaniu takiego bdu. Duo atwiej jest by krytycznym wobec typw danych i zgasza bdy tak szybko jak to tylko moliwe. Jest take moliwe napisanie kodu Pythona, ktry oczekuje innej liczby zwracanych wartoci, ni zdalna funkcja waciwie zwraca. Przykd 12.17. Wywoanie metody i oczekiwanie niewaciwej liczby zwracanych wartoci >>> wsdlFile = http://www.xmethods.net/sd/2001/TemperatureService.wsdl >>> server = WSDL.Proxy(wsdlFile) >>> (city, temperature) = server.getTemp(27502) #(1) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: unpack non-sequence

282

ROZDZIA 13. SOAP

1. Zauwaye pomyk? server.getTemp zwraca tylko jedn warto, liczb zmiennoprzecinkow (ang. oat), ale my napisalimy kod, ktry zakada, e otrzymamy dwie wartoci i prbuje je przypisa do dwch oddzielnych zmiennych. Zauwa, e tutaj nie pojawi si wyjtek SOAP fault. Co do zdalnego serwera, to wszystko odbyo si jak naley. Bd pojawi si dopiero po zakoczeniu transakcji SOAP, WSDL.Proxy zwrci liczb zmiennoprzecinkow a nasz lokalny interpreter Pythona prbowa zgodnie z naszym zaleceniem podzieli j pomidzy dwie zmienne. Poniewa funkcja zwrcia tylko jedn warto, zosta zgoszony wyjtek Pythona, a nie SOAP Fault. A co z serwisem Google? Najczstszym problemem jaki z nim miaem byo to, e zapominaem waciwie ustawi klucz aplikacji. Przykad 12.18. wywoanie metody z bdem specycznym dla aplikacji >>> from SOAPpy import WSDL >>> server = WSDL.Proxy(r/path/to/local/GoogleSearch.wsdl) >>> results = server.doGoogleSearch(foo, mark, 0, 10, False, "", #(1) ... False, "", "utf-8", "utf-8") <Fault SOAP-ENV:Server: #(2) Exception from service object: Invalid authorization key: foo: <SOAPpy.Types.structType detail at 14164616>: {stackTrace: com.google.soap.search.GoogleSearchFault: Invalid authorization key: foo at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe( QueryLimits.java:220) at com.google.soap.search.QueryLimits.validateKey(QueryLimits.java:127) at com.google.soap.search.GoogleSearchService.doPublicMethodChecks( GoogleSearchService.java:825) at com.google.soap.search.GoogleSearchService.doGoogleSearch( GoogleSearchService.java:121) at sun.reflect.GeneratedMethodAccessor13.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.soap.server.RPCRouter.invoke(RPCRouter.java:146) at org.apache.soap.providers.RPCJavaProvider.invoke( RPCJavaProvider.java:129) at org.apache.soap.server.http.RPCRouterServlet.doPost( RPCRouterServlet.java:288) at javax.servlet.http.HttpServlet.service(HttpServlet.java:760) at javax.servlet.http.HttpServlet.service(HttpServlet.java:853) at com.google.gse.HttpConnection.runServlet(HttpConnection.java:237) at com.google.gse.HttpConnection.run(HttpConnection.java:195) at com.google.gse.DispatchQueue$WorkerThread.run(DispatchQueue.java:201) Caused by: com.google.soap.search.UserKeyInvalidException: Key was of wrong size. at com.google.soap.search.UserKey.<init>(UserKey.java:59) at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe( QueryLimits.java:217) ... 14 more }> Traceback (most recent call last):

13.8. ROZWIZYWANIE PROBLEMW

283

File "<stdin>", line 1, in ? File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 453, in __call__ return self.__r_call(*args, **kw) File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 475, in __r_call self.__hd, self.__ma) File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 389, in __call raise p SOAPpy.Types.faultType: <Fault SOAP-ENV:Server: Exception from service object: Invalid authorization key: foo: <SOAPpy.Types.structType detail at 14164616>: {stackTrace: com.google.soap.search.GoogleSearchFault: Invalid authorization key: foo at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe( QueryLimits.java:220) at com.google.soap.search.QueryLimits.validateKey(QueryLimits.java:127) at com.google.soap.search.GoogleSearchService.doPublicMethodChecks( GoogleSearchService.java:825) at com.google.soap.search.GoogleSearchService.doGoogleSearch( GoogleSearchService.java:121) at sun.reflect.GeneratedMethodAccessor13.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.soap.server.RPCRouter.invoke(RPCRouter.java:146) at org.apache.soap.providers.RPCJavaProvider.invoke( RPCJavaProvider.java:129) at org.apache.soap.server.http.RPCRouterServlet.doPost( RPCRouterServlet.java:288) at javax.servlet.http.HttpServlet.service(HttpServlet.java:760) at javax.servlet.http.HttpServlet.service(HttpServlet.java:853) at com.google.gse.HttpConnection.runServlet(HttpConnection.java:237) at com.google.gse.HttpConnection.run(HttpConnection.java:195) at com.google.gse.DispatchQueue$WorkerThread.run(DispatchQueue.java:201) Caused by: com.google.soap.search.UserKeyInvalidException: Key was of wrong size. at com.google.soap.search.UserKey.<init>(UserKey.java:59) at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe( QueryLimits.java:217) ... 14 more }> 1. Zauwaye pomyk? Nie ma bdw w samej skadni wywoania lub w liczbie argumentw lub w typach danych. Problem jest specyczny dla tej konkretnej aplikacji: pierwszym argumentem powinien by nasz klucz aplikacji, ale foo nie jest prawidowym kluczem dla Google. 2. Serwer Google odpowiada poprzez SOAP Fault i niesamowicie dugi komunikat bdu, ktry zawiera kompletny zrzut stosu Javy. Zapamitaj, e wszystkie bdy SOAP s oznaczane poprzez SOAP Faults: bdy w konguracjach, bdy w argumentach funkcji i bdy specyczne dla aplikacji jak ten. Zakopana gdzie tam jest kluczowa informacja: Invalid authorization key: foo (niewaciwy klucz autoryzacji).

284

ROZDZIA 13. SOAP

13.9

SOAP - podsumowanie

Podsumowanie
Serwisy internetowe SOAP s bardzo skomplikowane. Specykacja jest bardzo ambitna i prbuje sprosta wielu rnym przypadkom uycia dla serwisw internetowych. Ten rozdzia dotkn jednego z prostszych przypadkw uycia. Zanim zanurkujemy do nastpnego rozdziau, upewnij si, e opanowae nastpujce kwestie: Poczenie si z serwerem SOAP i wywoanie zdalnych metod Zaadowanie pliku WSDL i uycie go do introspekcji zdalnych metod Debugowanie wywoa SOAP ze ledzeniem komunikacji sieciowej Rozwizywanie problemw z najczstszymi bdami dotyczcymi SOAP

Rozdzia 14

Testowanie jednostkowe

285

286

ROZDZIA 14. TESTOWANIE JEDNOSTKOWE

14.1

Wprowadzenie do liczb rzymskich

Wprowadzenie do liczb rzymskich


W poprzednich rozdziaach nurkowalimy poprzez bezporednie przygldanie si kodowi, aby zrozumie go tak szybko, jak to moliwe. Teraz, gdy ju troch obeznalimy Pythona, troch si cofniemy i spojrzymy na kroki, ktre trzeba wykona przed napisaniem kodu. Kilka rozdziaw wczeniej pisalimy, debugowalimy i optymalizowalimy zbir uytecznych funkcji, ktre suyy do konwersji z i na liczby rzymskie. W Podrozdziale 7.3, Analiza przypadku: Liczby rzymskie, opisalimy mechanizm konstruowania i sprawdzania poprawnoci liczb w zapisie rzymskim, lecz teraz cofnijmy si troch i zastanwmy si, co moglibymy uwzgldni, aby rozszerzy to narzdzie, by w dwch kierunkach. Zasady tworzenia liczb rzymskich prowadz do kilku interesujcych obserwacji: 1. Istnieje tylko jeden poprawny sposb reprezentowania pewnej liczby w postaci rzymskiej. 2. Odwrotno te jest prawd: jeli cig znakw jest poprawn liczb rzymsk, to reprezentuje ona tylko jedn liczb (tzn. moemy j przeczyta tylko w jeden sposb). 3. Tylko ograniczony zakres liczb moe by zapisany jako liczby rzymskie, a dokadniej liczby od 1 do 3999 (Liczby rzymskiej posiadaj kilka sposobw wyraania wikszych liczb np. poprzez dodanie nadkrelenia nad cyframi rzymskimi, co oznacza, e normaln warto tej liczby trzeba pomnoy przez 1000, jednak nie bdziemy si wdawa w szczegy. Dla potrzeb tego rozdziau, zaoymy, e liczby rzymskie id od 1 do 3999). 4. Nie mamy moliwo zapisania 0 jako liczby rzymskiej. (Co ciekawe, staroytni Rzymianie nie wyobraali sobie 0 jako liczby. Za pomoc liczb liczymy, ile czego mamy, jednak jak moemy policzy co, czego nie mamy?) 5. Nie moemy w postaci liczby rzymskiej zapisa liczby ujemnej. 6. W postaci liczby rzymskiej nie moemy zapisywa uamkw, czy liczb, ktre nie s cakowite. Biorc to wszystko pod uwag, co moemy oczekiwa od zbioru funkcji, ktre konwertuj z i na liczby rzymskie? Wymagania roman.py: 1. toRoman powinien zwraca rzymsk reprezentacj wszystkich liczb cakowitych z zakresu od 1 do 3999. 2. toRoman powinien nie zadziaa (ang. fail ), gdy otrzyma liczb cakowit z poza przedziau od 1 do 3999. 3. toRoman powinien nie zadziaa, gdy otrzyma niecakowit liczb. 4. fromRoman powinien przyjmowa poprawn liczb rzymsk i zwrci liczb, ktra j reprezentuje. 5. fromRoman powinien nie zadziaa, kiedy otrzyma niepoprawn liczb rzymsk.

14.1. WPROWADZENIE DO LICZB RZYMSKICH

287

6. Kiedy dan liczb konwertujemy na liczb rzymsk, a nastpnie z powrotem na liczb, powinnimy otrzyma t sam liczb, z ktr zaczynalimy. Wic dla kadego n od 1 do 3999 fromRoman(toRoman(n)) == n. 7. toRoman powinien zawsze zwrci liczb rzymsk korzystajc z wielkich liter. 8. fromRoman powinien akceptowa jedynie liczby rzymskie skadajce si z wielkich liter (tzn. powinien nie zadziaa, gdy otrzyma wejcie zoone z maych liter).

288

ROZDZIA 14. TESTOWANIE JEDNOSTKOWE

14.2

Testowanie - nurkujemy

Nurkujemy
Teraz, kiedy w peni zdeniowalimy zachowanie funkcji konwertujcych, zrobimy co odrobin niespodziewanego: napiszemy zestaw testw, ktry pokae, co te funkcje potra, a take upewni nas, e robi dokadnie to, co chcemy. Dobrze usyszelicie: zaczniemy od napisania kodu testujcego kod, ktry nie zosta jeszcze napisany. Takie podejcie nazywa si testowaniem jednostkowym, poniewa zestaw dwch funkcji konwertujcych moe by napisany i przetestowany jako jednostka, niezalenie od kodu wikszego programu, jakiego czci moe si w zestaw sta w przyszoci. Python posiada gotowe narzdzie suce do testowania jednostkowego modu o nazwie unittest. Modu unittest jest czci dystrybucji jzyka Python od wersji 2.1 wzwy. Uytkownicy starszych wersji (np. Python 2.0) mog pobra ten modu ze strony pyunit.sourceforge.net. Testowanie jednostkowe jest wanym elementem strategii rozwoju oprogramowania, w ktrej na pierwszym miejscu stawia si testowanie. Jeli ma by napisany jaki test, to wane jest, aby by on napisany jak najwczeniej (moliwie przed napisaniem testowanego kodu) oraz aby by aktualizowany wraz ze zmieniajcymi si wymaganiami. Testowanie jednostkowe nie zastpuje testowania wyszego poziomu, takiego jak testowanie funkcjonalne czy systemowe, ale jest bardzo istotne we wszystkich fazach rozwoju oprogramowania: 1. Jeszcze przed napisaniem kodu zmusza nas do sprecyzowania i wyraenia wymaga w uyteczny sposb. 2. Podczas pisania kodu chroni nas od niepotrzebnego kodowania. Kiedy wszystkie testy przechodz, testowana funkcja jest ju gotowa. 3. Podczas refaktoryzacji upewnia nas, e nowa wersja kodu zachowuje si tak samo, jak stara. 4. W procesie utrzymania kodu ochrania nas, kiedy kto przychodzi do nas z krzykiem, e nasza ostatnia zmiana popsua jego stary kod (Ale prosz pana, w momencie mojego czekinowania wszystkie testy przechodziy...). 5. Podczas programowania w zespole zwiksza pewno, e nowy kod, ktry chcemy doda, nie popsuje kodu innych osb, poniewa najpierw uruchomimy ich testy (Widziaem to ju podczas tzw. sprintw. Zesp dzieli zadanie midzy siebie, kady otrzymuje specykacj tego, nad czym bdzie pracowa, pisze do tego testy jednostkowe, a nastpnie dzieli si tymi testami z pozostaymi czonkami zespou. W ten sposb nikt nie posunie si zbyt daleko w rozwijaniu kodu, ktry nie wspdziaa z kodem innych osb).

14.3. WPROWADZENIE DO ROMANTEST.PY

289

14.3

Wprowadzenie do romantest.py

Wprowadzenie do romantest.py
Poniej przedstawiono peny zestaw testw do funkcji konwertujcych, ktre nie zostay jeszcze napisane, ale wkrtce znajd si w roman.py. Nie jest wcale oczywiste, jak to wszystko ze sob dziaa; adna z poniszych klas i metod nie odnosi si do adnej innej. Jak wkrtce zobaczymy, ma to swoje uzasadnienie. Przykad 13.1. romantest.py Jeli jeszcze tego nie zrobie, moesz pobra ten oraz inne przykady (http:// diveintopython.org/download/diveintopython-examples-5.4.zip) uywane w tej ksice. """Unit test for roman.py""" import roman import unittest class KnownValues(unittest.TestCase): knownValues = ( (1, I), (2, II), (3, III), (4, IV), (5, V), (6, VI), (7, VII), (8, VIII), (9, IX), (10, X), (50, L), (100, C), (500, D), (1000, M), (31, XXXI), (148, CXLVIII), (294, CCXCIV), (312, CCCXII), (421, CDXXI), (528, DXXVIII), (621, DCXXI), (782, DCCLXXXII), (870, DCCCLXX), (941, CMXLI), (1043, MXLIII), (1110, MCX), (1226, MCCXXVI), (1301, MCCCI), (1485, MCDLXXXV), (1509, MDIX), (1607, MDCVII),

290 (1754, (1832, (1993, (2074, (2152, (2212, (2343, (2499, (2574, (2646, (2723, (2892, (2975, (3051, (3185, (3250, (3313, (3408, (3501, (3610, (3743, (3844, (3888, (3940, (3999,

ROZDZIA 14. TESTOWANIE JEDNOSTKOWE MDCCLIV), MDCCCXXXII), MCMXCIII), MMLXXIV), MMCLII), MMCCXII), MMCCCXLIII), MMCDXCIX), MMDLXXIV), MMDCXLVI), MMDCCXXIII), MMDCCCXCII), MMCMLXXV), MMMLI), MMMCLXXXV), MMMCCL), MMMCCCXIII), MMMCDVIII), MMMDI), MMMDCX), MMMDCCXLIII), MMMDCCCXLIV), MMMDCCCLXXXVIII), MMMCMXL), MMMCMXCIX))

def testToRomanKnownValues(self): """toRoman should give known result with known input""" for integer, numeral in self.knownValues: result = roman.toRoman(integer) self.assertEqual(numeral, result) def testFromRomanKnownValues(self): """fromRoman should give known result with known input""" for integer, numeral in self.knownValues: result = roman.fromRoman(numeral) self.assertEqual(integer, result) class ToRomanBadInput(unittest.TestCase): def testTooLarge(self): """toRoman should fail with large input""" self.assertRaises(roman.OutOfRangeError, roman.toRoman, 4000) def testZero(self): """toRoman should fail with 0 input""" self.assertRaises(roman.OutOfRangeError, roman.toRoman, 0) def testNegative(self): """toRoman should fail with negative input""" self.assertRaises(roman.OutOfRangeError, roman.toRoman, -1)

14.3. WPROWADZENIE DO ROMANTEST.PY

291

def testNonInteger(self): """toRoman should fail with non-integer input""" self.assertRaises(roman.NotIntegerError, roman.toRoman, 0.5) class FromRomanBadInput(unittest.TestCase): def testTooManyRepeatedNumerals(self): """fromRoman should fail with too many repeated numerals""" for s in (MMMM, DD, CCCC, LL, XXXX, VV, IIII): self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s) def testRepeatedPairs(self): """fromRoman should fail with repeated pairs of numerals""" for s in (CMCM, CDCD, XCXC, XLXL, IXIX, IVIV): self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s) def testMalformedAntecedent(self): """fromRoman should fail with malformed antecedents""" for s in (IIMXCC, VX, DCM, CMM, IXIV, MCMC, XCX, IVI, LM, LD, LC): self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s) class SanityCheck(unittest.TestCase): def testSanity(self): """fromRoman(toRoman(n))==n for all n""" for integer in range(1, 4000): numeral = roman.toRoman(integer) result = roman.fromRoman(numeral) self.assertEqual(integer, result) class CaseCheck(unittest.TestCase): def testToRomanCase(self): """toRoman should always return uppercase""" for integer in range(1, 4000): numeral = roman.toRoman(integer) self.assertEqual(numeral, numeral.upper()) def testFromRomanCase(self): """fromRoman should only accept uppercase input""" for integer in range(1, 4000): numeral = roman.toRoman(integer) roman.fromRoman(numeral.upper()) self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, numeral.lower()) if __name__ == "__main__": unittest.main()

292 Materiay dodatkowe

ROZDZIA 14. TESTOWANIE JEDNOSTKOWE

Na stronie domowej PyUnit znajduje si szeroka dyskusja na temat uywania zrbu unittest, cznie z zagadnieniami zaawansowanymi, ktrych w tym rozdziale nie poruszono. Czsto Zadawane Pytania dotyczce PyUnit wyjaniaj, dlaczego przypadki testowe umieszczane s oddzielnie w stosunku do kodu, ktry testuj. Python Library Reference podsumowuje modu unittest ExtremeProgramming.org omawia przyczyny, dla ktrych naley pisa testy jednostkowe Na stronach The Portland Pattern Repository mona znale trwajc wci dyskusj na temat testw jednostkowych, standardow denicj testu jednostkowego, przyczyny pisania testw przed pisaniem kodu oraz wiele dogbnych analiz na ten temat.

14.4. TESTOWANIE POPRAWNYCH PRZYPADKW

293

14.4

Testowanie poprawnych przypadkw

Tworzenie poszczeglnych przypadkw testowych naley do najbardziej podstawowych elementw testowania jednostkowego. Przypadek testowy stanowi odpowied na pewne pytanie dotyczce kodu, ktry jest testowany. Przypadek testowy powinien: ...dziaa bez koniecznoci wprowadzania danych przez czowieka. Testowanie jednostkowe powinno by zautomatyzowane. ...samodzielnie stwierdza, czy testowana funkcja dziaa poprawnie, czy nie, bez koniecznoci interpretacji wynikw przez czowieka. ...dziaa w izolacji, oddzielnie i niezalenie od innych przypadkw testowych (nawet wwczas, gdy testuj one te same funkcje). Kady przypadek testowy powinien by wysp. Zbudujmy wic pierwszy przypadek testowy, biorc powysze pod uwag. Mamy nastpujce wymaganie:

1. Funkcja toRoman powinna zwraca tekstow reprezentacj w zapisie rzymskim wszystkich liczb cakowitych z przedziau od 1 do 3999. Przykad 13.2. testToRomanKnownValues class KnownValues(unittest.TestCase): knownValues = ( (1, I), (2, II), (3, III), (4, IV), (5, V), (6, VI), (7, VII), (8, VIII), (9, IX), (10, X), (50, L), (100, C), (500, D), (1000, M), (31, XXXI), (148, CXLVIII), (294, CCXCIV), (312, CCCXII), (421, CDXXI), (528, DXXVIII), (621, DCXXI), (782, DCCLXXXII), (870, DCCCLXX), (941, CMXLI), #(1)

294 (1043, (1110, (1226, (1301, (1485, (1509, (1607, (1754, (1832, (1993, (2074, (2152, (2212, (2343, (2499, (2574, (2646, (2723, (2892, (2975, (3051, (3185, (3250, (3313, (3408, (3501, (3610, (3743, (3844, (3888, (3940, (3999,

ROZDZIA 14. TESTOWANIE JEDNOSTKOWE MXLIII), MCX), MCCXXVI), MCCCI), MCDLXXXV), MDIX), MDCVII), MDCCLIV), MDCCCXXXII), MCMXCIII), MMLXXIV), MMCLII), MMCCXII), MMCCCXLIII), MMCDXCIX), MMDLXXIV), MMDCXLVI), MMDCCXXIII), MMDCCCXCII), MMCMLXXV), MMMLI), MMMCLXXXV), MMMCCL), MMMCCCXIII), MMMCDVIII), MMMDI), MMMDCX), MMMDCCXLIII), MMMDCCCXLIV), MMMDCCCLXXXVIII), MMMCMXL), MMMCMXCIX))

#(2) #(3)

def testToRomanKnownValues(self): """toRoman should give known result with known input""" for integer, numeral in self.knownValues: result = roman.toRoman(integer) self.assertEqual(numeral, result) 1. W celu utworzenia przypadku testowego tworzymy now podklas klasy TestCase z moduu unittest. Klasa TestCase udostpnia wiele uytecznych metod, ktre mona uy we wasnym przypadku testowym celem przetestowania okrelonych warunkw. 2. Jest to lista par liczba cakowita/warto w zapisie rzymskim, ktrych poprawno sprawdziem rcznie. Zawiera ona dziesi najniszych liczb, liczb najwiksz, kad liczb, ktra jest reprezentowana przy pomocy jednego znaku w zapisie rzymskim oraz pewne inne, losowo wybrane wartoci. Celem przypadku testowego nie jest przetestowanie wszystkich mogcych si pojawi danych wejciowych, lecz pewnej reprezentatywnej prbki.

#(4) #(5) #(6)

14.4. TESTOWANIE POPRAWNYCH PRZYPADKW

295

3. Kady pojedynczy test posiada swoj metod, ktra nie bierze adnych parametrw oraz nie zwraca adnej wartoci. Jeli metoda zakoczy si normalnie bez rzucenia wyjtku, uznaje si wwczas, e taki test przeszed; jeli z metody zostanie rzucony wyjtek, wwczas uznaje si, e test nie przeszed. 4. W tym miejscu woamy funkcj toRoman. (Rzeczywicie, funkcja ta nie zostaa jeszcze napisana, ale kiedy ju j napiszemy, ta wanie linijka spowoduje jej wywoanie). Zauwamy, e wanie zdeniowalimy API funkcji toRoman: pobiera ona argument typu int (liczb, ktra ma zosta przeksztacona na zapis rzymski) i zwraca warto typu string (rzymsk reprezentacj wartoci przekazanej w parametrze). Jeli rzeczywiste API bdzie inne, ten test zakoczy si niepowodzeniem. 5. Zauwamy rwnie, e podczas wywoywania toRoman nie apiemy adnych wyjtkw. Jest to celowe. Funkcja toRoman nie powinna zgasza wyjtkw w sytuacji, gdy wywoujemy j z prawidowymi wartociami, a wszystkie wartoci, z ktrymi j wywoujemy, s poprawne. Jeli toRoman rzuci wyjtek, test zakoczy si niepowodzeniem. 6. Jeli zaoymy, e funkcja toRoman zostaa poprawnie zdeniowana i wywoana oraz poprawnie si zakoczya, zwracajc pewn warto, to ostatni rzecz, jak musimy sprawdzi, jest to, czy zwrcona warto jest poprawna. Tego rodzaju sprawdzenie jest bardzo powszechne, a w klasie TestCase istnieje metoda assertEqual, ktra moe w tym pomc: sprawdza ona, czy dwie wartoci s sobie rwne. Jeli warto zwrcona przez funkcj toRoman (result) nie jest rwna znanej nam, spodziewanej wartoci (numeral), assertEqual spowoduje rzucenie wyjtku, a test zakoczy si niepowodzeniem. Jeli te dwie wartoci s rwne, metoda ta nic nie robi. Jeli kada warto zwrcona przez toRoman pasuje do wartoci, ktrej si spodziewamy, to assertEqual nigdy nie rzuci wyjtku, a wic testToRomanKnownValues zakoczy si normalnie, co oznacza, e funkcja toRoman przesza ten test.

296

ROZDZIA 14. TESTOWANIE JEDNOSTKOWE

14.5

Testowanie niepoprawnych przypadkw

Testowanie funkcji w sytuacji, w ktrej na wejciu pojawiaj si wycznie poprawne wartoci, nie jest wystarczajce; naley dodatkowo sprawdzi, e funkcja koczy si niepowodzeniem, gdy otrzymuje ona niepoprawne dane wejciowe. Nie moe to by jednak dowolne niepowodzenie; musi by ono dokadnie takie, jakiego si spodziewamy. Przypomnijmy sobie pozostae wymagania dotyczce funkcji toRoman: 2. Funkcja toRoman powinna koczy si niepowodzeniem, gdy przekazana jest jej warto spoza przedziau od 1 do 3999. 3. Funkcja toRoman powinna koczy si niepowodzeniem, gdy przekazana jest jej warto nie bdca liczb cakowit. W jzyku Python funkcje kocz si niepowodzeniem wwczas, gdy rzucaj wyjtki. W module unittest znajduj si natomiast metody, dziki ktrym mona wykry, czy funkcja, otrzymawszy niepoprawne dane wejciowe, rzuca odpowiedni wyjtek: Przykad 13.3. Testowanie niepoprawnych danych wejciowych do funkcji toRoman class ToRomanBadInput(unittest.TestCase): def testTooLarge(self): """toRoman should fail with large input""" self.assertRaises(roman.OutOfRangeError, roman.toRoman, 4000) def testZero(self): """toRoman should fail with 0 input""" self.assertRaises(roman.OutOfRangeError, roman.toRoman, 0) def testNegative(self): """toRoman should fail with negative input""" self.assertRaises(roman.OutOfRangeError, roman.toRoman, -1) def testNonInteger(self): """toRoman should fail with non-integer input""" self.assertRaises(roman.NotIntegerError, roman.toRoman, 0.5) 1. Klasa TestCase z moduu unittest udostpnia metod assertRaises, ktra przyjmuje nastpujce argumenty: wyjtek, ktrego si spodziewamy, funkcj, ktr testujemy oraz argumenty, ktre maj by przekazane do funkcji (jeli testowana funkcja przyjmuje wicej ni jeden argument, naley je wszystkie przekaza po kolei do funkcji assertRaises, ktra przekae je w tej wanie kolejnoci do testowanej funkcji). Zwrcie baczn uwag na to, co tutaj robimy: zamiast rcznego wywoywania funkcji i sprawdzania, czy zosta rzucony wyjtek odpowiedniego typu (poprzez otoczenie wywoania blokiem try...except), uywamy funkcji assertRaises, ktra robi to wszystko za nas. Wszystko, co naley zrobi, to przekaza typ wyjtku (roman.OutOfRangeError), funkcj (toRoman) oraz jej argument (4000), a assertRaises zajmie si wywoaniem toRoman z przekazanym parametrem oraz sprawdzeniem, czy rzucony wyjtek to rzeczywicie roman.OutOfRangeError. (Zauwamy rwnie, e do funkcji assertRaises przekazujemy funkcj toRoman jako parametr; nie wywoujemy jej ani nie przekazujemy jej nazwy w postaci napisu. Czy wspominaem ostatnio, jak bardzo

#(1)

#(2)

#(3)

14.5. TESTOWANIE NIEPOPRAWNYCH PRZYPADKW

297

przydatne jest to, e w jzyku Python wszystko jest obiektem, wczajc w to funkcje i wyjtki?) 2. Oprcz przetestowania wartoci zbyt duych naley te przetstowa wartoci zbyt mae. Pamitajmy, e w zapisie rzymskim nie mona wyrazi ani wartoci 0, ani liczb ujemnych, wic dla kadej z tych sytuacji mamy przypadek testowy (testZero i testNegative). W funkcji testZero sprawdzamy, czy toRoman rzuca wyjtek roman.OutOfRangeError, gdy jest wywoana z wartoci 0; jeli nie rzuci tego wyjtku (zarwno z powodu zwrcenia pewnej wartoci jak i rzucenia jakiego innego wyjtku), test powinien zakoczy si niepowodzeniem. 3. Wymaganie #3 okrela, e toRoman nie moe przyj jako danych wejciowych liczb niecakowitych, wic tutaj upewniamy si, e dla wartoci 0.5 toRoman rzuci wyjtek roman.NotIntegerError. Jeli toRoman nie rzuci takiego wyjtku, test ten powinien zakoczy si niepowodzeniem. Kolejne dwa wymagania s podobne do pierwszych trzech, przy czym odnosz si one do funkcji fromRoman zamiast toRoman: 4. Funkcja fromRoman powinna przyjmowa napis bdcy poprawn liczb w zapisie rzymskim i zwraca liczb cakowit, ktr ten napis reprezentuje. 5. Funkcja fromRoman powinna zakoczy si niepowodzeniem, gdy otrzyma na wejciu napis nie bdcy poprawn liczb w zapisie rzymskim. Wymaganie #4 jest obsugiwane w podobny sposb, jak wymaganie #1, poprzez iterowanie po zestawie znanych wartoci i testowanie kadej z nich. Wymaganie #5 jest z kolei obsugiwane podobnie, jak wymagania #2 i #3, poprzez testowanie serii niepoprawnych cigw wejciowych i sprawdzanie, czy fromRoman rzuca odpowiedni wyjtek. Przykad 13.4. Testowanie niepoprawnych danych wejciowych do funkcji fromRoman class FromRomanBadInput(unittest.TestCase): def testTooManyRepeatedNumerals(self): """fromRoman should fail with too many repeated numerals""" for s in (MMMM, DD, CCCC, LL, XXXX, VV, IIII): self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s) def testRepeatedPairs(self): """fromRoman should fail with repeated pairs of numerals""" for s in (CMCM, CDCD, XCXC, XLXL, IXIX, IVIV): self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s) def testMalformedAntecedent(self): """fromRoman should fail with malformed antecedents""" for s in (IIMXCC, VX, DCM, CMM, IXIV, MCMC, XCX, IVI, LM, LD, LC): self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s) 1. Nie ma tu nic nowego do powiedzenia: wzorzec postpowania jest dokadnie taki sam jak w przypadku testowania niepoprawnego wejcia do funkcji toRoman. Zaznacz tylko, e mamy teraz nieco inny wyjtek: roman.InvalidRomanNumeralError. Okazao si wic, e potrzebujemy trzech okrelonych przez nas wyjtkw, ktre

#(1)

298

ROZDZIA 14. TESTOWANIE JEDNOSTKOWE powinny zosta zdeniowane w roman.py (wraz z roman.OutOfRangeError i roman.NotIntegerError). Kiedy ju zajmiemy si implementacj roman.py w dalszej czci tego rozdziau, dowiesz si, jak deniowa wasne wyjtki.

14.6. TESTOWANIE ZDROWOROZSDKOWE

299

14.6

Testowanie zdroworozsdkowe

Do czsto zdarza si, e pewien fragment kodu zawiera zbir funkcji powizanych ze sob; zwykle s to funkcje konwertujce, z ktrych pierwsza przeksztaca A do B, a druga przeksztaca B do A. W takim przypadku rozsdnie jest utworzy zdroworozsdkowe sprawdzenie, dziki ktremu upewnimy si, e moemy przeksztaci A do B i z powrotem do A bez utraty dokadnoci, bez wprowadzania bdw zaokrgle i bez powodowania jakichkolwiek bdw innego typu. Rozwamy nastpujce wymaganie:

6. Jeli mamy pewn warto liczbow, ktr przeksztacamy na reprezentacj w zapisie rzymskim, a t przeksztacamy z powrotem do wartoci liczbowej, powinnimy otrzyma warto, od ktrej rozpoczynalimy przeksztacenie. A wic fromRoman(toRoman(n)) == n dla kadego n w przedziale 1..3999. Przykad 13.5. Testowanie toRoman wzgldem fromRoman class SanityCheck(unittest.TestCase): def testSanity(self): """fromRoman(toRoman(n))==n for all n""" for integer in range(1, 4000): numeral = roman.toRoman(integer) result = roman.fromRoman(numeral) self.assertEqual(integer, result) 1. Funkcj range widzielimy ju wczeniej, z tym, e tutaj wywoana jest ona z dwoma parametrami, dziki czemu zwraca list kolejnych liczb cakowitych z przedziau od wartoci bdcej pierwszym argumentem funkcji (1) do wartoci bdcej drugim argumentem funkcji (4000), bez tej wartoci. Zwrci wic kolejne liczby z przedziau 1..3999, ktre stanowi zakres poprawnych wartoci wejciowych do funkcji konwertujcej na notacj rzymsk. 2. Jeli ju tu jestemy, to wspomn tylko, e integer nie jest sowem kluczowym jzyka Python; zostao ono uyte po prostu jako nazwa zmiennej. 3. Waciwa logika testujca jest oczywista: bierzemy liczb cakowit (integer), przeksztacamy j do reprezentacji rzymskiej (numeral), nastpnie reprezentacj t przeksztacamy z powrotem do wartoci cakowitej (result) i upewniamy si, e otrzymalimy t sam warto, od ktrej rozpoczlimy przeksztacenia. Jeli nie jest to prawd, wwczas assertEqual rzuci wyjtek, a test natychmiast zakoczy si niepowodzeniem. Jeli za kada liczba po przeksztaceniach jest rwna wartoci pocztkowej to assertEqual zakoczy si prawidowo, rwnie testSanity zakoczy si prawidowo, a test zakoczy si powodzeniem. Ostatnie dwa wymagania rni si od poprzednich, poniewa wydaj si arbitralne i trywialne zarazem: 7. Funkcja toRoman powinna zwraca napis reprezentujcy liczb w notacji rzymskiej przy uyciu wycznie wielkich liter. 8. Funkcja fromRoman powinna akceptowa na wejciu napisy reprezentujce liczby w notacji rzymskiej pisane wycznie wielkimi literami (tj. powinna zakoczy si niepowodzeniem, gdy w napisie wejciowym znajduj si mae litery).

#(1) #(2)

#(3)

300

ROZDZIA 14. TESTOWANIE JEDNOSTKOWE

Nie da si ukry, e wymagania te s troch arbitralne. Moglibymy przecie ustali, e fromRoman przyjmuje zarwno napisy skadajce si z maych liter, jak rwnie napisy zawierajce zarwno mae, jak i due litery. Z drugiej strony, wymagania te nie s cakowicie arbitralne: jeli toRoman zawsze zwraca napisy skadajce si z wielkich liter, wwczas fromRoman musi akceptowa na wejciu przynajmniej te napisy, ktre skadaj si wycznie z wielkich liter, inaczej zdroworozsdkowe sprawdzenie (wymaganie #6) zakoczy si niepowodzeniem. Ustalenie, e na wejciu przyjmujemy napisy zoone wycznie z wielkich liter, jest arbitralne, jednak jak potwierdzi to kady integrator systemw wielko znakw ma zawsze znaczenie, a wic warto od razu t kwesti wyspecykowa. A skoro warto j wyspecykowa, to warto j rwnie przetestowa. Przykad 13.6. Testowanie wielkoci znakw class CaseCheck(unittest.TestCase): def testToRomanCase(self): """toRoman should always return uppercase""" for integer in range(1, 4000): numeral = roman.toRoman(integer) self.assertEqual(numeral, numeral.upper()) def testFromRomanCase(self): """fromRoman should only accept uppercase input""" for integer in range(1, 4000): numeral = roman.toRoman(integer) roman.fromRoman(numeral.upper()) self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, numeral.lower())

#(1)

#(2) #(3) #(4)

1. Najciekawsze w powyszym tecie jest to, jak wielu rzeczy on nie testuje. Nie testuje tego, czy warto zwrcona przez toRoman jest prawidowa czy choby spjna; na te pytania odpowiadaj inne przypadki testowe. Ten przypadek testowy sprawdza wycznie wielko liter. Poniewa zarwno on jak i sprawdzenie zdroworozsdkowe przebiegaj przez wszystkie wartoci z zakresu i wywouj toRoman, to moecie spotka si z pokus, aby obydwa te przypadki poczy w jeden1 . Jednak dziaanie takie pogwacioby jedn z podstawowych zasad testowania: kady przypadek testowy powinien odpowiada na dokadnie jedno pytanie. Wyobramy sobie, e poczylimy sprawdzenie wielkoci liter ze sprawdzeniem zdroworozsdkowym, a nowopowstay przypadek testowy zakoczy si niepowodzeniem. W takiej sytuacji stanlibymy przed koniecznoci gbszego przeanalizowania tego przypadku, aby dowiedzie si, w ktrej czci testu pojawi si problem, a wic co tak naprawd owo niepowodzenie oznacza. Jeli musicie analizowa wyniki testw po to, aby dowiedzie si, co one oznaczaj, to jest to oczywisty znak, e wasze przypadki testowe zostay le zaprojektowane. 2. Podobn lekcj otrzymujemy w tym miejscu: nawet, jeli wiemy, e funkcja toRoman zawsze zwraca wielkie litery, to aby przetestowa, e fromRoman przyjmuje napis zoony z wielkich liter, tutaj jawnie przeksztacamy warto wynikow toRoman do wielkich liter. Dlaczego to robimy? Ot dlatego, e zwracanie
1 Opr

si wszystkiemu za wyjtkiem pokusy. Oscar Wilde

14.6. TESTOWANIE ZDROWOROZSDKOWE

301

przez toRoman wielkich liter wynika z niezalenego wymagania. Jeli to wymaganie zostanie zmienione tak, e na przykad, funkcja ta bdzie zawsze zwracaa mae litery, to cho testToRomanCase bdzie musia si zmieni, ten test bdzie wci dziaa. To kolejna z podstawowych zasad testowania: kady przypadek testowy musi dziaa niezalenie od innych przypadkw. Kady test jest wysp. 3. Zauwacie, e wartoci zwracanej przez fromRoman nigdzie nie przypisujemy. W jzyku Python taka skadnia jest poprawna; jeli funkcja zwraca pewn warto, ale nikt nie jest ni zainteresowany, Python po prostu t warto wyrzuca. W tym przypadku wanie tego chcemy. Ten przypadek testowy w aden sposb nie testuje wartoci zwracanej; testuje jedynie to, czy fromRoman akceptuje napis zoony z wielkich liter i nie rzuca przy tym wyjtku. 4. Ta linijka, cho skomplikowana, bardzo przypomina to, co zrobilimy w testach ToRomanBadInput i FromRomanBadInput. W tym tecie upewniamy si, e wywoanie pewnej funkcji (roman.fromRoman) z pewnym szczeglnym parametrem (numeral.lower(), bieca warto rzymska pochodzca z ptli, pisana maymi literami) rzuci okrelony wyjtek (roman.InvalidRomanNumeralError). Jeli tak si stanie (dla kadej wartoci z ptli), test zakoczy si powodzeniem; jeli za przynajmniej raz zdarzy si co innego (zostanie rzucony inny wyjtek lub zostanie zwrcona warto bez rzucania wyjtku), test zakoczy si niepowodzeniem. W nastpnym rozdziale zobaczymy, jak napisa kod, ktry wszystkie te testy przechodzi.

302

ROZDZIA 14. TESTOWANIE JEDNOSTKOWE

Rozdzia 15

Testowanie 2

303

304

ROZDZIA 15. TESTOWANIE 2

15.1

roman.py, etap 1

Teraz, gdy ju s gotowe testy jednostkowe, nadszed czas na napisanie testowanego przez nie kodu. Zrobimy to w kilku etapach, dziki czemu bdziecie mieli okazj najpierw zobaczy, e wszystkie testy kocz si niepowodzeniem, a nastpnie przeledzi, w jaki sposb zaczynaj przechodzi, jeden po drugim, tak, e w kocu zapenione zostan wszelkie luki w module roman1.py. Przykad 14.1. roman1.py Plik jest dostpny w katalogu in py/roman/stage1/ wewntrz katalogu examples. Jeli jeszcze tego nie zrobilicie, moecie pobra ten oraz inne przykady uywane w tej ksice std. """Convert to and from Roman numerals""" #Define exceptions class RomanError(Exception): pass #(1) class OutOfRangeError(RomanError): pass #(2) class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #(3) def toRoman(n): """convert integer to Roman numeral""" pass def fromRoman(s): """convert Roman numeral to integer""" pass 1. W ten sposb w jzyku Python deniujemy nasze wasne wyjtki. Wyjtki s klasami, a tworzy si je przez utworzenie klasy pochodnej po jednej z ju istniejcych klas reprezentujcych wyjtki. Zaleca si (cho nie jest to wymagane), aby klasy pochodne tworzy po klasie Exception bdcej klas bazow dla wszystkich wyjtkw wbudowanych. W tym miejscu deniuj RomanError, ktra stanowi bdzie kas bazow dla wszystkich nowych klas wyjtkw, o ktrych powiem pniej. Utworzenie bazowej klasy wyjtku jest kwesti stylu; rwnie atwo mgbym kad now klas wyjtku wyprowadzi bezporednio z klasy Exception. 2. Wyjtki OutOfRangeError oraz NotIntegerError bd wykorzystywane przez funkcj fromRoman do poinformowania otoczenia o rnych nieprawidowociach w danych wejciowych, tak jak zostao to zdeniowane w ToRomanBadInput. 3. Wyjtek InvalidRomanNumeralError bdzie wykorzystany przez funkcj fromRoman do oznaczenia nieprawidowoci w danych wejciowych, tak jak zostao to zdeniowane w FromRomanBadInput. 4. Na tym etapie dymy do tego, aby zdeniowa API kadej z naszych funkcji, jednak nie chcemy jeszcze pisa ich kodu. Sygnalizujemy to uywajc sowa kluczowego pass. Nadesza teraz wielka chwila (wchodz werble!): moemy w kocu uruchomi testy na naszym maym, kadubkowym module. W tej chwili kady przypadek testowy

#(4)

15.1. ROMAN.PY, ETAP 1

305

powinien zakoczy si niepowodzeniem. W istocie, jeli na etapie 1 ktrykolwiek test przejdzie, powinnimy wrci do romantests.py i zastanowi si, dlaczego napisalimy tak bezuyteczny test, e przechodzi on dla funkcji, ktre w rzeczywistoci nic nie robi. Uruchomcie romantest1.py podajc w linii polece opcj -v, dziki ktrej otrzymamy dokadniejsze informacje i bdziemy mogli przeledzi, ktory test jest uruchamiany. Przy odrobinie szczcia wyjcie powinno wyglda tak: Przykad 14.2. Wyjcie programu romantest1.py testujcego roman1.py fromRoman should only accept uppercase input ... ERROR toRoman should always return uppercase ... ERROR fromRoman should fail with malformed antecedents ... FAIL fromRoman should fail with repeated pairs of numerals ... FAIL fromRoman should fail with too many repeated numerals ... FAIL fromRoman should give known result with known input ... FAIL toRoman should give known result with known input ... FAIL fromRoman(toRoman(n))==n for all n ... FAIL toRoman should fail with non-integer input ... FAIL toRoman should fail with negative input ... FAIL toRoman should fail with large input ... FAIL toRoman should fail with 0 input ... FAIL ====================================================================== ERROR: fromRoman should only accept uppercase input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 154, in testFromRomanCase roman1.fromRoman(numeral.upper()) AttributeError: None object has no attribute upper ====================================================================== ERROR: toRoman should always return uppercase ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 148, in testToRomanCase self.assertEqual(numeral, numeral.upper()) AttributeError: None object has no attribute upper ====================================================================== FAIL: fromRoman should fail with malformed antecedents ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 133, in testMalformedAntecedent self.assertRaises(roman1.InvalidRomanNumeralError, roman1.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with repeated pairs of numerals ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 127, in testRepeatedPairs

306

ROZDZIA 15. TESTOWANIE 2

self.assertRaises(roman1.InvalidRomanNumeralError, roman1.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with too many repeated numerals ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 122, in testTooManyRepeatedNumerals self.assertRaises(roman1.InvalidRomanNumeralError, roman1.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should give known result with known input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 99, in testFromRomanKnownValues self.assertEqual(integer, result) File "c:\python21\lib\unittest.py", line 273, in failUnlessEqual raise self.failureException, (msg or %s != %s % (first, second)) AssertionError: 1 != None ====================================================================== FAIL: toRoman should give known result with known input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 93, in testToRomanKnownValues self.assertEqual(numeral, result) File "c:\python21\lib\unittest.py", line 273, in failUnlessEqual raise self.failureException, (msg or %s != %s % (first, second)) AssertionError: I != None ====================================================================== FAIL: fromRoman(toRoman(n))==n for all n ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 141, in testSanity self.assertEqual(integer, result) File "c:\python21\lib\unittest.py", line 273, in failUnlessEqual raise self.failureException, (msg or %s != %s % (first, second)) AssertionError: 1 != None ====================================================================== FAIL: toRoman should fail with non-integer input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 116, in testNonInteger self.assertRaises(roman1.NotIntegerError, roman1.toRoman, 0.5) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises

15.1. ROMAN.PY, ETAP 1

307

raise self.failureException, excName AssertionError: NotIntegerError ====================================================================== FAIL: toRoman should fail with negative input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 112, in testNegative self.assertRaises(roman1.OutOfRangeError, roman1.toRoman, -1) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: OutOfRangeError ====================================================================== FAIL: toRoman should fail with large input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 104, in testTooLarge self.assertRaises(roman1.OutOfRangeError, roman1.toRoman, 4000) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: OutOfRangeError ====================================================================== FAIL: toRoman should fail with 0 input #(1) ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 108, in testZero self.assertRaises(roman1.OutOfRangeError, roman1.toRoman, 0) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: OutOfRangeError #(2) ---------------------------------------------------------------------Ran 12 tests in 0.040s #(3) FAILED (failures=10, errors=2) #(4)

1. Po uruchomieniu skryptu zostaje wywoana funkcja unittest.main(), ktra z kolei wywouje kad z metod zdeniowanych w kadej klasie wewntrz romantest.py. Dla kadego przypadku testowego wypisywany jest napis dokumentujcy odpowiadajcej mu metody oraz to, czy przypadek testowy przeszed, czy nie. Tak, jak si spodziewalimy, aden test nie przeszed. 2. Dla kadego przypadku testowego, ktry zakoczy si niepowodzeniem, unittest wypisuje zawarto stosu, dziki czemu wida dokadnie, co si stao. W tym przypadku wywoanie funkcji assertRaises (znanej rwnie pod nazw failUnlessRaises) spowodowao rzucenie wyjtku AssertionError z tego powodu, e w tecie spodziewalimy si, e toRoman rzuci OutOfRangeError, a taki wyjtek nie zosta rzucony. 3. Po wypisaniu szczegw, unittest wypisuje podsumowanie zawierajce informacje o tym, ile testw zostao uruchomionych oraz jak dugo one trway. 4. Oglnie rzecz biorc, test jednostkowy nie przechodzi, jeli przynajmniej jeden przypadek testowy nie przechodzi. Kiedy przypadek testowy nie przej-

308

ROZDZIA 15. TESTOWANIE 2 dzie, unittest rozrnia niepowodzenia (failures) i bdy (errors). Niepowodzenie wystpuje w przypadku wywoa metod assertXYZ, np. assertEqual czy assertRaises, ktre kocz si niepowodzeniem, poniewa nie zosta speniony pewien zakadany warunek albo nie zosta rzucony spodziewany wyjtek. Bd natomiast wystpuje wwczas, gdy zostanie rzucony jakikolwiek inny wyjtek i to zarwno w kodzie testowanym, jak i w kodzie samego testu. Na przykad bd wystpi w metodzie testFromRomanCase (Funkcja fromRoman powinna akceptowa na wejciu napisy zawierajce wycznie wielkie litery), poniewa wywoanie numeral.upper() rzucio wyjtek AttributeError: toRoman miao zwrci napis, a tego nie zrobio. Natomiast testZero (Funkcja toRoman otrzymujca na wejciu warto 0 powinna zakoczy si niepowodzeniem) zakoczya si niepowodzeniem, poniewa wywoanie fromRoman nie rzucio wyjtku InvalidRomanNumeral, ktrego spodziewa si assertRaises.

15.2. ROMAN.PY, ETAP 2

309

15.2

roman.py, etap 2

Struktur moduu roman mamy ju z grubsza okrelon, nadszed wic czas na napisanie kodu i sprawienie, e nasze testy zaczn w kocu przechodzi. Przykad 14.3. roman2.py Plik jest dostpny w katalogu in py/roman/stage2/ wewntrz katalogu examples. Jeli jeszcze tego nie zrobilicie, moecie pobra ten oraz inne przykady uywane w tej ksice std. """Convert to and from Roman numerals""" #Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Define digit mapping romanNumeralMap = ((M, (CM, (D, (CD, (C, (XC, (L, (XL, (X, (IX, (V, (IV, (I,

1000), 900), 500), 400), 100), 90), 50), 40), 10), 9), 5), 4), 1))

#(1)

def toRoman(n): """convert integer to Roman numeral""" result = "" for numeral, integer in romanNumeralMap: while n >= integer: result += numeral n -= integer return result def fromRoman(s): """convert Roman numeral to integer""" pass

#(2)

1. romanNumeralMap jest krotk krotek, ktra deniuje trzy elementy: (a) reprezentacj znakow najbardziej podstawowych liczb rzymskich; zauwacie, e nie s to wycznie liczby, ktrych reprezentacja skada si z jednego znaku; zdeniowane s rwnie pary dwuznakowe, takie jak CM (o

310

ROZDZIA 15. TESTOWANIE 2 sto mniej ni tysic), dziki ktrym kod funkcji toRoman bdzie znacznie prostszy (b) porzdek liczb rzymskich; s one uporzdkowane malejco wzgldem ich liczbowej wartoci od M do I (c) warto liczbow odpowiadajc reprezentacji rzymskiej; kada wewntrzna krotka jest par (reprezentacja rzymska, warto liczbowa)

2. To jest wanie miejsce, w ktrym wida, e opacao si wprowadzi opisan wyej bogat struktur danych nie potrzebujemy adnej specjalnej logiki do obsuenia reguy odejmowania. Aby przeksztaci warto liczbow do reprezentacji rzymskiej wystarczy przeiterowa po romanNumeralMap szukajc najwyszej wartoci cakowitej mniejszej bd rwnej wartoci wejciowej. Po jej znalezieniu dopisujemy odpowiadajc jej reprezentacj rzymsk na koniec napisu wyjciowego, odejmujemy jej warto od wartoci wejciowej, pierzemy, puczemy, powtarzamy. Przykad 14.4. Jak dziaa toRoman Jeli sposb dziaania funkcji toRoman nie jest cakiem jasny, dodajcie na koniec ptli while instrukcj print: while n >= integer: result += numeral n -= integer print subtracting, integer, from input, adding, numeral, to output >>> import roman2 >>> roman2.toRoman(1424) subtracting 1000 from input, adding M to output subtracting 400 from input, adding CD to output subtracting 10 from input, adding X to output subtracting 10 from input, adding X to output subtracting 4 from input, adding IV to output MCDXXIV Funkcja toRoman wydaje si dziaa, przynajmniej w przypadku tego szybkiego, rcznego sprawdzenia. Czy jednak przechodzi ona testy? C, niezupenie. Przykad 14.5. Wyjcie programu romantest2.py testujcego roman2.py Pamitajcie o tym, aby uruchomi romantest2.py z opcj -v w linii polece, dziki czemu wczy si tryb rozwleky. fromRoman should only accept uppercase input ... FAIL toRoman should always return uppercase ... ok fromRoman should fail with malformed antecedents ... FAIL fromRoman should fail with repeated pairs of numerals ... FAIL fromRoman should fail with too many repeated numerals ... FAIL fromRoman should give known result with known input ... FAIL toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... FAIL toRoman should fail with non-integer input ... FAIL toRoman should fail with negative input ... FAIL #(1)

#(2) #(3)

15.2. ROMAN.PY, ETAP 2 toRoman should fail with large input ... FAIL toRoman should fail with 0 input ... FAIL

311

1. Poniewa w romanNumeralMap reprezentacja liczb rzymskich jest wyraona przy pomocy wielkich liter, funkcja toRoman rzeczywicie zawsze zwraca napisy zoone z wielkich liter. A wic ten test przechodzi. 2. Tu pojawia si istotna wiadomo: obecna wersja toRoman przechodzi test znanych wartoci. Cho test ten nie jest zbyt wyczerpujcy, sprawdza on wiele spord poprawnych danych wejciowych, wliczajc w to wartoci, ktre powinny da w wyniku kad reprezentacj jednoliterow, najwiksz moliw warto (3999) czy te warto, ktra daje w wyniku najdusz reprezentacj rzymsk (3888). Na tej podstawie moemy by raczej pewni, e funkcja zwrci poprawn reprezentacj dla wszystkich poprawnych danych wejciowych. 3. Niestety, funkcja nie dziaa dla nieprawidowych danych wejciowych; nie przechodzi aden test badajcy dziaanie funkcji dla niepoprawnych danych. Ma to sens, poniewa nie umiecilimy jeszcze w kodzie funkcji adnego sprawdzenia dotyczcego bdnych danych. Testy, o ktrych tu mwimy, sprawdzaj (uywajc assertRaises), czy w takich sytuacjach zostaje rzucony odpowiedni wyjtek, a my nigdzie go nie rzucamy. Zrobimy to jednak ju w nastpnym etapie. Poniej znajduje si dalszy cig wyjcia po uruchomieniu testw jednostkowych, prezentujcy szczegy niepowodze. Jest ich a 10. ====================================================================== FAIL: fromRoman should only accept uppercase input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 156, in testFromRomanCase roman2.fromRoman, numeral.lower()) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with malformed antecedents ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 133, in testMalformedAntecedent self.assertRaises(roman2.InvalidRomanNumeralError, roman2.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with repeated pairs of numerals ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 127, in testRepeatedPairs self.assertRaises(roman2.InvalidRomanNumeralError, roman2.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises

312

ROZDZIA 15. TESTOWANIE 2

raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with too many repeated numerals ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 122, in testTooManyRepeatedNumerals self.assertRaises(roman2.InvalidRomanNumeralError, roman2.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should give known result with known input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 99, in testFromRomanKnownValues self.assertEqual(integer, result) File "c:\python21\lib\unittest.py", line 273, in failUnlessEqual raise self.failureException, (msg or %s != %s % (first, second)) AssertionError: 1 != None ====================================================================== FAIL: fromRoman(toRoman(n))==n for all n ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 141, in testSanity self.assertEqual(integer, result) File "c:\python21\lib\unittest.py", line 273, in failUnlessEqual raise self.failureException, (msg or %s != %s % (first, second)) AssertionError: 1 != None ====================================================================== FAIL: toRoman should fail with non-integer input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 116, in testNonInteger self.assertRaises(roman2.NotIntegerError, roman2.toRoman, 0.5) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: NotIntegerError ====================================================================== FAIL: toRoman should fail with negative input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 112, in testNegative self.assertRaises(roman2.OutOfRangeError, roman2.toRoman, -1) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: OutOfRangeError ======================================================================

15.2. ROMAN.PY, ETAP 2

313

FAIL: toRoman should fail with large input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 104, in testTooLarge self.assertRaises(roman2.OutOfRangeError, roman2.toRoman, 4000) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: OutOfRangeError ====================================================================== FAIL: toRoman should fail with 0 input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 108, in testZero self.assertRaises(roman2.OutOfRangeError, roman2.toRoman, 0) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: OutOfRangeError ---------------------------------------------------------------------Ran 12 tests in 0.320s FAILED (failures=10)

314

ROZDZIA 15. TESTOWANIE 2

15.3

roman.py, etap 3

roman.py, etap 3
Teraz ju toRoman odpowiednio sobie radzi z dobrym wejciem (liczbami cakowitymi od 1 do 3999), wic teraz jest czas zajc si niepoprawnym wejciem (wszystkim innym). Przykad 14.6. roman3.py Plik ten jest dostpny z py/roman/stage3/ w katalogu przykadw. Jeli jeszcze tego nie zrobie, moesz pobra ten i inne przykady wykorzystane w tej ksice. """Convert to and from Roman numerals""" #Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Define digit mapping romanNumeralMap = ((M, (CM, (D, (CD, (C, (XC, (L, (XL, (X, (IX, (V, (IV, (I,

1000), 900), 500), 400), 100), 90), 50), 40), 10), 9), 5), 4), 1))

def toRoman(n): """convert integer to Roman numeral""" if not (0 < n < 4000): #(1) raise OutOfRangeError, "number out of range (must be 1..3999)" #(2) if int(n) <> n: #(3) raise NotIntegerError, "non-integers can not be converted" result = "" for numeral, integer in romanNumeralMap: while n >= integer: result += numeral n -= integer return result def fromRoman(s): #(4)

15.3. ROMAN.PY, ETAP 3 """convert Roman numeral to integer""" pass

315

1. Jest to przyjemny pythonowy skrt: wielokrotne porwnanie. Jest to odpowiedniek do if not ((0 < n) and (n < 4000)), jednak atwiejszy do odczytu. Za pomoc tego kontrolujemy zakres wartoci i sprawdzamy, czy wprowadzona liczba nie jest za dua, ujemna, czy te rwna zero. 2. Wyrzucamy wyjtek za pomoc wyraenia raise. Moemy wyrzuci kady wbudowane wyjtek, a take inny zdeniowany przez nas wyjtek. Drugi parametr, wiadomo bdu, jest opcjonalny; jeli dostaniemy wyjtek i nigdzie jego nie obsuymy, zostanie on wywietlone w traceback (w postaci ladw stosu). 3. Za pomoc tego sprawdzamy, czy liczba nie jest cakowita. Liczby nie bdce liczbami cakowitymi nie mog zosta przekonwertowane na system rzymski. 4. Pozostaa cz funkcji jest niezmieniona. Przykad 14.7. Obserwujemy, jak toRoman radzi sobie z bdnym wejciem >>> import roman3 >>> roman3.toRoman(4000) Traceback (most recent call last): File "<interactive input>", line 1, in ? File "roman3.py", line 27, in toRoman raise OutOfRangeError, "number out of range (must be 1..3999)" OutOfRangeError: number out of range (must be 1..3999) >>> roman3.toRoman(1.5) Traceback (most recent call last): File "<interactive input>", line 1, in ? File "roman3.py", line 29, in toRoman raise NotIntegerError, "non-integers can not be converted" NotIntegerError: non-integers can not be converted

Przykad 14.8. Wyjcie romantest3.py w zalenoci od roman3.py fromRoman should only accept uppercase input ... FAIL toRoman should always return uppercase ... ok fromRoman should fail with malformed antecedents ... FAIL fromRoman should fail with repeated pairs of numerals ... FAIL fromRoman should fail with too many repeated numerals ... FAIL fromRoman should give known result with known input ... FAIL toRoman should give known result with known input ... ok #(1) fromRoman(toRoman(n))==n for all n ... FAIL toRoman should fail with non-integer input ... ok #(2) toRoman should fail with negative input ... ok #(3) toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok

316

ROZDZIA 15. TESTOWANIE 2

1. toRoman dalej przechodzi testy o znanych wartociach, co jest pocieszajce. Ponadto przechodzi wszystkie testy, ktre przechodzi w etapie 2, zatem ostatni kod niczego nie popsu. 2. Bardziej ekscytujcy jest fakty, e teraz nasz program przechodzi wszystkie testy z niepoprawnym wejciem. Przechodzi ten test (czyli testNonInteger), poniewa kontrolujemy, czy int(n) <> n. Kiedy do funkcji toRoman zostanie przekazana warto nie bdca liczb cakowit, porwnanie int(n) <> n wyapie to i wyrzuci wyjtek NotIntegerError, a tego oczekuje test testNonInteger. 3. Program przechodzi ten test (test testNegative), poniewa w przypadku prawdziwoci wyraenia not (0 < n < 4000) zostanie wyrzucony wyjtek OutOfRangeError, a ktrego oczekuje test testNegative. ====================================================================== FAIL: fromRoman should only accept uppercase input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage3\romantest3.py", line 156, in testFromRomanCase roman3.fromRoman, numeral.lower()) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with malformed antecedents ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage3\romantest3.py", line 133, in testMalformedAntecedent self.assertRaises(roman3.InvalidRomanNumeralError, roman3.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with repeated pairs of numerals ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage3\romantest3.py", line 127, in testRepeatedPairs self.assertRaises(roman3.InvalidRomanNumeralError, roman3.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with too many repeated numerals ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage3\romantest3.py", line 122, in testTooManyRepeatedNumerals self.assertRaises(roman3.InvalidRomanNumeralError, roman3.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName

15.3. ROMAN.PY, ETAP 3

317

AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should give known result with known input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage3\romantest3.py", line 99, in testFromRomanKnownValues self.assertEqual(integer, result) File "c:\python21\lib\unittest.py", line 273, in failUnlessEqual raise self.failureException, (msg or %s != %s % (first, second)) AssertionError: 1 != None ====================================================================== FAIL: fromRoman(toRoman(n))==n for all n ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage3\romantest3.py", line 141, in testSanity self.assertEqual(integer, result) File "c:\python21\lib\unittest.py", line 273, in failUnlessEqual raise self.failureException, (msg or %s != %s % (first, second)) AssertionError: 1 != None ---------------------------------------------------------------------Ran 12 tests in 0.401s FAILED (failures=6) #(1) 1. Teraz liczba niezaliczonych testw zmniejszya si do 6 i wszystkie je powoduje fromRoman, czyli: test znanych wartoci, trzy testy dotyczce niepoprawnych argumentw, kontrola wielkoci znakw i kontrola zdroworozsdkowa (czyli fromRoman(toRoman(n))==n). Oznacza to, e toRoman przeszed wszystkie testy, ktre mg przej samemu. (Nawala w tecie zdroworozsdkowym, ale test ten wymaga take napisania funkcji fromRoman, a to jeszcze nie zostao zrobione.) Oznacza to, e musimy przesta ju kodowa toRoman. Ju nie ulepszamy, nie kombinujemy, bez ekstra a moe ten. Stop. Teraz odejdziemy od klawiatury. Jedn z najistotniejszych spraw jest to, e rozumowy unit testing mwi tobie, kiedy przesta kodowa. Kiedy funkcja przechodzi wszystkie unit testy przeznaczone dla niej, koczymy kodowa t funkcj. Kiedy wszystkie unit test dla caego moduu zostan zaliczone, przestajemy kodowa modu.

318

ROZDZIA 15. TESTOWANIE 2

15.4

roman.py, etap 4

Implementacja funkcji toRoman zostaa zakoczona, czas zaj si funkcj fromRoman. Dziki bogatej strukturze danych przechowujcej pewne wartoci w reprezentacji rzymskiej wraz z ich wartociami liczbowymi, zadanie to nie bdzie wcale trudniejsze, ni napisanie funkcji toRoman. Przykad 14.9. roman4.py Plik jest dostpny w katalogu in py/roman/stage4/ wewntrz katalogu examples. Jeli jeszcze tego nie zrobilicie, moecie pobra ten oraz inne przykady uywane w tej ksice std. """Convert to and from Roman numerals""" #Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Define digit mapping romanNumeralMap = ((M, (CM, (D, (CD, (C, (XC, (L, (XL, (X, (IX, (V, (IV, (I,

1000), 900), 500), 400), 100), 90), 50), 40), 10), 9), 5), 4), 1))

# toRoman function omitted for clarity (it hasnt changed) def fromRoman(s): """convert Roman numeral to integer""" result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: #(1) result += integer index += len(numeral) return result 1. Sposb dziaania jest taki sam jak w toRoman. Iterujemy po reprezentacjach rzymskich w strukturze danych (bdcej krotk krotek), jednak zamiast dopasowywania najwikszej wartoci cakowitej tak czsto, jak to moliwe, dopasowujemy najwysz reprezentacj rzymsk tak czsto, jak to moliwe.

15.4. ROMAN.PY, ETAP 4

319

Przykad 14.10. Jak dziaa fromRoman Jeli wci nie jestecie pewni, jak dziaa fromRoman, na kocu ptli while dodajcie instrukcj print: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) print found, numeral, of length, len(numeral), , adding, integer >>> import roman4 >>> roman4.fromRoman(MCMLXXII) found M , of length 1, adding 1000 found CM , of length 2, adding 900 found L , of length 1, adding 50 found X , of length 1, adding 10 found X , of length 1, adding 10 found I , of length 1, adding 1 found I , of length 1, adding 1 1972 Przykad 14.11. Wyjcie programu romantest4.py testujcego roman4.py fromRoman should only accept uppercase input ... FAIL toRoman should always return uppercase ... ok fromRoman should fail with malformed antecedents ... FAIL fromRoman should fail with repeated pairs of numerals ... FAIL fromRoman should fail with too many repeated numerals ... FAIL fromRoman should give known result with known input ... ok #(1) toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok #(2) toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok 1. Mamy tu dwie interesujce wiadomosci: po pierwsze, fromRoman dziaa dla poprawnych danych wejciowych, przynajmniej dla tych, ktre s zdeniowane w tecie poprawnych wartoci. 2. Po drugie, test zdroworozsdkowy rwnie przeszed. Wiedzc o tym, e przeszed rwnie test znanych wartoci, moemy by raczej pewni, e zarwno fromRoman jak i toRoman dziaaj poprawnie dla poprawnych danych wejciowych. (Nic nam tego jednak nie gwarantuje; teoretycznie jest moliwe, e w funkcji toRoman ukryty jest jaki bd, przez ktry dla pewnego zestawu danych wejciowych generowane s niepoprawne reprezentacje rzymskie, natomaist fromRoman zawiera moe symetryczny bd, ktry z kolei powoduje, e dla tych wanie rzymskich reprezentacji generowane s niepoprawne wartoci liczbowe. W zalenoci od zastosowa waszego kodu, a take wymaga, jakim ten kod podlega, moe to stanowi dla was pewien problem; jeli tak jest, dopiszcie wicej bardziej wszechstronnych testw tak, aby zmniejszya si wasza niepewno.)

320

ROZDZIA 15. TESTOWANIE 2

====================================================================== FAIL: fromRoman should only accept uppercase input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage4\romantest4.py", line 156, in testFromRomanCase roman4.fromRoman, numeral.lower()) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with malformed antecedents ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage4\romantest4.py", line 133, in testMalformedAntecedent self.assertRaises(roman4.InvalidRomanNumeralError, roman4.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with repeated pairs of numerals ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage4\romantest4.py", line 127, in testRepeatedPairs self.assertRaises(roman4.InvalidRomanNumeralError, roman4.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with too many repeated numerals ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage4\romantest4.py", line 122, in testTooManyRepeatedNumerals self.assertRaises(roman4.InvalidRomanNumeralError, roman4.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ---------------------------------------------------------------------Ran 12 tests in 1.222s FAILED (failures=4)

15.5. ROMAN.PY, ETAP 5

321

15.5

roman.py, etap 5

Funkcja fromRoman dziaa poprawnie dla poprawnych danych wejciowych, nadszed wic czas na dooenie ostatniego klocka w naszej ukadance: napisanie kodu, dziki ktremu funkcja ta bdzie dziaaa poprawnie rwnie dla niepoprawnych danych wejciowych. Oznacza to, e musimy znale sposb na ustalenie, czy dany napis stanowi poprawn rzymsk reprezentacj pewnej wartoci. To zadanie jest znacznie trudniejsze, ni sprawdzenie poprawnoci wartoci liczbowej w funkcji toRoman, jednak moemy do tego celu uy silnego narzdzia: wyrae regularnych. Jeli nie znacie wyrae regularnych i nie przeczytalicie jeszcze podrozdziau 7 Wyraenia regularne, nadszed wanie doskonay moment, aby to zrobi. Jak widzielimy w podrozdziale 7.3 Analiza przypadku: Liczby rzymskie, istnieje kilka prostych regu, dziki ktrym mona skonstruowa napis reprezentujcy warto liczbow w zapisie rzymskim, uywajc liter M, D, C, L, X, V oraz I. Przeledmy je po kolei: 1. Znaki mona dodawa. I to 1, II to 2, III to 3. VI to 6 (dosownie: 5 i 1), VII to 7, a VIII to 8. 2. Liczby skadajce si z jedynki i (by moe) zer (I, X, C oraz M) liczby dziesitkowe mog by powtarzane do trzech razy. Przy czwartym naley odj t warto od znaku reprezentujcego liczb skadajc si z pitki i (by moe) zer liczb pitkow. Nie mona przedstawi liczby 4 jako IIII, naley przedstawi j jako IV (1 odjte od 5). Liczb 40 zapisujemy jako XL (10 odjte od 50), 41 jako XLI, 42 jako XLII, 43 jako XLIII, a 44 jako XLIV (10 odjte od 50 oraz 1 odjte od 5). 3. Podobnie tworzymy liczby dziewitkowe: naley odejmowa od najbliszej liczby dziesitkowej: 8 to VIII, jednak 9 to IX (1 odjte od 10), a nie VIIII (poniewa I nie moe by powtrzone wicej ni trzy razy), za 90 to XC, a 900 to CM. 4. Liczby pitkowe nie mog by powtarzane. 10 zawsze reprezentowane jest jako X, a nie VV, 100 jako C, nigdy za jako LL. 5. Liczby w reprezentacji rzymskiej s zawsze zapisywane od najwikszych do najmniejszych i odczytywane od lewej do prawej, a wic porzdek znakw ma ogromne znaczenie. DC to 600; CD to zupenie inna liczba (400, 100 odjte od 500). CI to 101, IC za nie jest poprawn wartoci w zapisie rzymskim, poniewa nie mona bezporednio odj 1 od 100: naleaoby zapisa XCIX (10 odjte od 100 oraz 1 odjte od 10). Przykad 14.12. roman5.py Plik jest dostpny w katalogu in py/roman/stage5/ wewntrz katalogu examples. Jeli jeszcze tego nie zrobilicie, moecie pobra ten oraz inne przykady uywane w tej ksice std. """Convert to and from Roman numerals""" import re #Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass

322

ROZDZIA 15. TESTOWANIE 2

class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Define digit mapping romanNumeralMap = ((M, (CM, (D, (CD, (C, (XC, (L, (XL, (X, (IX, (V, (IV, (I,

1000), 900), 500), 400), 100), 90), 50), 40), 10), 9), 5), 4), 1))

def toRoman(n): """convert integer to Roman numeral""" if not (0 < n < 4000): raise OutOfRangeError, "number out of range (must be 1..3999)" if int(n) <> n: raise NotIntegerError, "non-integers can not be converted" result = "" for numeral, integer in romanNumeralMap: while n >= integer: result += numeral n -= integer return result #Define pattern to detect valid Roman numerals romanNumeralPattern = ^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$ #(1) def fromRoman(s): """convert Roman numeral to integer""" if not re.search(romanNumeralPattern, s): raise InvalidRomanNumeralError, Invalid Roman numeral: %s % s result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result

#(2)

1. To kontynuacja wyraenia, o ktrym dyskutowalimy w podrozdziale 7.3 Ana-

15.5. ROMAN.PY, ETAP 5

323

liza przypadku: Liczby rzymskie. Miejsce dziesitki jest w napisie XC (90), XL (40) oraz w napisie zoonym z opcjonalnego L oraz nastpujcym po niej opcjonalnym znaku X powtrzonym od 0 do 3 razy. Miejsce jedynki jest w napisie IX (9), IV (4) oraz przy opcjonalnym V z nastpujcym po niej, opcjonalnym znakiem I powtrzonym od 0 do 3 razy. 2. Po wpisaniu tej logiki w wyraenie regularne otrzymamy trywialny kod sprawdzajcy poprawno napisw potencjalnie reprezentujcych liczby rzymskie. Jeli re.search zwrci obiekt, wyraenie regularne zostao dopasowane, a wic dane wejciowe s poprawne; w przeciwnym wypadku, dane wejciowe s niepoprawne. W tym momencie macie prawo by nieufni wobec tego wielkiego, brzydkiego wyraenia regularnego, ktre ma si rzekomo dopasowa do wszystkich poprawnych napisw reprezentujcych liczby rzymskie. Oczywicie, nie musicie mi wierzy, spjrzcie zatem na wyniki testw: Example 14.13. Output of romantest5.py against roman5.py fromRoman should only accept uppercase input ... ok #(1) toRoman should always return uppercase ... ok fromRoman should fail with malformed antecedents ... ok #(2) fromRoman should fail with repeated pairs of numerals ... ok #(3) fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ---------------------------------------------------------------------Ran 12 tests in 2.864s OK #(4) 1. Jedna rzecz o jakiej nie wspomniaem w kontekcie wyrae regularnych to fakt, e s one zalene od wielkoci znakw. Ze wzgldu na to, e wyraenie regularne romanNumeralPattern zostao zapisane przy uyciu wielkich liter, sprawdzenie re.search odrzuci wszystkie napisy, ktre zawieraj przynajmniej jedn ma liter. Dlatego te test wielkich liter przechodzi. 2. Co wicej, przechodz rwnie testy nieprawidowych danych wejciowych. Przykadowo test niepoprawnych poprzednikw sprawdza przypadki takie, jak MCMC. Jak widzimy, wyraenie regularne nie pasuje do tego napisu, a wic fromRoman rzuca wyjtek InvalidRomanNumeralError, i jest to dokadnie taki wyjtek, jakiego spodziewa si test niepoprawnych poprzednikw, a wic test ten przechodzi. 3. Rzeczywicie przechodz wszystkie testy sprawdzajce niepoprawne dane wejciowe. Wyraenie regularne wyapuje wszystkie przypadki, o jakich mylelimy podczas przygotowywania naszych przypadkw testowych.

324

ROZDZIA 15. TESTOWANIE 2

4. Nagrod najwikszego rozczarowania roku otrzymuje swko OK, ktre zostao wypisane przez modu unittest w chwili gdy okazao si, e wszystkie testy zakoczyy si powodzeniem. Kiedy wszystkie testy przechodz, przesta kodowa.

Rozdzia 16

Refaktoryzacja

325

326

ROZDZIA 16. REFAKTORYZACJA

16.1

Obsuga bdw

Mimo wielkiego wysiku wkadanego w pisanie testw jednostkowych bdy wci si zdarzaj. Co mam na myli piszc bd? Bd to przypadek testowy, ktry nie zosta jeszcze napisany. Przykad 15.1. Bd >>> import roman5 >>> roman5.fromRoman("") 0 1. Czy pamitasz poprzedni rozdzia, w ktrym okazao si, e pusty napis pasuje do wyraenia regularnego uywanego do sprawdzania poprawnoci liczb rzymskich? Ot okazuje si, e jest to prawd nawet w ostatecznej wersji wyraenia regularnego. I to jest wanie bd; podanym rezultatem przekazania pustego napisu, podobnie jak kadego innego napisu, ktry nie reprezentuje poprawnej liczby rzymskiej, jest rzucenie wyjtku InvalidRomanNumeralError. Po udanym odtworzeniu bdu, ale przed jego naprawieniem, powinno si napisa przypadek testowy, ktry nie dziaa, uwidaczniajc w ten sposb znaleziony bd. Przykad 15.2. Testowanie bdu (romantest61.py) class FromRomanBadInput(unittest.TestCase): # previous test cases omitted for clarity (they havent changed) def testBlank(self): """fromRoman should fail with blank string""" self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, "") 1. Sprawa jest prosta. Wywoujemy fromRoman z pustym napisem i upewniamy si, e zosta rzucony wyjtek InvalidRomanNumeralError. Najtrudniejsz czci byo znalezienie bdu; teraz, kiedy ju o nim wiemy, testowanie okazuje si atwe. Mamy ju odpowiedni przypadek testowy, jednak nie bdzie on dziaa, poniewa w kodzie wci jest bd: Przykad 15.3. Wyjcie programu romantest61.py testujcego roman61.py fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with blank string ... FAIL fromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok

#(1)

#(1)

16.1. OBSUGA BDW

327

====================================================================== FAIL: fromRoman should fail with blank string ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage6\romantest61.py", line 137, in testBlank self.assertRaises(roman61.InvalidRomanNumeralError, roman61.fromRoman, "") File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ---------------------------------------------------------------------Ran 13 tests in 2.864s FAILED (failures=1) Teraz moemy przystpi do naprawy bdu. Przykad 15.4. Poprawiane bdu (roman62.py) Plik jest dostpny w katalogu py/roman/stage6/ znajdujcym si w katalogu z przykadami. def fromRoman(s): """convert Roman numeral to integer""" if not s: raise InvalidRomanNumeralError, Input can not be blank if not re.search(romanNumeralPattern, s): raise InvalidRomanNumeralError, Invalid Roman numeral: %s % s result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result 1. Potrzebne s tylko dwie dodatkowe linie kodu: jawne sprawdzenie pustego napisu oraz wyraenie raise. Przykad 15.5. Wyjcie programu romantest62.py testujcego roman62.py fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with blank string ... ok fromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok

#(1)

#(1)

328

ROZDZIA 16. REFAKTORYZACJA

toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ---------------------------------------------------------------------Ran 13 tests in 2.834s OK 1. Test pustego napisu przechodzi, a wic bd udao si naprawi. 2. Wszystkie pozostae testy przechodz, co oznacza, e poprawka bdu nie zepsua kodu w innych miejscach. Koniec kodowania. Ten sposb kodowania nie sprawi, e znajdowanie bdw stanie si atwiejsze. Proste bdy (takie, jak ten w przykadzie) wymagaj prostych testw jednostkowych; bdy bardziej zoone bd wymagay testw odpowiednio bardziej zoonych. W rodowisku, w ktrym na testowanie kadzie si duy nacisk, moe si pocztkowo wydawa, e poprawienie bdu zabiera znacznie wicej czasu: najpierw naley dokadnie wyrazi w kodzie, na czym polega bd (czyli napisa przypadek testowy), a pniej dopiero go poprawi. Nastpnie, jeli przypadek testowy nie przechodzi, naley sprawdzi, czy to poprawka bya niewystarczajca, czy moe kod przypadku testowego zosta niepoprawnie zaimplementowany. Jednak w dugiej perspektywie takie przeczanie si midzy kodem i testami niewtpliwie si opaca, poniewa poprawienie bdu za pierwszym razem jest o wiele bardziej prawdopodobne. Dodatkowo, moliwo uruchomienia wszystkich testw cznie z dopisanym nowym przypadkiem testowym pozwala atwo sprawdzi, czy poprawka bdu nie spowodowaa problemw w starym kodzie. Dzisiejszy test jednostkowy staje si wic jutrzejszym testem regresyjnym. #(2)

16.2. OBSUGA ZMIENIAJCYCH SI WYMAGA

329

16.2

Obsuga zmieniajcych si wymaga

Chobymy prbowali przyszpili swoich klientw do ziemi w celu uzyskania od nich dokadnych wymaga, uywajc tak przeraajcych narzdzi tortur, jak noyce czy gorcy wosk, to i tak te wymagania si zmieni. Wikszo klientw nie wie, czego chce, dopki tego nie zobaczy, a nawet jak ju zobaczy, to nie jest w stanie wyartykuowa tego wystarczajco precyzyjnie, aby byo to uyteczne. Nawet, gdyby im si to udao, to zapewne i tak w kolejnym wydaniu bd chcieli czego wicej. Tak wic lepiej bdmy przygotowani na aktualizowanie swoich przypadkw testowych w miar jak zmieniaj si wymagania. Przypumy, na przykad, e chcielimy rozszerzy zakres funkcji konwertujcych liczby rzymskie. Czy pamitacie regu, ktra mwi, e adna litera nie moe by powtrzona wicej ni trzy razy? Ot Rzymianie chcieli uczyni wyjtek od tej reguy tak, aby mc reprezentowa warto 4000 stawiajc obok siebie cztery litery M. Jeli wprowadzimy t zmian, bdziemy mogli rozszerzy zakres liczb moliwych do przeksztacenia na liczb rzymsk z 1..3999 do 1..4999. Najpierw jednak musimy wprowadzi kilka zmian do przypadkw testowych. Przykad 15.6. Zmiana przypadkw testowych przy nowych wymaganiach (romantest71.py) Plik jest dostpny w katalogu py/roman/stage7/ znajdujcym si w katalogu examples. Jeli jeszcze tego nie zrobilicie, cignijcie ten oraz inne przykady (http:// diveintopython.org/download/diveintopython-examples-5.4.zip) uywane w tej ksice. import roman71 import unittest class KnownValues(unittest.TestCase): knownValues = ( (1, I), (2, II), (3, III), (4, IV), (5, V), (6, VI), (7, VII), (8, VIII), (9, IX), (10, X), (50, L), (100, C), (500, D), (1000, M), (31, XXXI), (148, CXLVIII), (294, CCXCIV), (312, CCCXII), (421, CDXXI), (528, DXXVIII), (621, DCXXI),

330

ROZDZIA 16. REFAKTORYZACJA (782, DCCLXXXII), (870, DCCCLXX), (941, CMXLI), (1043, MXLIII), (1110, MCX), (1226, MCCXXVI), (1301, MCCCI), (1485, MCDLXXXV), (1509, MDIX), (1607, MDCVII), (1754, MDCCLIV), (1832, MDCCCXXXII), (1993, MCMXCIII), (2074, MMLXXIV), (2152, MMCLII), (2212, MMCCXII), (2343, MMCCCXLIII), (2499, MMCDXCIX), (2574, MMDLXXIV), (2646, MMDCXLVI), (2723, MMDCCXXIII), (2892, MMDCCCXCII), (2975, MMCMLXXV), (3051, MMMLI), (3185, MMMCLXXXV), (3250, MMMCCL), (3313, MMMCCCXIII), (3408, MMMCDVIII), (3501, MMMDI), (3610, MMMDCX), (3743, MMMDCCXLIII), (3844, MMMDCCCXLIV), (3888, MMMDCCCLXXXVIII), (3940, MMMCMXL), (3999, MMMCMXCIX), (4000, MMMM), (4500, MMMMD), (4888, MMMMDCCCLXXXVIII), (4999, MMMMCMXCIX)) def testToRomanKnownValues(self): """toRoman should give known result with known input""" for integer, numeral in self.knownValues: result = roman71.toRoman(integer) self.assertEqual(numeral, result) def testFromRomanKnownValues(self): """fromRoman should give known result with known input""" for integer, numeral in self.knownValues: result = roman71.fromRoman(numeral)

16.2. OBSUGA ZMIENIAJCYCH SI WYMAGA self.assertEqual(integer, result)

331

class ToRomanBadInput(unittest.TestCase): def testTooLarge(self): """toRoman should fail with large input""" self.assertRaises(roman71.OutOfRangeError, roman71.toRoman, 5000) def testZero(self): """toRoman should fail with 0 input""" self.assertRaises(roman71.OutOfRangeError, roman71.toRoman, 0) def testNegative(self): """toRoman should fail with negative input""" self.assertRaises(roman71.OutOfRangeError, roman71.toRoman, -1) def testNonInteger(self): """toRoman should fail with non-integer input""" self.assertRaises(roman71.NotIntegerError, roman71.toRoman, 0.5) class FromRomanBadInput(unittest.TestCase): def testTooManyRepeatedNumerals(self): """fromRoman should fail with too many repeated numerals""" for s in (MMMMM, DD, CCCC, LL, XXXX, VV, IIII): self.assertRaises(roman71.InvalidRomanNumeralError, roman71.fromRoman, s) def testRepeatedPairs(self): """fromRoman should fail with repeated pairs of numerals""" for s in (CMCM, CDCD, XCXC, XLXL, IXIX, IVIV): self.assertRaises(roman71.InvalidRomanNumeralError, roman71.fromRoman, s) def testMalformedAntecedent(self): """fromRoman should fail with malformed antecedents""" for s in (IIMXCC, VX, DCM, CMM, IXIV, MCMC, XCX, IVI, LM, LD, LC): self.assertRaises(roman71.InvalidRomanNumeralError, roman71.fromRoman, s) def testBlank(self): """fromRoman should fail with blank string""" self.assertRaises(roman71.InvalidRomanNumeralError, roman71.fromRoman, "") class SanityCheck(unittest.TestCase): def testSanity(self): """fromRoman(toRoman(n))==n for all n""" for integer in range(1, 5000): numeral = roman71.toRoman(integer) result = roman71.fromRoman(numeral) self.assertEqual(integer, result) class CaseCheck(unittest.TestCase): def testToRomanCase(self):

#(2)

#(3)

#(4)

332

ROZDZIA 16. REFAKTORYZACJA """toRoman should always return uppercase""" for integer in range(1, 5000): numeral = roman71.toRoman(integer) self.assertEqual(numeral, numeral.upper()) def testFromRomanCase(self): """fromRoman should only accept uppercase input""" for integer in range(1, 5000): numeral = roman71.toRoman(integer) roman71.fromRoman(numeral.upper()) self.assertRaises(roman71.InvalidRomanNumeralError, roman71.fromRoman, numeral.lower())

if __name__ == "__main__": unittest.main() 1. Istniejce wartoci nie zmieniaj si (to wci rozsdne wartoci do przetestowania), jednak musimy doda kilka do poszerzonego zakresu. Powyej dodaem 4000 (najkrtszy napis), 4500 (drugi najkrtszy), 4888 (najduszy) oraz 4999 (najwikszy co do wartoci). 2. Zmienia si denicja duych danych wejciowych. Ten test mia nie przechodzi dla wartoci 4000 i zgasza w takiej sytuacji bd; teraz wartoci z przedziau 4000-4999 s poprawne, a jako pierwsz niepoprawn warto naley przyj 5000. 3. Zmienia si denicja zbyt wielu powtrzonych cyfr rzymskich. Ten test wywoywa fromRoman z wartoci MMMM i spodziewa si bdu. Obecnie MMMM jest poprawn liczb rzymsk, a wic naley zmieni niepoprawn warto na MMMMM. 4. Testy prostego sprawdzenia i sprawdzenia wielkoci liter iteruj po wartociach z przedziau od 1 do 3999. Ze wzgldu na poszerzenie tego przedziau rozszerzamy te ptle w testach tak, aby uwzgldniay wartoci do 4999. Teraz przypadki testowe odzwierciedlaj ju nowe wymagania, jednak nie uwzgldnia ich jeszcze kod, a wic mona si spodziewa, e pewne testy nie przejd: Przykad 15.7. Wyjcie programu romantest71.py testujcego roman71.py fromRoman should only accept uppercase input ... ERROR toRoman should always return uppercase ... ERROR fromRoman should fail with blank string ... ok fromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ERROR toRoman should give known result with known input ... ERROR fromRoman(toRoman(n))==n for all n ... ERROR toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok #(1)

#(2) #(3) #(4)

16.2. OBSUGA ZMIENIAJCYCH SI WYMAGA

333

1. Test sprawdzajcy wielko liter nie przechodzi, poniewa ptla uwzgldnia wartoci od 1 do 4999, natomiast toRoman akceptuje wartoci z przedziau od 1 do 3999. Jak tylko licznik ptli osignie warto 4000, test nie przechodzi. 2. Test poprawnych wartoci uywajcy toRoman nie przechodzi dla napisu MMMM, poniewa toRoman wci sdzi, e jest to warto niepoprawna. 3. Test poprawnych wartoci uywajcy toRoman nie przechodzi dla wartoci 4000, poniewa toRoman wci sdzi, e jest to warto spoza zakresu. 4. Test poprawnoci rwnie nie przechodzi dla wartoci 4000, poniewa toRoman wci sdzi, e jest to warto spoza zakresu. ====================================================================== ERROR: fromRoman should only accept uppercase input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage7\romantest71.py", line 161, in testFromRomanCase numeral = roman71.toRoman(integer) File "roman71.py", line 28, in toRoman raise OutOfRangeError, "number out of range (must be 1..3999)" OutOfRangeError: number out of range (must be 1..3999) ====================================================================== ERROR: toRoman should always return uppercase ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage7\romantest71.py", line 155, in testToRomanCase numeral = roman71.toRoman(integer) File "roman71.py", line 28, in toRoman raise OutOfRangeError, "number out of range (must be 1..3999)" OutOfRangeError: number out of range (must be 1..3999) ====================================================================== ERROR: fromRoman should give known result with known input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage7\romantest71.py", line 102, in testFromRomanKnownValues result = roman71.fromRoman(numeral) File "roman71.py", line 47, in fromRoman raise InvalidRomanNumeralError, Invalid Roman numeral: %s % s InvalidRomanNumeralError: Invalid Roman numeral: MMMM ====================================================================== ERROR: toRoman should give known result with known input ---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage7\romantest71.py", line 96, in testToRomanKnownValues result = roman71.toRoman(integer) File "roman71.py", line 28, in toRoman raise OutOfRangeError, "number out of range (must be 1..3999)" OutOfRangeError: number out of range (must be 1..3999) ====================================================================== ERROR: fromRoman(toRoman(n))==n for all n

334

ROZDZIA 16. REFAKTORYZACJA

---------------------------------------------------------------------Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage7\romantest71.py", line 147, in testSanity numeral = roman71.toRoman(integer) File "roman71.py", line 28, in toRoman raise OutOfRangeError, "number out of range (must be 1..3999)" OutOfRangeError: number out of range (must be 1..3999) ---------------------------------------------------------------------Ran 13 tests in 2.213s FAILED (errors=5) Kiedy ju mamy przypadki testowe, ktre ze wzgldu na nowe wymagania przestay przechodzi, moemy myle o poprawieniu kodu tak, aby by zgodny z testami (kiedy zaczyna si pisa testy jednostkowe, naley si przyzwyczai do jednej rzeczy: testowany kod nigdy nie wyprzedza przypadkw testowych. Zdarza si, e kod nie nada, co oznacza, e wci jest co do zrobienia, przy czym jak tylko kod dogoni testy, zadanie jest ju wykonane). Przykad 15.8. Implementacja nowych wymaga (roman72.py) Plik jest umieszczony w katalogu py/roman/stage7/ znajdujcym si w katalogu examples. """Convert to and from Roman numerals""" import re #Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Define digit mapping romanNumeralMap = ((M, (CM, (D, (CD, (C, (XC, (L, (XL, (X, (IX, (V, (IV, (I,

1000), 900), 500), 400), 100), 90), 50), 40), 10), 9), 5), 4), 1))

def toRoman(n): """convert integer to Roman numeral""" if not (0 < n < 5000): raise OutOfRangeError, "number out of range (must be 1..4999)"

16.2. OBSUGA ZMIENIAJCYCH SI WYMAGA if int(n) <> n: raise NotIntegerError, "non-integers can not be converted" result = "" for numeral, integer in romanNumeralMap: while n >= integer: result += numeral n -= integer return result

335

#Define pattern to detect valid Roman numerals romanNumeralPattern = ^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$ def fromRoman(s): """convert Roman numeral to integer""" if not s: raise InvalidRomanNumeralError, Input can not be blank if not re.search(romanNumeralPattern, s): raise InvalidRomanNumeralError, Invalid Roman numeral: %s % s result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result 1. toRoman wymaga jednej maej zmiany w sprawdzeniu zakresu. Tam, gdzie byo sprawdzenie 0 < n < 4000, powinno by 0 < n < 5000. Naley te zmieni tre komunikatu bdu, tak, aby odzwierciedla on nowy, akceptowalny zakres wartoci (1..4999 zamiast 1..3999). Nie trzeba dokonywa adnych innych zmian w kodzie funkcji, ktry ju obsuguje nowe wymaganie. (Kod dodaje M dla kadego penego tysica; gdy na wejciu jest warto 4000, otrzymamy liczb rzymsk MMMM. Jedynym powodem tego, e funkcja nie dziaaa tak wczeniej byo jej jawne zakoczenie przy wartoci przekraczajcej dopuszczalny zakres. 2. Nie trzeba w ogle zmienia fromRoman. Jedyna wymagana zmiana dotyczy wzorca romanNumeralPattern; po bliszym przyjrzeniu si wida, e wystarczyo doda jeszcze jedno opcjonalne M w pierwszej czci wyraenia regularnego. Pozwoli to na dopasowanie maksymalnie czterech znakw M zamiast trzech, a wic uwzgldni wartoci rzymskie z przedziau do 4999 zamiast do 3999. Obecna implementacja fromRoman jest bardzo oglna: wyszukuje ona powtarzajce si znaki w zapisie rzymskim, a nastpnie sumuje ich odpowiednie wartoci, nie przejmujc si liczb ich wystpie. Funkcja ta nie obsugiwaa wczeniej napisu MMMM wycznie dlatego, e napis taki nie zostaby wczeniej dopasowany do wyraenia regularnego. Moecie by odrobin sceptyczni wobec stwierdzenia, e te dwie mae zmiany to wszystko, czego potrzebujemy. Nie wierzcie mi na sowo, po prostu to sprawdcie: Przykad 15.9. Wyjcie programu romantest72.py testujcego roman72.py

#(2)

336

ROZDZIA 16. REFAKTORYZACJA

fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with blank string ... ok fromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ---------------------------------------------------------------------Ran 13 tests in 3.685s OK 1. Wszytskie testy przechodz. Koczymy kodowanie. Pene testowanie jednostkowe oznacza, e nigdy nie trzeba polega na sowach programisty mwicego: Zaufaj mi.

#(

16.3. REFAKTORYZACJA

337

16.3

Refaktoryzacja

Najcenniejsz rzecz, jak daje testowanie jednostkowe, nie jest uczucie, jakiego dowiadczamy, kiedy wszystkie testy przechodz, ani nawet uczucie w chwili, gdy kto obwinia nas o popsucie swojego kodu, a my jestemy w stanie udowodni, e to nie nasza wina. Najcenniejsz rzecz w testowaniu jednostkowym jest to, e daje nam ono nieskrpowan wolno podczas refaktoryzacji. Refaktoryzacja to proces, ktry polega na tym, e bierze si dziaajcy kod i zmienia go tak, aby dziaa jeszcze lepiej. Zwykle lepiej znaczy szybciej, cho moe to rwnie znaczy przy mniejszym zuyciu pamici, przy mniejszym zuyciu przestrzeni dyskowej czy nawet bardziej elegancko. Czymkolwiek refaktoryzacja jest dla was, dla waszego projektu czy waszego rodowiska pracy, suy ona utrzymaniu programu w dobrym zdrowiu przez dugi czas. W naszym przypadku lepiej znaczy szybciej. Dokadnie rzecz ujmujc, funkcja fromRoman jest wolniejsza ni musiaaby by ze wzgldu na due, brzydkie wyraenie regularne, ktrego uywamy do zwerykowania, czy napis stanowi poprawn reprezentacj liczby w notacji rzymskiej. Prawdopodobnie nie opaca si cakowicie eliminowa tego wyraenia (byoby to trudne i mogoby doprowadzi do powstania jeszcze wolniejszego kodu), jednak mona uzyska pewne przyspieszenie dziki temu, e wyraenie regularne zostanie wstpnie skompilowane. Przykad 15.10. Kompilacja wyraenia regularnego >>> import re >>> pattern = ^M?M?M?$ >>> re.search(pattern, M) <SRE_Match object at 01090490> >>> compiledPattern = re.compile(pattern) >>> compiledPattern <SRE_Pattern object at 00F06E28> >>> dir(compiledPattern) [findall, match, scanner, search, split, sub, subn] >>> compiledPattern.search(M) <SRE_Match object at 01104928> 1. To skadnia, ktr ju wczeniej widzielicie: re.search pobiera wyraenie regularne jako napis (pattern) oraz napis, do ktrego wyraenie bdzie dopasowywane (M). Jeli wyraenie zostanie dopasowane, funkcja zwrci obiekt match, ktry mona nastpnie odpyta, aby dowiedzie si, co zostao dopasowane i w jaki sposb. 2. To jest ju nowa skadnia: re.compile pobiera wyraenie regularne jako napis i zwraca obiekt pattern. Zauwamy, e nie przekazujemy napisu, do ktrego bdzie dopasowywane wyraenie. Kompilacja wyraenia regularnego nie ma nic wsplnego z dopasowywaniem wyraenia do konkretnego napisu (jak np. M); dotyczy ona wycznie samego wyraenia. 3. Obiekt pattern zwrcony przez funkcj re.compile posiada wiele poytecznie wygldajcych funkcji, midzy innymi kilka takich, ktre s dostpne bezporednio w module re (np. search czy sub). 4. Wywoujc funkcji search na obiekcie pattern z napisem M jako parametrem osigamy ten sam efekt, co wywoujc re.search z wyraeniem regularnym i

#(1) #(2)

#(3) #(4)

338

ROZDZIA 16. REFAKTORYZACJA napisem M jako parametrami. Z t rnic, e osigamy go o wiele, wiele szybciej. (W rzeczywistoci funkcja re.search kompiluje wyraenie regularne i na obiekcie bdcym wynikiem tej kompilacji wywouje metod search.)

Kompilacja wyrae regularnych. Jeli kiedykolwiek bdziecie potrzebowali uy wyraenia regularnego wicej ni raz, powinnicie najpierw skompilowa je do obiektu pattern, a nastpnie wywoywa bezporednio jego metody Przykad 15.11.Skompilowane wyraenie regularne w roman81.py Plik jest dostpny w katalogu in py/roman/stage8/ wewntrz katalogu examples. Jeli jeszcze tego nie zrobilicie, moecie pobra ten oraz inne przykady uywane w tej ksice std. # toRoman and rest of module omitted for clarity romanNumeralPattern = \ re.compile(^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$) (1) def fromRoman(s): """convert Roman numeral to integer""" if not s: raise InvalidRomanNumeralError, Input can not be blank if not romanNumeralPattern.search(s): raise InvalidRomanNumeralError, Invalid Roman numeral: %s % s result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result 1. Wyglda podobnie, cho w rzeczywistoci bardzo duo si zmienio. romanNumeralPattern nie jest ju napisem; to obiekt pattern, ktry zosta zwrcony przez re.compile. 2. Ta linia oznacza, e na obiekcie romanNumeralPattern mona bezporednio wywoywa metody. Bd one wykonane o wiele szybciej, ni np. podczas kadorazowego wywoania re.search. Tutaj wyraenie regularne zostao skompilowane dokadnie jeden raz i zapamitane pod nazw romanNumeralPattern w momencie pierwszego importu moduu; od tego momentu. ilekro bdzie wywoana metoda fromRoman, gotowe wyraenie bdzie dopasowywane do napisu wejciowego, bez adnych krokw porednich odbywajcych si niejawnie. Wobec tego o ile szybciej dziaa kod po skompilowaniu wyraenia regularnego? Sprawdcie sami: Przykad 15.12. Wyjcie programu romantest81.py testujcego roman81.py ............. ----------------------------------------------------------------------

(2)

#(1)

16.3. REFAKTORYZACJA Ran 13 tests in 3.385s OK

339 #(2) #(3)

1. Tutaj tylko maa uwaga: tym razem uruchomiem testy bez podawania opcji v, dlatego te zamiast penego napisu komentujcego dla kadego testu, ktry zakoczy si powodzeniem, zostaa wypisana kropka. (Gdyby test zakoczy si niepowodzeniem, zostaaby wypisana litera F, a w przypadku bdu litera E. Potencjalne problemy mona wci atwo zidentykowa, poniewa w razie niepowodze lub bdw wypisywana jest zawarto stosu.) 2. Uruchomienie 13 testw zajo 3.385 sekund w porwnaniu z 3.685 sekund, jakie zajy testy bez wczeniejszej kompilacji wyraenia regularnego. To poprawa w wysokoci 8%, a warto pamita, e przez wikszo czasu w testach jednostkowych wykonywane s take inne rzeczy. (Gdy przetestowaem same wyraenia regularne, niezalenie od innych testw, okazao si, e kompilacja wyraenia regularnego polepszya czas operacji wyszukiwania rednio o 54%.) Niele, jak na tak niewielk poprawk. 3. Och, gdybycie si jeszcze zastanawiali, prekompilacja wyraenia regularnego niczego nie zepsua, co wanie udowodnilimy. Jest jeszcze jedna optymalizacja wydajnoci, ktr chciaem wyprbowa. Nie powinno by niespodziank, e przy wysokiej zoonoci skadni wyrae regularnych istnieje wicej ni jeden sposb napisania tego samego wyraenia. W trakcie dyskusji nad tym rozdziaem, jaka odbya si na comp.lang.python kto zasugerowa, ebym dla powtarzajcych si opcjonalnych znakw sprbowa uy skadni {m, n}. Przykad 15.13. roman82.py Plik jest dostpny w katalogu in py/roman/stage8/ wewntrz katalogu examples. Jeli jeszcze tego nie zrobilicie, moecie pobra ten oraz inne przykady uywane w tej ksice std. # rest of program omitted for clarity #old version #romanNumeralPattern = \ # re.compile(^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$) #new version romanNumeralPattern = \ re.compile(^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$)

#(1)

1. Zastpilimy M?M?M?M? wyraeniem M{0,4}. Obydwa zapisy oznaczaj to samo: dopasuj od 0 do 4 znakw M. Podobnie C?C?C? zostao zastpione C{0,3} (dopasuj od 0 do 3 znakw C) i tak dalej dla X oraz I. Powyszy zapis wyraenia regularnego jest odrobin krtszy (cho nie bardziej czytelny). Pytanie brzmi: czy jest on szybszy? Przykad 15.14. Wyjcie programu romantest82.py testujcego roman82.py

340

ROZDZIA 16. REFAKTORYZACJA

............. ---------------------------------------------------------------------Ran 13 tests in 3.315s #(1) OK 1. Przy tej formie wyraenia regularnego testy jednostkowe dziaay w sumie 2% szybciej. Nie brzmi to moe zbyt ekscytujco, dlatego przypomn, e wywoania funkcji wyszukujcej stanowi niewielk cz wszystkich testw; przez wikszo czasu testy robi co innego. (Gdy niezalenie od innych testw przetestowaem wycznie wydajno wyraenia regularnego, okazao si, e jest ona o 11% wiksza przy nowej skadni). Dziki prekompilacji wyraenia regularnego i zmianie jego skadni udao si poprawi wydajno samego wyraenia o ponad 60%, a wszystkich testw cznie o ponad 10%. 2. Znacznie waniejsze od samego wzrostu wydajnoci jest to, e modu wci doskonale dziaa. To jest wanie wolno, o ktrej wspominaem ju wczeniej: wolno poprawiania, zmieniania i przepisywania dowolnego fragmentu kodu i moliwo sprawdzenia, e zmiany te w midzyczasie wszystkiego nie popsuy. Nie chodzi tu o poprawki dla samych poprawek; mielimy bardzo konkretny cel (przyspieszy toRoman) i bylimy w stanie go zrealizowa bez zbytnich waha i troski o to, czy nie wprowadzilimy do kodu nowych bdw. Chc zrobi jeszcze jedn, ostatni zmian i obiecuj, e na niej skocz refaktoryzacj i dam ju temu moduowi spokj. Jak wielokrotnie widzielicie, wyraenia regularne szybko staj si bardzo nieporzdne i mocno trac na swej czytelnoci. Naprawd nie chciabym dosta tego moduu do utrzymania za sze miesicy. Oczywicie, testy przechodz, wic mam pewno, e kod dziaa, jednak jeli nie jestem cakowicie pewien, w jaki sposb on dziaa, to bdzie mi trudno dodawa do niego nowe wymagania, poprawia bdy czy w inny sposb go utrzymywa. W podrozdziale idzielicie, e Python umoliwia dokadne udokumentowanie logiki kodu. Przykad 15.15. roman83.py Plik jest dostpny w katalogu in py/roman/stage8/ wewntrz katalogu examples. Jeli jeszcze tego nie zrobilicie, moecie pobra ten oraz inne przykady uywane w tej ksice std. # rest of program omitted for clarity #old version #romanNumeralPattern = \ # re.compile(^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$) #new version romanNumeralPattern = re.compile( ^ # beginning of string M{0,4} # thousands - 0 to 4 Ms (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 Cs), # or 500-800 (D, followed by 0 to 3 Cs) (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 Xs), # or 50-80 (L, followed by 0 to 3 Xs) #(2)

16.3. REFAKTORYZACJA (IX|IV|V?I{0,3}) $ , re.VERBOSE)

341 # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 Is), # or 5-8 (V, followed by 0 to 3 Is) # end of string #(1)

1. Funkcja re.compile moe przyjmowa drugi (opcjonalny) argument, bdcy zbiorem znacznikw kontrolujcych wiele aspektw skompilowanego wyraenia regularnego. Powyej wyspecykowalimy znacznik re.VERBOSE, ktry podpowiada kompilatorowi jzyka Python, e wewntrz wyraenia regularnego znajduj si komentarze. Komentarze te wraz z biaymi znakami nie stanowi czci wyraenia regularnego; funkcja re.compile nie bierze ich pod uwag podczas kompilacji. Dziki temu zapisowi, cho samo wyraenie jest identyczne jak poprzednio, jest ono niewtpliwie znacznie bardziej czytelne. Przykad 15.16. Wyjcie z programu romantest83.py testujcego roman83.py ............. ---------------------------------------------------------------------Ran 13 tests in 3.315s OK 1. Nowa, rozwleka wersja wyraenia regularnego dziaa dokadnie tak samo, jak wersja poprzednia. Rzeczywicie, skompilowane obiekty wyrae regularnych bd identyczne, poniewa re.compile wyrzuca z wyraenia dodatkowe znaki, ktre umiecilimy tam w charakterze komentarza. 2. Nowa, rozwleka wersja wyraenia regularnego przechodzi wszystkie testy, tak jak wersja poprzednia. Nie zmienio si nic oprcz tego, e programista, ktry wrci do kodu po szeciu miesicach bdzie mia moliwo zrozumienia, w jaki sposb dziaa wyraenie regularne.

#(1) #(2)

342

ROZDZIA 16. REFAKTORYZACJA

16.4

Postscript

Sprytny czytelnik po przeczytaniu poprzedniego podrozdziau byby w stanie jeszcze bardziej polepszy kod programu. Najwikszym bowiem problemem (i dziur (?) wydajnociow) programu w obecnym ksztacie jest wyraenie regularne, wymagane ze wzgldu na to, e nie ma innego sensownego sposobu werykacji poprawnoci liczby w zapisie rzymskim. Tych liczb jest jednak tylko 5000; dlaczego by wic nie zbudowa tablicy przeszukiwania tylko raz, a pniej po prostu z niej korzysta? Ten pomys wyda si jeszcze lepszy, gdy zdamy sobie spraw, e w ogle nie musimy korzysta z wyrae regularnych. Skoro mona zbudowa tablic przeszukiwa suc do konwersji wartoci liczbowych w ich rzymsk reprezentacj, to mona rwnie zbudowa tablic odwrotn do przeksztacania liczb rzymskich w ich warto liczbow. Najlepsze za jest to, e w sprytny czytelnik miaby ju do swojej dyspozycji peen zestaw testw jednostkowych. Cho zmodykowaby poow kodu w module, to testy pozostayby takie same, a wic mgby on udowodni, e kod po zmianach dziaa tak samo, jak wczeniej. Przykad 15.17. roman9.py Plik jest dostpny w katalogu in py/roman/stage9/ wewntrz katalogu examples. Jeli jeszcze tego nie zrobilicie, moecie pobra ten oraz inne przykady uywane w tej ksice std. #Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Roman numerals must be less than 5000 MAX_ROMAN_NUMERAL = 4999 #Define digit mapping romanNumeralMap = ((M, (CM, (D, (CD, (C, (XC, (L, (XL, (X, (IX, (V, (IV, (I,

1000), 900), 500), 400), 100), 90), 50), 40), 10), 9), 5), 4), 1))

#Create tables for fast conversion of roman numerals. #See fillLookupTables() below. toRomanTable = [ None ] # Skip an index since Roman numerals have no zero fromRomanTable = {}

16.4. POSTSCRIPT

343

def toRoman(n): """convert integer to Roman numeral""" if not (0 < n <= MAX_ROMAN_NUMERAL): raise OutOfRangeError, "number out of range (must be 1..%s)" % MAX_ROMAN_NUMERAL if int(n) <> n: raise NotIntegerError, "non-integers can not be converted" return toRomanTable[n] def fromRoman(s): """convert Roman numeral to integer""" if not s: raise InvalidRomanNumeralError, "Input can not be blank" if not fromRomanTable.has_key(s): raise InvalidRomanNumeralError, "Invalid Roman numeral: %s" % s return fromRomanTable[s] def toRomanDynamic(n): """convert integer to Roman numeral using dynamic programming""" result = "" for numeral, integer in romanNumeralMap: if n >= integer: result = numeral n -= integer break if n > 0: result += toRomanTable[n] return result def fillLookupTables(): """compute all the possible roman numerals""" #Save the values in two global tables to convert to and from integers. for integer in range(1, MAX_ROMAN_NUMERAL + 1): romanNumber = toRomanDynamic(integer) toRomanTable.append(romanNumber) fromRomanTable[romanNumber] = integer fillLookupTables() A jak szybki jest taki kod? Przykad 15.18. Wyjcie programu romantest9.py testujcego roman9.py ............. ---------------------------------------------------------------------Ran 13 tests in 0.791s OK Pamitajmy, e najlepszym wynikiem, jaki udao nam si do tej pory uzyska, by czas 3.315 sekund dla 13 testw. Oczywicie, to porwnanie nie jest zbyt uczciwe, poniewa w tej wersji modu bdzie si duej importowa (bd generowane tablice

344

ROZDZIA 16. REFAKTORYZACJA

przeszukiwa). Jednak ze wzgldu na to, ze importowanie moduu odbywa si jednokrotnie, czas, jaki ono zajmuje, mona uzna za pomijalny. Mora z tej historii? Prostota jest cnot. Szczeglnie wtedy, gdy chodzi o wyraenia regularne. A testy jednostkowe daj nam pewno i odwag do refaktoryzacji w duej skali... nawet, jeli to nie my pisalimy oryginalny kod.

16.5. PODSUMOWANIE

345

16.5

Podsumowanie

Testowanie jednostkowe to bardzo silna koncepcja, ktra, jeli zostanie poprawnie wdroona, moe zarwno zredukowa koszty utrzymywania, jak i zwikszy elastyczno kadego trwajcego dugo projektu informatycznego. Wane jest, aby zrozumie, e testowanie jednostkowe nie jest panaceum, Magicznym Rozwizywaczem Problemw czy srebrn kul. Napisanie dobrych przypadkw testowych jest trudne, a utrzymywanie ich wymaga ogromnej dyscypliny (szczeglnie w sytuacjach, gdy klienci daj natychmiastowych poprawek krytycznych bdw). Testowanie jednostkowe nie zastpuje rwnie innych form testowania, takich jak testy funkcjonalne, testy integracyjne czy testy akceptacyjne. Jest jednak wykonalne, dziaa, a kiedy ju zobaczycie je w dziaaniu, bdziecie si zastanawia, jak w ogle moglicie bez nich y. W tym rozdziale poruszylimy wiele spraw; cz z nich nie dotyczya wycznie jzyka Python. Biblioteki do testowania jednostkowego istniej dla wielu rnych jzykw, a ich uywanie wymaga jedynie zrozumienia kilku podstawowych koncepcji: projektowania przypadkw testowych, ktre s specyczne, zautomatyzowane i niezalene pisania przypadkw testowych przed napisaniem testowanego kodu pisania przypadkw testowych, ktre uwzgldniaj poprawne dane wejciowe i spodziewaj si poprawnych wynikw pisania przypadkw testowych, ktre uwzgldniaj niepoprawne dane wejciowe i spodziewaj si odpowiednich niepowodze pisania i aktualizowania przypadkw testowych przedstawiajcych bdy lub odzwierciedlajcych nowe wymagania bezwzgldnej refaktoryzacji w celu poprawy wydajnoci, skalowalnoci, czytelnoci, utrzymywalnoci oraz kadej innej -noci, ktra nie jest wystarczajca Po przeczytaniu tego rozdziau nie powinnicie mie rwnie adnych problemw z wykonywaniem zada specycznych dla jzyka Python: tworzeniem klas pochodnych po unittest.TestCase i pisaniem metod bdcych szczeglnymi przypadkami testowymi uywaniem assertEqual do sprawdzania, czy funkcja zwrcia spodziewan warto uywaniem assertRaises do sprawdzania, czy funkcja rzuca spodziewany wyjtek wywoywaniem unittest.main() w klauzuli if wszystkich przypadkw testowych na raz name w celu uruchomienia

uruchamianiem zestawu testw jednostkowych zarwno w trybie normalnym, jak i rozwlekym

346

ROZDZIA 16. REFAKTORYZACJA

16.6

Programowanie funkcyjne

Nurkujemy
W rozdziale 13 (Testowanie) poznalicie lozo testowania jednostkowego. Rozdzia 14 (Testowanie 2) pozwoli wam zaimplementowa podstawowe testy jednostkowe w jzyku Python. Rozdzia 15 (Refaktoryzacja) uwiadomi wam, e dziki testom jednostkowym refaktoryzacja na wielk skal staje si znacznie prostsza. W tym za rozdziale, cho wci bdziemy bazowa na wczeniejszych, przykadowych programach, skupimy si bardziej na zaawansowanych technikach stosowanych w jzyku Python, ni na samym testowaniu. Poniej przedstawiony jest peny kod programu bdcego tanim i prostym sposobem uruchamiania testw regresyjnych. Pobiera on testy jednostkowe, jakie zostay napisane dla poszczeglnych moduw, zbiera je do jednego, wielkiego zestawu testowego i uruchamia je wszystkie jako cao. Obecnie, podczas pisania tej ksiki, skrypt ten suy mi jako cz procesu budowania; napisaem testy jednostkowe dla wielu przykadowych programw (nie tylko dla roman.py przedstawionego w rozdziale 13, Testowanie), a pierwsz rzecz jak robi skrypt do automatycznego budowania jest uruchomienie tego wanie programu, dziki czemu mog si upewni, e wszystkie moje przykady wci dziaaj. Jeli zakoczy si on niepowodzeniem, wwczas automatyczne budowanie zostaje natychmiast przerwane. Nie chc publikowa niedziaajcych przykadw, podobnie jak wy nie chcecie pobiera ich z sieci, a pniej dugo siedzie, drapa si po gowie i zastanawia, dlaczego nie dziaaj. Przykad 16.1. regression.py Jeli jeszcze tego nie zrobilicie, moecie pobra ten oraz inne przykady uywane w tej ksice. """Regression testing framework This module will search for scripts in the same directory named XYZtest.py. Each such script should be a test suite that tests a module through PyUnit. (As of Python 2.1, PyUnit is included in the standard library as "unittest".) This script will aggregate all found test suites into one big test suite and run them all at once. """ import sys, os, re, unittest def regressionTest(): path = os.path.abspath(os.path.dirname(sys.argv[0])) files = os.listdir(path) test = re.compile("test\.py$", re.IGNORECASE) files = filter(test.search, files) filenameToModuleName = lambda f: os.path.splitext(f)[0] moduleNames = map(filenameToModuleName, files) modules = map(__import__, moduleNames) load = unittest.defaultTestLoader.loadTestsFromModule return unittest.TestSuite(map(load, modules)) if __name__ == "__main__":

16.6. NURKUJEMY unittest.main(defaultTest="regressionTest")

347

Uruchomienie programu w tym samym katalogu, w ktrym znajduj si pozostae przykadowe skrypty uywane w tej ksice, spowoduje wyszukanie wszystkich testw jednostkowych o nazwie moduletest.py, uruchomienie ich wszystkich jako jeden test, a nastpnie stwierdzenie, czy jako cao przeszy, czy nie. Przykad 16.2. Przykadowe wyjcie z programu regression.py [you@localhost py]$ python regression.py -v help should fail with no object ... ok #(1) help should return known result for apihelper ... ok help should honor collapse argument ... ok help should honor spacing argument ... ok buildConnectionString should fail with list input ... ok #(2) buildConnectionString should fail with string input ... ok buildConnectionString should fail with tuple input ... ok buildConnectionString handles empty dictionary ... ok buildConnectionString returns known result with known input ... ok fromRoman should only accept uppercase input ... ok #(3) toRoman should always return uppercase ... ok fromRoman should fail with blank string ... ok fromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok kgp a ref test ... ok kgp b ref test ... ok kgp c ref test ... ok kgp d ref test ... ok kgp e ref test ... ok kgp f ref test ... ok kgp g ref test ... ok ---------------------------------------------------------------------Ran 29 tests in 2.799s OK 1. Pierwszych 5 testw pochodzi z apihelpertest.py, ktry testuje przykadowy skrypt z rozdziau 4 (Potga introspekcji). 2. Kolejne 5 testw pochodzi z odbchelpertest.py, ktry testuje przykadowy skrypt z rozdziau 2 (Pierwszy program)

348

ROZDZIA 16. REFAKTORYZACJA

3. Pozostae testy pochodz z romantest.py, zestawu testw, ktry zgbialimy w rozdziale 13, (Testowanie).

16.7. ZNAJDOWANIE CIEKI

349

16.7

Znajdowanie cieki

Czasami, kiedy uruchomimy skrypt jzyka Python z linii polece, chcielibymy wiedzie, w jakim miejscu na dysku ten skrypt si znajduje. To jeden z tych brzydkich, maych trikw, ktre ciko wymyli samemu (o ile to w ogle moliwe), ale za to atwo zapamita, jeli ju si to zobaczy. Kluczem do tego problemu jest sys.argv. Jak widzielicie w rozdziale 9 (Przetwarzanie XML), jest to lista przechowujca argumenty linii polece. Dodatkowo, lista ta przechowuje rwnie nazw uruchamianego programu, dokadnie tak, jaka zostaa przekazana w linii polece, a na jej podstawie mona ju ustali pooenie programu na dysku. Przykad 16.3. fullpath.py Jeli jeszcze tego nie zrobilicie, moecie pobra ten oraz inne przykady uywane w tej ksice. import sys, os print sys.argv[0] =, sys.argv[0] #(1) pathname = os.path.dirname(sys.argv[0]) #(2) print path =, pathname print full path =, os.path.abspath(pathname) #(3) 1. Niezalenie od tego, w jaki sposb uruchomicie skrypt, sys.argv[0] bdzie zawsze zawiera nazw skryptu, w dokadnie takiej postaci, w jakiej pojawia si ona w linii polece. Jak wkrtce zobaczymy, nazwa moe, cho nie musi, zawiera informacj o penej ciece. 2. os.path.dirname pobiera napis zawierajcy nazw pliku i zwraca fragment tego napisu zawierajcy ciek do katalogu, w ktrym plik si znajduje. Jeli podana nazwa pliku nie zawiera informacji o ciece, wywoanie os.path.dirname zwrci napis pusty. 3. Kluczow funkcj jest os.path.abspath. Pobiera ona nazw ciekow, ktra moe by czciowa (wzgldna) lub pusta, i zwraca pen kwalikowan nazw ciekow. Funkcja os.path.abspath wymaga pewnych wyjanie. Jest ona bardzo elastyczna i moe przyjmowa nazwy ciekowe w dowolnej postaci. Przykad 16.4. Dalsze wyjanienia dotyczce os.path.abspath >>> import os >>> os.getcwd() #(1) /home/you >>> os.path.abspath() #(2) /home/you >>> os.path.abspath(.ssh) #(3) /home/you/.ssh >>> os.path.abspath(/home/you/.ssh) #(4) /home/you/.ssh >>> os.path.abspath(.ssh/../foo/) #(5) /home/you/foo

350

ROZDZIA 16. REFAKTORYZACJA

1. os.getcwd() zwraca biecy katalog roboczy. 2. Wywoanie os.path.abspath z napisem pustym zwraca biecy katalog roboczy, tak samo jak os.getcwd(). 3. Wywoanie os.path.abspath z czciow nazw ciekow powoduje skonstruowanie penej kwalikowanej nazwy ciekowej w oparciu o biecy katalog roboczy. 4. Wywoanie os.path.abspath z pen nazw ciekow zwraca t nazw. 5. os.path.abspath normalizuje nazw ciekow, ktr zwraca. Zwrcie uwag, e powyszy przykad bdzie dziaa nawet wwczas, jeli katalog foo nie istnieje. Funkcja os.path.abspath nigdy nie sprawdza istnienia elementw skadowych cieki na dysku; dokonuje ona jedynie manipulacji na napisach. Katalogi i pliki, ktrych nazwy s przekazywane do os.path.abspath nie musz istnie w systemie plikw.

os.path.abspath nie tylko konstruuje pene nazwy ciekowe (cieki bezwzgldne), lecz rwnie je normalizuje. Oznacza to, e wywoujc os.path.abspath(bin/../local/bin) z katalogu /usr/ otrzyma si napis /usr/local/bin. Normalizacja oznacza tutaj najwiksze moliwe uproszczenie cieki. W celu znormalizowania nazwy ciekowej bez przeksztacania jej w pen ciek nalezy uy funkcji os.path.normpath. Przykad 16.5. Przykadowe wyjcie z programu fullpath.py [you@localhost py]$ python /home/you/diveintopython/common/py/fullpath.py #(1) sys.argv[0] = /home/you/diveintopython/common/py/fullpath.py path = /home/you/diveintopython/common/py full path = /home/you/diveintopython/common/py [you@localhost diveintopython]$ python common/py/fullpath.py #(2) sys.argv[0] = common/py/fullpath.py path = common/py full path = /home/you/diveintopython/common/py [you@localhost diveintopython]$ cd common/py [you@localhost py]$ python fullpath.py #(3) sys.argv[0] = fullpath.py path = full path = /home/you/diveintopython/common/py 1. W pierwszym przypadku sys.argv[0] zawiera pen ciek do skryptu. Mona uy funkcji os.path.dirname w celu usunicia nazwy skryptu, otrzymujc pen ciek do katalogu, w ktrym znajduje si skrypt. Funkcja os.path.abspath zwraca dokadnie to samo, co otrzymaa na wejciu. 2. Jeli skrypt jest uruchomiony przy uyciu cieki wzgldnej, sys.argv[0] w dalszym cigu zwraca dokadnie to, co pojawio si w linii polece. Wywoanie

16.7. ZNAJDOWANIE CIEKI

351

os.path.dirname zwrci czciow nazw ciekow (ciek wzgldn wzgldem biecego katalogu), natomiast os.path.abspath z czciowej nazwy ciekowej skonstruuje pen ciek (ciek bezwzgldn). 3. Jeli skrypt jest uruchomiony z biecego katalogu bez podawania jakiejkolwiek cieki, os.dir.pathname zwrci po prostu pusty napis. Podajc pusty napis do os.path.abspath otrzymamy ciek do biecego katalogu, a tego dokadnie oczekujemy, poniewa z tego wanie katalogu uruchamialimy skrypt. Podobnie jak inne funkcje w moduach os oraz os.path, os.path.abspath jest funkcj dziaajc na wszystkich platformach. Jeli uywacie systemu operacyjnego Windows (ktry w charakterze separatorw elementw cieki uywa odwrconych ukonikw) lub MacOS (ktry uywa dwukropkw) , wyjcie waszych programw bdzie si odrobin rnio od przedstawionego w tej ksice, ale przykady bd nadal dziaay. I o to wanie chodzi w module os. Dodatek. Jeden z czytelnikw by rozczarowany zaprezentowanym wyej rozwizaniem, poniewa chcia uruchomi wszystkie testy jednostkowe znajdujce si w biecym katalogu, niekoniecznie za w katalogu, w ktrym umieszczony jest program regression.py. Zasugerowa on nastpujce podejcie: Przykad 16.6. Uruchomienie skryptu z biecego katalogu import sys, os, re, unittest def regressionTest(): path = os.getcwd() #(1) sys.path.append(path) #(2) files = os.listdir(path) #(3) 1. Zamiast ustalania cieki z testami na katalog, w ktrym znajduje si obecnie wykonywany skrypt, ustalamy j na biecy katalog roboczy. Bdzie to ten katalog, w ktrym bylimy w momencie uruchomienia skryptu, a wic niekoniecznie oznacza katalog, w ktrym znajduje si skrypt. (Jeli nie chwytasz tego od razu, przeczytaj to zdanie powoli kilka razy). 2. Dodajemy t ciek do cieki wyszukiwania bibliotek jzyka Python, dziki czemu w momencie dynamicznego importowania moduw z testami jednostkowymi Python bdzie mg je odnale. Nie trzeba byo tego robi w sytuacji, w ktrej ciek z testami bya cieka do uruchomionego skryptu, poniewa Python zawsze przeszukuje katalog, w ktrym znajduje si uruchomiony skrypt. 3. Pozostaa cz funkcji pozostaje bez zmian. Dziki tej technice moliwe jest powtrne uycie skryptu regression.py w wielu projektach. Wystarczy umieci skrypt w pewnym katalogu wsplnym dla wielu projektw, a nastpnie, przed jego uruchomieniem, zmieni katalog na katalog projektu, ktrego testy chcemy uruchomi. Po uruchomieniu skryptu zostan odnalezione i uruchomione wszystkie testy projektu, znajdujce si w katalogu projektu, nie za testy znajdujce si w katalogu wsplnym dla projektw, w ktrym umieszczony zosta skrypt.

352

ROZDZIA 16. REFAKTORYZACJA

16.8

Filtrowanie listy

Jeszcze o ltrowaniu list


Zapoznalicie si ju z ltrowaniem list przy uyciu wyrae listowych (ang. list comprehension). Istnieje jeszcze jeden sposb na osignicie tego celu, przez wiele osb uznawany za bardziej wyrazisty. W jzyku Python istnieje wbudowana funkcja ltrujca (filter) przyjmujca dwa parametry, funkcj oraz list, i zwracajca list 1 . Funkcja przekazana jako pierwszy argument do funkcji filter musi przyjmowa jeden argument, natomiast lista zwrcona przez funkcj ltrujc bdzie zawieraa te elementy z listy przekazanej do funkcji ltujcej, dla ktrych funkcja przekazana w pierwszym argumencie zwrcia warto true. Czy wszystko jasne? To nie takie trudne, jak si wydaje. Przykad 16.7. Wprowadzenie do funkcji filter >>> ... ... >>> >>> [1, >>> [1, >>> >>> ... ... ... >>> [1, def odd(n): return n % 2 #(1)

li = [1, 2, 3, 5, 9, 10, 256, -3] filter(odd, li) #(2) 3, 5, 9, -3] [e for e in li if odd(e)] #(3) 3, 5, 9, -3] filteredList = [] for n in li: #(4) if odd(n): filteredList.append(n) filteredList 3, 5, 9, -3]

1. funkcja odd zwraca True jeli n jest nieparzyste a False w przeciwnym przypadku; uywa do tego wbudowanej funkcji modulo (%). 2. funkcja filter przyjmuje dwa argumenty: funkcj odd oraz list li. filter iteruje po licie i dla kadego jej elementu wywouje odd. Jeli odd zwrci warto true (pamitajcie, e kada niezerowa warto ma w jzyku Python logiczn warto true), wwczas element jest dodawany do listy wynikowej, w przeciwnym przypadku jest on pomijany. W rezultacie otrzymujemy list nieparzystych elementw z listy oryginalnej, w takiej samej kolejnoci, w jakiej elementy pojawiay si na oryginalnej licie. 3. Jak widzielimy w podrozdziale 4.5 Filtrowanie listy, ten sam cel mona osign uywajc wyrae listowych.
z technicznego punktu widzenia, drugim argumentem funkcji filter moe by dowolna sekwencja, wczajc w to listy, krotki oraz klasy, ktre funkcjonuj jak listy, poniewa maj zdeniowan metod specjaln getitem . Jeli to moliwe, funkcja filter zwraca ten sam typ danych, ktry otrzymaa, a wic wynikiem ltrowania listy bdzie lista, a wynikiem ltrowania krotki bdzie krotka. Uwagi te dotycz rwnie funkcji map, o ktrej bdzie mowa w nastpnym podrozdziale.
1 Patrzc

16.8. FILTROWANIE LISTY

353

4. Mona rwnie uy ptli. W zalenoci od tego, jakim dowiadczeniem programistycznym dysponujecie, ten sposb moe si wam wydawa bardziej bezporedni, jednak uycie funkcji takich jak filter jest znacznie bardziej wyraziste. Nie tylko jest prostsze w zapisie, jest rwnie atwiejsze w czytaniu. Czytanie kodu ptli przypomina patrzenie na obraz ze zbyt maej odlegoci; widzi si detale, jednak potrzeba kilku chwil, by mc odsun si na tyle, aby dojrze cae dzieo: Och, to tylko ltrowanie listy!. Przykad 16.8. funkcja filter w regression.py files = os.listdir(path) test = re.compile("test\.py$", re.IGNORECASE) files = filter(test.search, files) #(1) #(2) #(3)

1. Jak widzielimy w podrozdziale 16.2 Znajdowanie cieki, cieka (path) moe zawiera pen lub czciow nazw cieki do katalogu, w ktrym znajduje si wanie wykonywany skrypt lub moe zawiera pusty napis, jeli skrypt zosta uruchomiony z biecego katalogu. Jakkolwiek by nie byo, na licie files znajd si nazwy plikw z tego samego katalogu, w ktrym znajduje si uruchomiony skrypt. 2. Jest to skompilowane wyraenie regularne. Jak widzielimy w podrozdziale 15.3 sec:Refaktoryzacja]Refaktoryzacja, jeli to samo wyraenie ma by uywane wicej ni raz, warto je skompilowa w celu poprawienia wydajnoci programu. Obiekt powstay w wyniku kompilacji posiada metod search, ktra pobiera jeden argument: napis, do ktrego powinno si dopasowa wyraenie regularne. Jeli dopasowanie nastpi, metoda search zwrci obiekt klasy Match zawierajcy informacj o sposobie dopasowania wyraenia regularnego; w przeciwnym przypadku zwrci None, czyli zdeniowan w jzyku Python warto null. 3. Metoda search powinna zosta wywoana na obiekcie skompilowanego wyraenia regularnego dla kadego elementu na licie files. Jeli wyraenie zostanie dopasowane do elementu, metoda ta zwrci obiekt klasy Match, ktry ma warto logiczn true, a wic element zostanie doczony do listy wynikowej zwrconej przez ltr. Jeli wyraenie nie zostanie dopasowane, metoda search zwrci warto None, ktra ma warto logiczn false, a wic dopasowywany element nie zostanie doczony do listy wynikowej. Notatka historyczna. Wersje jzyka Python wczeniejsze ni 2.0 nie obsugiway jeszcze wyrae listowych, a wic nie mona byo ich uy w celu przeltrowania listy; istniaa jednak funkcja filter. Nawet po wprowadzeniu wyrae listowych w 2.0 niektre osoby wci wol uywa starej metody ltrujcej filter (oraz towarzyszcej jej funkcji map, o ktrej powiem jeszcze w tym rozdziale). W chwili obecnej dziaaj obydwie te techniki, a wybr jednej z nich jest po prostu kwesti stylu. Toczy si dyskusja, czy map i filter nie powinny zosta przedawnione w przyszych wersjach jzyka, jednak dotychczas ostateczna decyzja nie zapada. Przykad 16.9. Filtrowanie przy uyciu wyrae listowych files = os.listdir(path) test = re.compile("test\.py$", re.IGNORECASE) files = [f for f in files if test.search(f)] #(1)

354

ROZDZIA 16. REFAKTORYZACJA

1. Wykonanie tej linii kodu bdzie miao dokadnie ten sam efekt, co uycie funkcji ltrujcej. Ktry sposb jest bardziej ekspresyjny? Wszystko zaley od was.

16.9. ODWZOROWYWANIE LISTY

355

16.9

Odwzorowywanie listy

Jeszcze o odwzorowywaniu list


Wiecie ju, w jaki sposb uy wyrae listowych w celu odwzorowania jednej listy w inn. Mona to osign rwnie w inny sposb, uywajc wbudowanej funkcji map. Dziaa ona podobnie do funkcji filter. Przykad 16.10. Wprowadzenie do funkcji map >>> ... ... >>> >>> [2, >>> [2, >>> >>> ... ... >>> [2, def double(n): return n*2 li = [1, 2, 3, 5, 9, 10, 256, -3] map(double, li) 4, 6, 10, 18, 20, 512, -6] [double(n) for n in li] 4, 6, 10, 18, 20, 512, -6] newlist = [] for n in li: newlist.append(double(n)) newlist 4, 6, 10, 18, 20, 512, -6]

#(1) #(2)

#(3)

1. Funkcja map pobiera jako parametry funkcj oraz list 1 , a zwraca now list, ktra powstaje w wyniku wywoania funkcji przekazanej w pierwszym parametrze dla kadego elementu listy przekazanej w drugim parametrze. W tym przypadku kady element listy zosta pomnoony przez 2. 2. Ten sam efekt mona osign wykorzystujc wyraenia listowe. Wyraenia listowe pojawiy si w jzyku Python w wersji 2.0; funkcja map istniaa w jzyku od zawsze. 3. Jeli bardzo chcecie myle jak programista Visual Basica, to moglibycie rwnie do tego celu uy ptli. Przykad 16.11. funkcja map z listami zawierajcymi elementy rnych typw >>> li = [5, a, (2, b)] >>> map(double, li) [10, aa, (2, b, 2, b)] #(1)

1. Chciabym zwrci uwag, e funkcja, ktrej uywamy jako argumentu map moe by bez problemu zastosowana do list zawierajcych elementy rnych typw, o ile oczywicie poprawnie obsuguje kady z typw, jakie posiadaj elementy listy. W tym przypadku funkcja double mnoy swj argument przez 2, a Python wykona w tym momencie operacj waciw dla typu tego argumentu. W przypadku wartoci cakowitych oznacza to pomnoenie wartoci przez 2; w przypadku napisw oznacza to wyduenie napisu o samego siebie; dla krotek oznacza to utworzenie nowej krotki zawierajcej wszystkie elementy krotki oryginalnej, a po nich ponownie wszystkie elementy krotki oryginalnej.
1 Patrz

przypis w poprzednim podrozdziale, Filtrowanie listy

356

ROZDZIA 16. REFAKTORYZACJA Dobra, koniec zabawy. Popatrzmy na kawaek prawdziwego kodu. Przykad 16.12. Funkcja map w regression.py filenameToModuleName = lambda f: os.path.splitext(f)[0] #(1) moduleNames = map(filenameToModuleName, files) #(2)

1. Jak widzielimy w podrozdziale 4.7 Wyraenia lambda, lambda pozwala na zdeniowanie funkcji w locie. W przykadzie 6.17 Rozdzielanie cieek (w podrozdziale Praca z katalogami), os.path.splitext pobiera nazw pliku i zwraca par (nazwa, rozszerzenie). A wic funkcja filenameToModuleName bierze nazw pliku jako parametr i odcina od niej rozszerzenie, zwracajc nazw bez rozszerzenia. 2. Wywoanie map spowoduje wywoanie funkcji filenameToModuleName dla kadego elementu z listy files , a w rezultacie zwrcenie listy wartoci, jakie powstay po kadym z tych wywoa. Innymi sowy, odcinamy rozszerzenie dla kadej nazwy pliku i zbieramy powstae w ten sposb nazwy bez rozszerze do listy moduleNames. Jak zobaczycie w dalszej czci rozdziau, tego typu mylenie, ktre jest mocno skoncentrowane na przetwarzanych danych, mona rozcign przez wszystkie etapy pisania kodu, a do ostatecznego celu, jakim jest zdeniowanie i uruchomienie pojedynczego zestawu testw jednostkowych zawierajcego testy ze wszystkich poszczeglnych przypadkw testowych.

16.10. PROGRAMOWANIE KONCENTRUJCE SI NA DANYCH

357

16.10

Programowanie koncentrujce si na danych

W tym momencie zastanawiacie si zapewne, dlaczego takie podejcie moe by uznane za lepsze od podejcia, w ktrym uywa si ptli i bezporednich wywoa funkcji. I to jest bardzo dobre pytanie. Przede wszystkim jest to kwestia przyjcia pewnej optyki. Uycie funkcji map oraz filter zmusza do skoncentrowania si na przetwarzanych danych. W tym przypadku zaczlimy od sytuacji, w ktrej w ogle nie byo adnych danych; pierwsz rzecz, jak zrobilimy, byo uzyskanie cieki do katalogu, w ktrym znajdowa si uruchomiony skrypt, a kolejn uzyskanie na tej podstawie listy plikw znajdujcych si w tym katalogu. W ten sposb zaczlimy i dziki tym krokom zdobylimy dane, na ktrych moglimy dalej pracowa: list nazw plikw. Wiedzielimy jednak, e nie interesuj nas wszystkie pliki, a jedynie te, ktre s zestawami testw. Mielimy zbyt duo danych, a wic potrzebowalimy je jako przeltrowa. Skd wiedzielimy, ktre dane zachowa? Potrzebowalimy funkcji, ktra by to sprawdzaa, a ktr moglimy przekaza do funkcji ltrujcej. W tym akurat przypadku uylimy wyraenia regularnego, ale koncepcja jest wci taka sama, niezalenie od tego, w jaki sposb skonstruowana zostaa funkcja sprawdzajca. Po tym kroku posiadalimy ju list nazw plikw bdcych zestawami testowymi (i tylko nimi, poniewa wszystkie inne pliki zostay odltrowane), jednak w rzeczywistoci potrzebowalimy jedynie list nazw moduw. Mielimy wszystkie dane, jednak byy one w zym formacie. Zdeniowalimy wic funkcj, ktra przeksztacaa nazw pliku w nazw moduu i kady element z listy nazw plikw odwzorowalimy przy pomocy tej funkcji w nazw moduu, uzyskujc list nazw moduw. Z kadej nazwy pliku powstaa jedna nazwa moduu, a z listy nazw plikw powstaa lista nazw moduw. Zamiast funkcji filter moglimy uy ptli for z instrukcj if. Zamiast funkcji map moglimy uy ptli for z wywoaniem funkcji. Jednak uywanie ptli w ten sposb jest zajciem czasochonnym. W najlepszym przypadku stracimy niepotrzebnie czas, a w najgorszym wprowadzimy brzydkie bdy. Na przykad, odpowiadajc na pytanie: czy ten plik jest zestawem testw? zastanawiamy si nad logik specyczn dla danego zastosowania i aden jzyk programowania nie wyrazi tego za nas. Jednak kiedy ju wiemy, jak na takie pytanie odpowiedzie, czy naprawd potrzebujemy tego kopotliwego tworzenia nowej, pustej listy, napisania ptli for i instrukcji if, a nastpnie rcznego wywoywania funkcji append, aby doda element, ktry przeszed przez test w warunku if do tej listy, a dodatkowo jeszcze ledzenia, jaka zmienna przechowuje dane ju przeltrowane, a jaka te, ktre dopiero bd ltrowane? Dlaczego nie mielibymy po prostu zdeniowa odpowiedniego warunku, a ca reszt zrobi za nas Python? Oczywicie, moglibymy by sprytni i nie tworzy nowej listy, lecz usuwa niepotrzebne elementy z listy wejciowej. Ale ju si na tym sparzylimy: prba modykowania listy, po ktrej wanie iterujemy, moe powodowa bdy. Usuwamy element, przechodzimy do nastpnego elementu, i tym samym przeskakujemy przez jaki element. Czy Python to jeden z tych jzykw, w ktrych usuwanie elementw dziaa w ten wanie sposb? Ile czasu zajmie nam ustalenie tego faktu? Czy bdziemy pamita, czy taka iteracja jest bezpieczna, czy nie, kiedy bdziemy robi to ponownie? Programici trac zbyt wiele czasu i popeniaj wiele bdw podczas zajmowania si takimi czysto przecie technicznymi kwestiami, co jest przecie bezcelowe. Nie posuwa to pracy nad programem ani o jot, tylko niepotrzebnie zajmuje czas. Kiedy uczyem si jzyka Python po raz pierwszy, stroniem od wyrae listowych, a od funkcji map i filter stroniem jeszcze duej. Upieraem si, aby moje ycie byo

358

ROZDZIA 16. REFAKTORYZACJA

bardziej skomplikowane, poniewa przylgnem do znanego mi sposobu programowania zorientowanego na kod: uywaem ptli for oraz instrukcji warunkowych. Moje programy przypominay programy pisane w jzyku Visual Basic, przedstawiay bowiem dokadnie kady krok kadej operacji w kadej funkcji. W nich wszystkich pojawiay si te wci te same, mae problemy i brzydkie bdy. I nie miao to wikszego sensu. Zapomnijmy o tym. Szczegowe rozpisywanie kodu nie jest wane. Wane s dane. Dane nie s trudne, to tylko dane. Jeli mamy ich za duo, przeltrujmy je. Jeli nie s dokadnie takie, jakich sobie yczymy, uyjmy odwzorowania map. Skoncentrujmy si na danych, a niepotrzebn prac zostawmy za sob.

16.11. DYNAMICZNE IMPORTOWANIE MODUW

359

16.11

Dynamiczne importowanie moduw

Dynamiczne importowanie moduw


OK, do lozofowania. Pogadajmy o dynamicznym importowaniu moduw. Najpierw zerknijmy jak normalnie importuje si moduy. Skadnia polecenia import module sprawdza ciek w poszukiwaniu nazwanego moduu i importuje go po nazwie. W ten sposb mona importowa kilka moduw na raz, podajc nazwy moduw oddzielone przecinkiem. Z reszt, robilimy ju to w pierwszej linii skryptu z tego rozdziau. Przykad 16.13. Importowanie wielu moduw na raz import sys, os, re, unittest #(1) 1. Importowane s cztery moduy na raz: sys (funkcje systemowe oraz dostpu do parametrw przekazywanych z linii polece), os (wykonywanie funkcji systemowych takich jak np. listowanie katalogw), re (wyraenia regularne), oraz unittest (testy jednostkowe). A teraz zrbmy to samo, jednak przy uyciu dynamicznego importowania. Przykad 16.14. Dynamiczne importowanie moduw >>> >>> >>> >>> >>> >>> >>> >>> sys = __import__(sys) #(1) os = __import__(os) re = __import__(re) unittest = __import__(unittest) sys #(2) <module sys (built-in)> os <module os from /usr/local/lib/python2.2/os.pyc>

1. Wbudowana funkcja import robi to samo co uycie polecenia import, jednak jest to funkcja rzeczywista, ktra przyjmuje cig znakw jako argument. 2. Zmienna sys staje si moduem sys, to tak jakby napisa import sys. Zmienna os staje si moduem os i tak dalej. Reasumujc, import importuje modu, jednak aby tego dokona, pobiera jako argument cig znakw. W tym przypadku modu, ktry zaimportowalimy by po prostu na sztywno zakodowanym cigiem znakw, jednak nic nie stao na przeszkodzie, aby bya to zmienna lub wynik dziaania funkcji. Zmienna, pod ktr podstawiamy modu, nie musi si nazywa tak samo jak nazwa moduu, ktry importujemy. Rwnie dobrze moglibymy zaimportowa szereg moduw i przypisa je do listy. Przykad 16.15. Dynamiczne importowanie listy moduw >>> moduleNames = [sys, os, re, unittest] #(1) >>> moduleNames [sys, os, re, unittest] >>> modules = map(__import__, moduleNames) #(2) >>> modules #(3) [<module sys (built-in)>, <module os from c:\Python22\lib\os.pyc>,

360

ROZDZIA 16. REFAKTORYZACJA

<module re from c:\Python22\lib\re.pyc>, <module unittest from c:\Python22\lib\unittest.pyc>] >>> modules[0].version #(4) 2.2.2 (#37, Nov 26 2002, 10:24:37) [MSC 32 bit (Intel)] >>> import sys >>> sys.version 2.2.2 (#37, Nov 26 2002, 10:24:37) [MSC 32 bit (Intel)] 1. moduleNames jest po prostu list cigw znakw. Nic nadzwyczajnego, za wyjtkiem tego, e akurat te cigi znakw s nazwami moduw, ktre moglibymy zaimportowa, jeli bymy chcieli. 2. Wyobramy sobie, e chcielimy je zaimportowa, a dokonalimy tego poprzez mapowanie funkcji import na list. Pamitajmy jednak, e kady element listy (moduleNames) bdzie przekazany jako argument do wywoania raz za razem funkcji ( import ), dziki czemu zostanie zbudowana i zwrcona lista wartoci wynikowych 3. Tak wic z listy cigw znakw stworzylimy tak na prawd list rzeczywistych moduw. (Nasze cieki mog si rni w zalenoci od systemu operacyjnego, na ktrym zainstalowalimy Pythona, faz ksiyca i innych takich tam.) 4. Aby upewni si, e s to tak na prawd moduy, zerknijmy na niektre ich atrybuty. Pamitajmy, e modules[0] jest moduem sys, wic modules[0].version odpowiada sys.version. Wszystkie pozostae atrybuty i metody tych moduw s take dostpne. Nie ma nic niezwykego w poleceniu import, tak samo jak nie ma nic magicznego w moduach. Moduy s obiektami. Wszystko jest obiektem. Teraz ju powinnimy mc wszystko to poskada do kupy i rozszyfrowa o co tak na prawd chodzi w kodzie zamieszczonych tutaj przykadw.

Rozdzia 17

Programowanie funkcyjne

361

362

ROZDZIA 17. PROGRAMOWANIE FUNKCYJNE

17.1

Programowanie funkcyjne - wszystko razem

Dowiedzielicie si ju wystarczajco duo, by mc odczyta pierwszych siedem linii kodu z przykadu podanego na pocztku rozdziau, w ktrym odczytywane s pliki w katalogu a nastpnie importowane wybrane spord nich moduy. Przykad 16.16. Funkcja regressionTest def regressionTest(): path = os.path.abspath(os.path.dirname(sys.argv[0])) files = os.listdir(path) test = re.compile("test\.py$", re.IGNORECASE) files = filter(test.search, files) filenameToModuleName = lambda f: os.path.splitext(f)[0] moduleNames = map(filenameToModuleName, files) modules = map(__import__, moduleNames) load = unittest.defaultTestLoader.loadTestsFromModule return unittest.TestSuite(map(load, modules)) Spjrzmy na ten kod w sposb interaktywny, linia po linii. Zamy, e katalogiem biecym jest c:\diveintopython\py, w ktrym znajduj si przykady doczone do tej ksiki, z omawianym w tym rozdziale skryptem wcznie. Jak widzielimy w podrozdziale 16.2 Znajdowanie cieki, nazwa katalogu, w ktrym znajduje si skrypt, tra do zmiennej path, sprbujmy wic t cz zakodowa na sztywno, po czym dopiero przej dalej. Przykad 16.17. Krok 1: Pobieranie wszystkich plikw >>> import sys, os, re, unittest >>> path = rc:\diveintopython\py >>> files = os.listdir(path) >>> files #(1) [BaseHTMLProcessor.py, LICENSE.txt, apihelper.py, apihelpertest.py, argecho.py, autosize.py, builddialectexamples.py, dialect.py, fileinfo.py, fullpath.py, kgptest.py, makerealworddoc.py, odbchelper.py, odbchelpertest.py, parsephone.py, piglatin.py, plural.py, pluraltest.py, pyfontify.py, regression.py, roman.py, romantest.py, uncurly.py, unicode2koi8r.py, urllister.py, kgp, plural, roman, colorize.py] 1. les jest list nazw wszystkich plikw i katalogw znajdujcych si w katalogu, z ktrego pochodzi skrypt. (Jeli wczeniej uruchamialicie ju jakie przykady, na licie moecie rwnie zauway pliki .pyc) Przykad 16.18. Krok 2: Filtrowanie w celu znalezienia interesujcych plikw >>> test = re.compile("test\.py$", re.IGNORECASE) #(1) >>> files = filter(test.search, files) #(2) >>> files #(3) [apihelpertest.py, kgptest.py, odbchelpertest.py, pluraltest.py, romantest.py]

17.1. PROGRAMOWANIE FUNKCYJNE - WSZYSTKO RAZEM

363

1. Wyraenie regularne zostanie dopasowane do kadego napisu zakoczonego na test.py. Zauwacie, e kropka musi zosta poprzedzona sekwencj unikow; w wyraeniach regularnych kropka oznacza dopasuj dowolny znak, jednak nam zaley na dosownym dopasowaniu znaku kropki. 2. Skompilowane wyraenie regularne dziaa jak funkcja, a wic moemy jej uy do przeltrowania dugiej listy nazw plikw i katalogw, dziki czemu uzyskamy list nazw, do ktrych zostao dopasowane wyraenie. 3. Otrzymalimy wic list skryptw bdcych testami jednostkowymi, poniewa tylko one maj nazw JAKASNAZWAtest.py. Przykad 16.19. Krok 3: Odwzorowanie nazw plikw na nazwy moduw >>> filenameToModuleName = lambda f: os.path.splitext(f)[0] #(1) >>> filenameToModuleName(romantest.py) #(2) romantest >>> filenameToModuleName(odchelpertest.py) odbchelpertest >>> moduleNames = map(filenameToModuleName, files) #(3) >>> moduleNames #(4) [apihelpertest, kgptest, odbchelpertest, pluraltest, romantest] 1. Jak widzielimy w podrozdziale 4.7 Wyraenia lambda, lambda pozwala na szybkie zdeniowanie jednolinijkowych funkcji w locie. Tutaj funkcja lambda pobiera nazw pliku wraz z rozszerzeniem i zwraca cz nazwy bez rozszerzenia, uywajc do tego funkcji os.path.splitext z biblioteki standardowej, ktr poznalimy w przykadzie 6.17 Rozdzielanie cieek w podrozdziale Praca z katalogami. 2. filenameToModuleName jest funkcj. W porwnaniu ze zwykymi funkcjami, ktre tworzy si przy pomocy instrukcji def, w funkcjach lambda nie ma niczego magicznego. Moemy wywoa filenameToModuleName jak kad inn funkcj, a robi ona dokadnie to, czego potrzebujemy: odcina rozszerzenie z napisu przekazanego jej w parametrze wejciowym. 3. Tutaj moemy wywoa t funkcj na kadej nazwie pliku znajdujcej si na licie plikw bdcych testami jednostkowymi. Uywamy do tego funkcji map. 4. W wyniku otrzymujemy to, czego oczekiwalimy: list nazw moduw. Przykad 16.20. Krok 4: Odwzorowanie nazw moduw na moduy >>> modules = map(__import__, moduleNames) >>> modules [<module apihelpertest from apihelpertest.py>, <module kgptest from kgptest.py>, <module odbchelpertest from odbchelpertest.py>, <module pluraltest from pluraltest.py>, <module romantest from romantest.py>] >>> modules[-1] <module romantest from romantest.py> #(1) #(2)

#(3)

364

ROZDZIA 17. PROGRAMOWANIE FUNKCYJNE

1. Jak widzielimy w podrozdziale 16.6 Dynamiczne importowanie moduw, w celu odwzorowania listy nazw moduw (napisw) na waciwe moduy (obiekty wywoywalne, do ktrych mona mie dostp jak do jakichkolwiek innych moduw), mona uy funkcji map oraz import . 2. modules to teraz lista moduw, do ktrych mona mie taki sam dostp, jak do jakichkolwiek innych moduw. 3. Ostatnim moduem na licie jest modu romantest, tak jak gdybymy napisali: import romantest Przykad 16.21. Krok 5: adowanie moduw do zestawu testw >>> load = unittest.defaultTestLoader.loadTestsFromModule >>> map(load, modules) #(1) [<unittest.TestSuite tests=[ <unittest.TestSuite tests=[<apihelpertest.BadInput testMethod=testNoObject>]>, <unittest.TestSuite tests=[<apihelpertest.KnownValues testMethod=testApiHelper>]>, <unittest.TestSuite tests=[ <apihelpertest.ParamChecks testMethod=testCollapse>, <apihelpertest.ParamChecks testMethod=testSpacing>]>, ... ] ] >>> unittest.TestSuite(map(load, modules)) #(2)

1. To s prawdziwe obiekty moduw. Nie tylko mamy do nich dostp taki, jak do innych moduw i moemy tworzy instancje klas oraz wywoywa funkcje, mamy rwnie moliwo introspekcji (wgldu) w modu, ktrej moemy uy przede wszystkim do tego, aby dowiedzie si, jakie klasy i funkcje dany modu posiada. To wanie robi metoda loadTestsFromModule: dokonuje introspekcji, a nastpnie dla kadego moduu zwraca obiekt unittest.TestSuite. Kady taki obiekt zawiera list obiektw unittest.TestSuite, po jednym dla kadej klasy dziedziczcej po TestCase zdeniowanej w module. Kady z obiektw na tej licie zawiera z kolei list metod testowych zdeniowanych w klasie testowej. 2. Na kocu umieszczamy list obiektw TestSuite wewntrz jednego, duego zestawu testw. Modu unittest nie ma problemw z przechodzeniem po drzewie zestaww testowych zagniedonych w zestawach testowych; dotrze on do kadej metody testowej i j wywoa, sprawdzajc, czy przesza, czy nie, a nastpnie przejdzie do kolejnej metody. Modu unittest zwykle przeprowadza za nas proces introspekcji. Czy pamitacie magiczn funkcj unittest.main(), ktr wywoyway poszczeglne moduy, aby odpali wszystkie znajdujce si w nich testy? Metoda unittest.main() w rzeczywistoci tworzy instancj klasy unittest.TestProgram, ktra z kolei tworzy instancj unittest.defaultTestLoader, suc do zaadowania moduu, z ktrego zostaa wywoana. (Skd jednak ma referencj do moduu, z ktrego zostaa wywoana, jeli nie dostaa jej od nas? Ot dziki rwnie magicznemu poleceniu import ( main ), ktre dynamicznie importuje wykonywany wanie modu. Mgbym napisa ca ksik

17.1. PROGRAMOWANIE FUNKCYJNE - WSZYSTKO RAZEM

365

na temat trikw i technik uywanych w module unittest, ale chyba bym jej nie skoczy.) Przykad 16.22. Krok 6: Powiedzie moduowi unittest, aby uy naszego zestawu testowego if __name__ == "__main__": unittest.main(defaultTest="regressionTest") #(1) 1. Zamiast pozwala moduowi unittest wykona ca magi za nas, wikszo zrobilimy sami. Utworzylimy funkcj (regressionTest), ktra sama importuje moduy i woa unittest.defaultTestLoader, a nastpnie utworzylimy duy zestaw testw. Teraz potrzebujemy jedynie, aby unittest, zamiast szuka testw i budowa zestaw testowy w standardowy sposb, uruchomi funkcj regressionTest, ktra zwrci gotwy do uycia obiekt testSuite.

366

ROZDZIA 17. PROGRAMOWANIE FUNKCYJNE

17.2

Programowanie funkcyjne - podsumowanie

Program regression.py i wynik jego dziaania powinien by teraz cakiem zrozumiay. Powinnicie te bez kopotu wykonywa nastpujce zadania: Przeksztacanie informacji o ciece otrzymanej z linii polece Filtrowanie list przy uyciu metody filter zamiast uywania wyrae listowych Odwzorowywanie list przy uyciu metody map zamiast uywania wyrae listowych Dynamiczne importowanie moduw

Rozdzia 18

Funkcje dynamiczne

367

368

ROZDZIA 18. FUNKCJE DYNAMICZNE

18.1

Funkcje dynamiczne

Nurkujemy
Chc teraz opowiedzie o rzeczownikach w liczbie mnogiej. Take o funkcjach zwracajcych inne funkcje, o zaawansowanych wyraeniach regularnych oraz o generatorach, ktre pojawiy si w jzyku Python w wersji 2.3. Zaczn jednak od tego, w jaki sposb tworzy si rzeczowniki w liczbie mnogiej. Jeli jeszcze nie przeczytalicie rozdziau 7 (Wyraenia regularne), nadszed doskonay moment, aby to zrobi. W tym rozdziale chc szybko przej do bardziej zaawansowanego uycia wyrae regularnych, zakadam wic, e dobrze rozumiecie podstawy. Jzyk angielski jest jzykiem schizofrenicznym, ktry sporo zapoyczy z innych jzykw; zasady tworzenia rzeczownikw w liczbie mnogiej na podstawie liczby pojedynczej s zrnicowane i zoone. Istniej pewne zasady, jednak istniej rwnie wyjtki od tych zasad, a nawet wyjtki od tych wyjtkw. Jeli dorastalicie w kraju, w ktrym mwi si po angielsku lub uczylicie si angielskiego w czasie, gdy chodzilicie do szkoy, ponisze reguy powinny by wam dobrze znane: 1. Jeli sowo koczy si na S, X lub Z, naley doda ES. Bass staje si basses, fax staje si faxes a waltz staje si waltzes. 2. Jeli sowo koczy si na dwiczne H, naley doda ES; jeli koczy si na nieme H, naley doda samo S. Co to jest dwiczne H? Takie, ktre po poczeniu z innymi goskami mona usysze. A wic coach staje si coaches a rash staje si rashes, poniewa goski CH i SH s dwiczne. Jednak cheetah staje si cheetahs, poniewa wystpuje tutaj H bezdwiczne. 3. Jeli sowo koczy si na Y, ktre brzmi jak I, naley zmieni Y na IES; jeli Y jest poczony z gosk, ktra brzmi inaczej, naley doda S. A wic vacancy staje si vacancies, ale day staje si days. 4. Jeli wszystko zawiedzie, naley doda S i mie nadziej, e si uda. (Wiem, jest mnstwo wyjtkw. Man staje si men a woman staje si women, jednak human staje si humans. Mouse staje si mice,a louse staje si lice, jednak house staje si houses. Knife staje si knives a wife staje si wives, jednak lowlife staje si lowlifes. Nie mwcie mi nawet o sowach, ktre same w sobie oznaczaj liczb mnog, jak sheep, deer czy haiku.) W innych jzykach wyglda to oczywicie zupenie inaczej. Zaprojektujemy wic modu, ktry dla kadego rzeczownika utworzy odpowiedni rzeczownik w liczbie mnogiej. Zaczniemy od rzeczownikw w jzyku angielskim i od powyszych czterech zasad, jednak musimy mie na uwadze, e obsugiwanie nowych regu (a nawet nowych jzykw) jest nieuniknione.

18.2. PLURAL.PY, ETAP 1

369

18.2

plural.py, etap 1

Patrzymy na sowa, ktre przynajmniej w jzyku angielskim skadaj si z liter. Dysponujemy te reguami, ktre mwi, e musimy znale w sowie pewne kombinacje liter, a nastpnie odpowiednio to sowo zmodykowa. Brzmi to dokadnie jak zadanie dla wyrae regularnych. Przykad 17.1. plural1.py import re def plural(noun): if re.search([sxz]$, noun): #(1) return re.sub($, es, noun) #(2) elif re.search([^aeioudgkprt]h$, noun): return re.sub($, es, noun) elif re.search([^aeiou]y$, noun): return re.sub(y$, ies, noun) else: return noun + s 1. Rzeczywicie, jest to wyraenie regularne, jednak uywa ono skadni, jakiej w rozdziale 7 (Wyraenia regularne) nie widzielicie. Nawias kwadratowy oznacza: dopasuj dokadnie jeden z wymienionych tu znakw. A wic [sxz] oznacza s albo x, albo z, ale tylko jeden znak na raz. Znak $ powinien by wam znany; dopasowuje si on do koca napisu. A wic sprawdzamy tutaj, czy rzeczownik koczy si na jedn z liter s, x lub z. 2. Funkcja re.sub dokonuje podstawienia w oparciu o wyraenie regularne. Przyjrzyjmy si jej bliej. Przykad 17.2. Wprowadzenie funkcji re.sub >>> import re >>> re.search([abc], Mark) #(1) <_sre.SRE_Match object at 0x001C1FA8> >>> re.sub([abc], o, Mark) #(2) Mork >>> re.sub([abc], o, rock) #(3) rook >>> re.sub([abc], o, caps) #(4) oops 1. Czy napis Mark zawiera jedn z liter a, b lub c? Tak, zawiera a. 2. W porzdku; znajd a, b lub c i zastp je liter o. Mark zmienia si w Mork. 3. Ta sama funkcja zmienia rock w rook. 4. Moe si wydawa, e ta linia zmieni caps w oaps, jednak dzieje si inaczej. Funkcja re.sub zastpuje wszystkie wystpienia, nie tylko pierwsze. Caps zmienia si w oops poniewa zarwno c jak i a zostaj zastpione liter o. Przykad 17.3. Z powrotem do plural1.py

370 import re

ROZDZIA 18. FUNKCJE DYNAMICZNE

def plural(noun): if re.search([sxz]$, noun): return re.sub($, es, noun) #(1) elif re.search([^aeioudgkprt]h$, noun): #(2) return re.sub($, es, noun) #(3) elif re.search([^aeiou]y$, noun): return re.sub(y$, ies, noun) else: return noun + s 1. Wrmy do funkcji plural. Co robimy? Zamieniamy kocwk napisu na es. Innymi sowy, dodajemy es do napisu. Moglibymy osign ten cel uywajc dodawania napisw, na przykad stosujc wyraenie: rzeczownik + es, jednak tutaj wyrae regularnych bd uywa do wszystkiego, ze wzgldu na spjno oraz z innych powodw, ktre zostan wyjanione w dalszej czci rozdziau. 2. Patrzcie uwane, to kolejna nowo. Znak znajdujcy si wewntrz nawiasw kwadratowych oznacza co szczeglnego: negacj. [abc] oznacza dowolny znak oprcz a, b oraz c. Wyraenie [aeioudgkprt] oznacza kady znak za wyjtkiem a, e, i, o, u, d, g, k, p, r oraz t. Po tym znaku powinien znale si znak h koczcy napis. Tutaj szukamy sw koczcych si na H, ktre mona usysze. 3. Tutaj podobnie: dopasowujemy sowa koczce si na Y, przy czym znak stojcy przed Y musi by dowolnym znakiem za wyjtkiem a, e, i, o oraz u. Szukamy sw, ktre kocz si na Y i brzmi jak I. Przykad 17.4. Wicej na temat negacji w wyraeniach regularnych >>> import re >>> re.search([^aeiou]y$, vacancy) #(1) <_sre.SRE_Match object at 0x001C1FA8> >>> re.search([^aeiou]y$, boy) #(2) >>> >>> re.search([^aeiou]y$, day) >>> >>> re.search([^aeiou]y$, pita) #(3) >>> 1. Wyraenie zostanie dopasowane do vacancy poniewa sowo to koczy si na cy, a c nie jest a, e, i, o ani u. 2. Nie zostanie dopasowane do boy, ktry koczy si na oy, a powiedzielimy wyranie, e znakiem stojcym przed y nie moe by o. day nie zostanie dopasowane, poniewa koczy si na ay. 3. pita rwnie nie zostanie dopasowana, poniewa nie koczy si na y. Przykad 17.5. Wicej na temat re.sub

18.2. PLURAL.PY, ETAP 1 >>> re.sub(y$, ies, vacancy) #(1) vacancies >>> re.sub(y$, ies, agency) agencies >>> re.sub(([^aeiou])y$, r\1ies, vacancy) #(2) vacancies

371

1. To wyraenie regularne przeksztaca vacancy w vacancies oraz agency w agencies, dokadnie tak, jak chcemy. Zauwamy, e wyraenie to przeksztacioby boy w boies, gdyby nie fakt, e w funkcji uylimy wczeniej re.search, aby dowiedzie si, czy powinnimy rwnie dokona podstawienia przy uyciu re.sub. 2. Chciabym nadmieni mimochodem, e moliwe jest poczenie tych dwch wyrae regularnych (jednego, ktre sprawdza, czy pewna zasada ma zastosowanie, i drugiego, ktre faktycznie t zasad stosuje) w jedno. Wygldaoby ono dokadnie tak. Wikszo powinna by ju wam znana: aby zapamita znak, ktry stoi przed y, uywamy zapamitanej grupy, o ktrej bya mowa w podrozdziale 7.6 (Analiza przypadku: Przetwarzanie numerw telefonw). W podstawianym napisie pojawia si nowa skadnia, \1, ktre oznacza: czy pamitasz grup numer 1? wstaw j tutaj. W tym przypadku jako znak stojcy przed y zostanie zapamitane c, po czym dokonane zostanie podstawienie c w miejsce c oraz ies w miejsce y. (Jeli potrzebujemy wicej ni jednej zapamitanej grupy, uywamy \2, \3 itd.) Podstawienia wyrae regularnych stanowi niezwykle silny mechanizm, a skadnia \1 czyni go jeszcze silniejszym. Z drugiej strony przedstawienie caej operacji w postaci jednego wyraenia regularnego sprawioby, e staaby si ona mao czytelna i niewiele by miaa wsplnego ze sposobem, w jaki na pocztku opisywalimy sposb konstruowania liczby mnogiej. Utworzylimy reguy takie jak jeli sowo koczy si na S, X lub Z, dodaj ES i kiedy teraz patrzymy na funkcj plural, widzimy dwie linijki kodu, ktre mwi jeli sowo koczy si na S, X lub Z, dodaj ES. Nie mona tego zrobi bardziej bezporednio.

372

ROZDZIA 18. FUNKCJE DYNAMICZNE

18.3

plural.py, etap 2

Dodamy teraz warstw abstrakcji. Zaczlimy od zdeniowania listy regu: jeli jest tak, wtedy zrb tak, w przeciwnym przypadku id do nastpnej reguy. Teraz skomplikujemy pewn cz programu po to, by mc uproci inn. Przykad 17.6. plural2.py import re def match_sxz(noun): return re.search([sxz]$, noun) def apply_sxz(noun): return re.sub($, es, noun) def match_h(noun): return re.search([^aeioudgkprt]h$, noun) def apply_h(noun): return re.sub($, es, noun) def match_y(noun): return re.search([^aeiou]y$, noun) def apply_y(noun): return re.sub(y$, ies, noun) def match_default(noun): return 1 def apply_default(noun): return noun + s rules = ((match_sxz, apply_sxz), (match_h, apply_h), (match_y, apply_y), (match_default, apply_default) ) def plural(noun): for matchesRule, applyRule in rules: if matchesRule(noun): return applyRule(noun)

#(1)

#(2) #(3) #(4)

1. Cho ta wersja jest bardziej skomplikowana (z pewnoci jest dusza), robi ona dokadnie to samo: prbuje dopasowa kolejno cztery reguy, a nastpnie, jeli dopasowanie si powiedzie, stosuje ona odpowiednie wyraenie regularne. Rnica polega na tym, e kada regua dopasowujca oraz modykujca jest zdeniowana w swojej wasnej funkcji, przy czym funkcje te zostay zebrane w zmiennej rules, ktra jest krotk krotek.

18.3. PLURAL.PY, ETAP 2

373

2. Uywajc ptli for, moemy z krotki rules wyciga po dwie reguy na raz (jedn dopasowujc i jedn modykujc). Podczas pierwszej iteracji ptli for matchesRule przyjmie warto match sxz, a applyRule warto apply sxz. Podczas drugiej iteracji (jeli taka nastpi), matchesRule przyjmie warto match h, a applyRule przyjmie warto apply h. 3. Pamitajcie, e w jezyku Python wszystko jest obiektem, nawet funkcje. Krotka rules skada si z dwuelementowych krotek zawierajcych funkcje. Nie s to nazwy funkcji, lecz rzeczywicie funkcje. W ptli s one przypisywane do applyRule oraz matchesRule, ktre staj si funkcjami, a wic obiektami, ktre mona wywoa. W tym miejscu podczas w pierwszej iteracji ptli zostanie wykonany kod rwnowany wywoaniu: matches sxz(noun). 4. W tym za miejscu podczas pierwszej iteracji ptli for zostanie wykonany kod rwnowamy wywoaniu apply sxz(noun). Jeli ten dodatkowy poziom abstrakcji wydaje si zagmatwany, sprbujmy odwika powysz funkcj w celu lepszego uwidocznienia rwnowanoci. Ptla w funkcji plural jest rwnowana nastpujcej ptli: Przykad 17.7. Rozwikywanie funkcji plural def plural(noun): if match_sxz(noun): return apply_sxz(noun) if match_h(noun): return apply_h(noun) if match_y(noun): return apply_y(noun) if match_default(noun): return apply_default(noun) Zysk jest taki, e funkcja plural znacznie si uprocia. Bierze ona list regu zdeniowanych w innym miejscu i w sposb bardzo oglny iteruje po nich: bierze regu dopasowujc; czy regua pasuje? Jeli tak, wywouje regu modykujc. Reguy mog by zdeniowane w innym miejscu, w dowolny sposb. Funkcji plural pochodzenie regu nie interesuje. Zastanwmy si, czy warto byo wprowadza t warstw abstrakcji. Raczej nie. Zastanwmy si, co musielibymy zrobi, aby doda do funkcji now regu. C, w poprzednim podrozdziale e do funkcji plural naleaoby doda instrukcj if. W tym podrozdziale naleaoby doda dwie funkcje, macth foo i apply foo, a nastpnie zaktualizowa list regu wstawiajc je w takim miejscu, eby w stosunku do innych regu zostay one wywoane w odpowiedniej kolejnoci. Tak naprawd to byo tylko wprowadzenie do kolejnego podrozdziau. Idmy wic dalej.

374

ROZDZIA 18. FUNKCJE DYNAMICZNE

18.4

plural.py, etap 3

Zauwamy, e deniowanie osobnych, nazwanych funkcji dla kadej reguy dopasowujcej i modykujcej nie jest tak naprawd konieczne. Nigdy nie wywoujemy tych funkcji bezporednio; deniujemy je w krotce rules i wywoujemy je rwnie przy uyciu tej krotki. Sprbujmy zatem przeksztaci je do funkcji anonimowych. Przykad 17.8. plural3.py import re rules = \ ( ( lambda lambda ), ( lambda lambda ), ( lambda lambda ), ( lambda lambda ) )

word: re.search([sxz]$, word), word: re.sub($, es, word)

word: re.search([^aeioudgkprt]h$, word), word: re.sub($, es, word)

word: re.search([^aeiou]y$, word), word: re.sub(y$, ies, word)

word: re.search($, word), word: re.sub($, s, word) #(1)

def plural(noun): for matchesRule, applyRule in rules: if matchesRule(noun): return applyRule(noun)

#(2)

1. To ten sam zestaw regu, ktry widzielimy na etapie 2. Jedyna rnica polega na tym, e zamiast deniowa funkcje nazwane, takie jak match sxz czy apply sxz, wczylimy tre tych funkcji bezporednio do zmiennej rules uywajc funkcji lambda. 2. Zauwamy, e funkcja plural w ogle sie nie zmienia. Iteruje ona po zestawie funkcji reprezentujcych reguy, sprawdza pierwsz regu, a jeli zwrci ona warto true, wywouje ona drug regu i zwraca jej wynik. Dokadnie tak samo, jak wczeniej. Jedyna rnica polega teraz na tym, e funkcje z reguami zostay zdeniowane inline, jako funkcje anonimowe, przy uyciu funkcji lambda. Jednak dla funkcji plural sposb zdeniowania funkcji nie ma adnego znaczenia; otrzymuje ona list regu i wykonuje na niej swoj prac. Aby doda now regu tworzenia liczby mnogiej, wystarczy zdeniowa nowe funkcje (regu dopasowujc oraz regu modykujc) bezporednio w samej krotce

18.4. PLURAL.PY, ETAP 3

375

rules. Teraz jednak, kiedy mamy zdeniowa reguy wewntrz krotki, widzimy wyranie, e pojawiy si liczne niepotrzebne powtrzenia kodu. Mamy bowiem cztery pary funkcji, a kada z nich jest napisana wedug tego samego wzorca. Funkcje dopasowujce skadaj si z pojedynczego wywoania re.search, a funkcje modykujce z wywoania re.sub. Sprbujmy zrefaktoryzowa te podobiestwa.

376

ROZDZIA 18. FUNKCJE DYNAMICZNE

18.5

plural.py, etap 4

Aby deniowanie nowych regu byo prostsze, sprbujemy usun z kodu wystpujce tam powtrzenia. Przykad 17.9. plural4.py import re def buildMatchAndApplyFunctions((pattern, search, replace)): matchFunction = lambda word: re.search(pattern, word) #(1) applyFunction = lambda word: re.sub(search, replace, word) #(2) return (matchFunction, applyFunction) #(3) 1. buildMatchAndApplyFunctions to funkcja, ktrej zadaniem jest dynamiczne konstruowanie innych funkcji. Pobiera ona trzy parametry: pattern, search oraz replace (waciwie pobiera jeden parametr bdcy krotk, ale o tym za chwil), dziki ktrym mona zbudowa funkcj dopasowujc przy uyciu skadni lambda tak, aby pobieraa ona jeden parametr (word), a nastpnie wywoywaa re.search z wzorcem (pattern) przekazanym do funkcji buildMatchAndApplyFunctions oraz z parametrem word przekazanym do wanie budowanej funkcji dopasowujcej. Ojej. 2. W taki sam sposb odbywa si budowanie funkcji modykujcej. Funkcja modykujca pobiera jeden parametr i wywouje re.sub z parametrami search i replace przekazanymi do funkcji buildMatchAndApplyFunctions oraz parametrem word przekazanym do wanie budowanej funkcji modykujcej. Pokazana tutaj technika uywania wartoci zewntrznych parametrw w funkcjach budowanych dynamicznie nosi nazw dopenie (ang. closures). W gruncie rzeczy, podczas budowania funkcji modykujcej, zdeniowane zostaj dwie stae: funkcja pobiera jeden parametr (word), jednak dodatkowo uywa ona dwch innych wartoci (search oraz replace), ktre zostaj ustalone w momencie deniowania funkcji modykujcej. 3. Na kocu funkcja buildMatchAndApplyFunctions zwraca krotk zawierajc dwie wartoci: dwie wanie utworzone funkcje. Stae, ktre zostay zdeniowane podczas ich budowania (pattern w matchFunction oraz search i replace w applyFunction) pozostaj zapamitane w tych funkcjach, nawet po powrocie z funkcji buildMatchAndApplyFunctions. To szalenie fajna sprawa. Jeli jest to wci niesamowicie zagmatwane (powinno by, bo rzecz jest zoona), sprbujmy zobaczy z bliska, jak tego uy moe si odrobin wyjani. Przykad 17.10. Cig dalszy plural4.py patterns = \ ( ([sxz]$, $, es), ([^aeioudgkprt]h$, $, es), ((qu|[^aeiou])y$, y$, ies), ($, $, s) ) rules = map(buildMatchAndApplyFunctions, patterns)

#(1) #(2)

18.5. PLURAL.PY, ETAP 4

377

1. Nasze reguy tworzenia liczby mnogiej s teraz zdeniowane jako seria napisw (nie funkcji). Pierwszy napis to wyraenie regularne, ktre zostanie uyte w funkcji re.search w celu zbadania, czy regua pasuje do zadanego rzeczownika; drugi i trzeci napis to parametry search oraz replace funkcji re.sub, ktra zostanie uyta w ramach funkcji modykujcej do zmiany zadanego rzeczownika w odpowiedni posta liczby mnogiej. 2. To jest magiczna linijka. Pobiera ona list napisw jako parametr patterns, a nastpnie przeksztaca je w list funkcji. W jaki sposb? Ot przez odwzorowanie listy napisw na list funkcji przy uyciu funkcji buildMatchAndApplyFunctions, ktra, tak si akurat skada, pobiera trzy napisy jako parametr i zwraca krotk zawierajc dwie funkcje. Oznacza to, e zmienna rules bdzie miaa ostatecznie tak sam warto, jak w poprzednim przykadzie: list dwuelementowych krotek, spord ktrych kada zawiera dwie funkcje: pierwsz jest funkcja dopasowujca, ktra wywouje re.search a drug jest funkcja modykujca, ktra wywouje re.sub. Przysigam, e nie zmylam: zmienna rules bdzie zawieraa dokadnie tak sam list funkcji, jak zawieraa w poprzednim przykadzie. Odwikajmy denicj zmiennej rules i sprawdmy: Przykad 17.11. Odwikanie denicji zmiennej rules rules = \ ( ( lambda lambda ), ( lambda lambda ), ( lambda lambda ), ( lambda lambda ) )

word: re.search([sxz]$, word), word: re.sub($, es, word)

word: re.search([^aeioudgkprt]h$, word), word: re.sub($, es, word)

word: re.search([^aeiou]y$, word), word: re.sub(y$, ies, word)

word: re.search($, word), word: re.sub($, s, word)

Przykad 17.12. Dokoczenie plural4.py def plural(noun): for matchesRule, applyRule in rules: if matchesRule(noun): return applyRule(noun)

#(1)

1. Poniewa lista regu zaszyta w zmiennej rules jest dokadnie taka sama, jak w poprzednim przykadzie, nikogo nie powinno dziwi, e funkcja plural nie

378

ROZDZIA 18. FUNKCJE DYNAMICZNE zmienia si. Pamitajmy, e jest ona zupenie oglna; pobiera list funkcji deniujcych reguy i wywouje je w podanej kolejnoci. Nie ma dla niej znaczenia, w jaki sposb reguy zostay zdeniowane. Na etapie 2 byy to osobno zdeniowane funkcje nazwane. Na etapie 3 byy zdeniowane jako anonimowe funkcje lambda. Teraz, na etapie 4, s one budowane dynamicznie poprzez odwzorowanie listy napisw przy uyciu funkcji buildMatchAndApplyFunctions. Nie ma to znaczenia; funkcja plural dziaa cay czas tak samo.

Na wypadek, gdyby jeszcze nie bolaa was od tego gowa, przyznam si, e w denicji funkcji buildMatchAndApplyFunctions znajduje si pewna subtelno, o ktrej dotychczas nie mwiem. Wrmy na chwil i przyjrzyjmy si bliej: Przykad 17.13. Blisze spotkanie z funkcj buildMatchAndApplyFunctions def buildMatchAndApplyFunctions((pattern, search, replace)): #(1)

1. Zauwaylicie podwjne nawiasy? Funkcja ta tak naprawd nie przyjmuje trzech parametrw; przyjmuje ona dokadnie jeden parametr bdcy trzyelementow krotk. W momencie wywoywania funkcji, krotka ta jest rozwijana, a trzy elementy krotki s przypisywane do trzech rnych zmiennych o nazwach pattern, search oraz replace. Jestecie ju zagubieni? Zobaczmy to w dziaaniu. Przykad 17.14. Rozwijanie krotek podczas wywoywania funkcji >>> def foo((a, b, c)): ... print c ... print b ... print a >>> parameters = (apple, bear, catnap) >>> foo(parameters) #(1) catnap bear apple 1. Poprawnym sposobem wywoania funkcji foo jest przekazanie jej trzyelementowej krotki. W momencie wywoywania tej funkcji, elementy krotki s przypisywane rnym zmiennym lokalnym wewntrz funkcji foo. Wrmy na chwil do naszego programu i sprawdmy, dlaczego trik w postaci automatycznego rozwijania krotek by w ogle potrzebny. Zauwamy, e lista patterns to lista krotek, a kada krotka posiada trzy elementy. Wywoanie map(buildMatchAndApplyFunctions, patterns) oznacza, e funkcja buildMatchAndApplyFunctions nie zostanie wywoana z trzema parametrami. Uycie map do odwzorowania listy przy pomocy funkcji zawsze odbywa si przez wywoanie tej funkcji z dokadnie jednym parametrem: kadym elementem listy. W przypadku listy patterns, kady element listy jest krotk, a wic buildMatchAndApplyFunctions zawsze jest wywoywana z krotk, przy czym automatyczne rozwinicie tej krotki zostao zastosowane w denicji funkcji buildMatchAndApplyFunctions po to, aby elementy tej krotki zostay automatycznie przypisane do nazwanych zmiennych, z ktrymi mona dalej pracowa.

18.6. PLURAL.PY, ETAP 5

379

18.6

plural.py, etap 5

Usunlimy ju z kodu powtrzenia i dodalimy wystarczajco duo abstrakcji, aby reguy zamiany rzeczownika na liczb mnog znajdoway si na licie napisw. Nastpnym logicznym krokiem bdzie umieszczenie tych napisw (deniujcych reguy) w osobnym pliku, dziki czemu bdzie mona utrzymywa list regu niezalenie od uywajcego j kodu. Na pocztku utworzymy plik tekstowy zawierajcy reguy. Nie ma tu zoonych struktur, to po prostu napisy rozdzielone spacjami (lub znakami tabulacji) w trzech kolumnach. Plik ten nazwiemy rules.en; en oznacza English, reguy te dotycz bowiem rzeczownikw jzyka angielskiego. Pniej bdziemy mogli doda pliki z reguami dla innych jzykw. Przykad 17.15. rules.en [sxz]$ [^aeioudgkprt]h$ [^aeiou]y$ $ $ $ y$ $ es es ies s

Zobaczmy, jak moemy uy tego pliku. Przykad 17.16. plural5.py import re import string def buildRule((pattern, search, replace)): return lambda word: re.search(pattern, word) and re.sub(search, replace, word) #(1) def plural(noun, language=en): lines = file(rules.%s % language).readlines() patterns = map(string.split, lines) rules = map(buildRule, patterns) for rule in rules: result = rule(noun) if result: return result #(2) #(3) #(4) #(5) #(6)

1. W dalszym cigu uywamy tutaj techniki dopenie (dynamicznego budowania funkcji, ktre uywaj zmiennych zdeniowanych na zewntrz funkcji), jednak teraz poczylimy osobne funkcje dopasowujc oraz modykujc w jedn. (Powd, dla ktrego to zrobilimy, stanie si jasny w nastpnym podrozdziale). Pozwoli to nam osign ten sam cel, jaki osigalimy przy uyciu dwch funkcji, bdziemy jedynie musieli, jak zobaczymy za chwil, troch inaczej t now funkcj wywoa. 2. Funkcja plural pobiera teraz opcjonalny parametr, language, ktry ma domyln warto en. 3. Parametru language uywamy do skonstruowania nazwy pliku, nastpnie otwieramy ten plik i wczytujemy jego zawarto do listy. Jeli language ma warto en, wwczas zostanie otworzony plik rules.en, wczytana jego zawarto, podzielona na podstawie znakw nowego wiersza, i zwrcona w postaci listy. Kada linia z pliku zostanie wczytana jako jeden element listy.

380

ROZDZIA 18. FUNKCJE DYNAMICZNE

4. Jak widzielimy wczeniej, kada linia w pliku zawiera trzy wartoci, ktre s oddzielone biaymi znakami (spacj lub znakiem tabulacji, nie ma to znaczenia). Odwzorowanie powstaej z wczytania pliku listy przy pomocy funkcji string.split pozwoli na utworzenie nowej listy, ktrej elementami s trzyelementowe krotki. Linia taka, jak: [sxz]$ $ es zostanie zamieniona w krotk ([sxz]$, $, es). Oznacza to, e w zmiennej patterns znajd si trzyelementowe krotki zawierajce napisy, dokadnie tak, jak to wczeniej, na etapie 4, zakodowalimy na sztywno. 5. Jeli patterns jest list krotek, to rules bdzie list funkcji zdeniowanych dynamicznie przy kadym wywoaniu buildRule. Wywoanie buildRule(([sxz]$, $, es)) zwrci funkcj, ktra pobiera jeden parametr, word. Kiedy zwrcona funkcja jest wywoywana, zostanie wykonany kod: re.search([sxz]$, word) and re.sub($, es, word). 6. Poniewa teraz budujemy funkcj, ktra czy w sobie dopasowanie i modykacj, musimy troch inaczej j wywoa. Jeli wywoamy t funkcj i ona co nam zwrci, to bdzie to forma rzeczownika w liczbie mnogiej; jeli za nie zwrci nic (zwrci None), oznacza to, e regua dopasowujca nie zdoaa dopasowa wyraenia do podanego rzeczownika i naley w tej sytuacji sprbowa dopasowa kolejn regu. Poprawa kodu polegaa na tym, e udao nam si cakowicie wyczy reguy tworzenia liczby mnogiej do osobnego pliku. Dziki temu nie tylko bdzie mona utrzymywa ten plik niezalenie od kodu, lecz take, dziki wprowadzeniu odpowiedniej notacji nazewniczej, uywa funkcji plural z rnymi plikami zawierajcymi reguy dla rnych jzykw. Wad tego rozwizania jest fakt, e ilekro chcemy uy funkcji plural, musimy na nowo wczytywa plik z reguami. Mylaem, e uda mi si napisa t ksik bez uywania frazy: zostawiam to jako zagadnienie jako wiczenie dla czytelnikw, ale nie mog si powstrzyma: zbudowanie mechanizmu buforujcego dla zalenego od jzyka pliku z reguami, ktry automatycznie odwiea si, jeli plik z reguami zmieni si midzy wywoaniami, zostawiam jako wiczenie dla czytelnikw. Bawcie si dobrze!

18.7. PLURAL.PY, ETAP 6

381

18.7

plural.py, etap 6

Teraz jestecie ju gotowi, aby porozmawia o generatorach. Przykad 17.17. plural6.py import re def rules(language): for line in file(rules.%s % language): pattern, search, replace = line.split() yield lambda word: re.search(pattern, word) and re.sub(search, replace, word) def plural(noun, language=en): for applyRule in rules(language): result = applyRule(noun) if result: return result Powyszy kod uywa generatora. Nie zaczn nawet tumaczy, na czym polega ta technika, dopki nie przyjrzycie si najpierw prostszemu przykadowi. Przykad 17.18. Wprowadzenie do generatorw >>> def make_counter(x): ... print entering make_counter ... while 1: ... yield x #(1) ... print incrementing x ... x = x + 1 ... >>> counter = make_counter(2) #(2) >>> counter #(3) <generator object at 0x001C9C10> >>> counter.next() #(4) entering make_counter 2 >>> counter.next() #(5) incrementing x 3 >>> counter.next() #(6) incrementing x 4 1. Obecno sowa kluczowego yield w denicji make counter oznacza, e nie jest to zwyka funkcja. To specjalny rodzaj funkcji, ktra generuje wartoci przy kadym wywoaniu. Moecie myle o niej jak o funkcji kontynuujcej swoje dziaanie: wywoanie jej zwraca obiekt generatora, ktry moe zosta uyty do generowania kolejnych wartoci x. 2. Aby uzyska instancj generatora make counter, wystarczy wywoa funkcj make counter. Zauwacie, e nie spowoduje to jeszcze wykonania kodu tej funkcji. Mona to stwierdzi na podstawie faktu, e pierwsz instrukcj w tej funkcji jest instrukcja print, a nic jeszcze nie zostao wypisane.

382

ROZDZIA 18. FUNKCJE DYNAMICZNE

3. Funkcja make counter zwraca obiekt bdcy generatorem. 4. Kiedy po raz pierwszy wywoamy next() na obiekcie generatora, zostanie wykonany kod funkcji make counter a do pierwszej instrukcji yield, ktra spowoduje zwrcenie pewnej wartoci. W tym przypadku bdzie to warto 2, poniewa utworzylimy generator wywoujc make counter(2). 5. Kolejne wywoania next() na obiekcie generatora spowoduj kontynuowanie wykonywania kodu funkcji od miejsca, w ktrym wykonywanie funkcji zostao przerwane a do kolejnego napotkania instrukcji yield. W tym przypadku nastpn lini kodu oczekujc na wykonanie jest instrukcja print, ktra wypisuje tekst incrementing x, a kolejn instrukcja przypisania x = x + 1, ktra zwiksza warto x. Nastpnie wchodzimy w kolejny cykl ptli while, w ktrym wykonywana jest instrukcja yield, zwracajca biec warto x (obecnie jest to 3). 6. Kolejne wywoanie counter.next spowoduje wykonanie tych samych instrukcji, przy czym tym razem x osignie warto 4. I tak dalej. Poniewa make counter zawiera nieskoczon ptl, wic teoretycznie moglibymy robi to w nieskoczono: generator zwikszaby warto x i wypluwa jego biec warto. Spjrzmy jednak na bardziej produktywne przypadki uycia generatorw. Przykad 17.19. Uycie generatorw w miejsce rekurencji def fibonacci(max): a, b = 0, 1 #(1) while a < max: yield a #(2) a, b = b, a+b #(3) 1. Cig Fibonacciego skada si z liczb, z ktrych kada jest sum dwch poprzednich (za wyjtkiem dwch pierwszych liczb tego cigu). Cig rozpoczynaj liczby 0 i 1, kolejne wartoci rosn powoli, nastpnie rnice midzy nimi coraz szybciej si zwikszaj. Aby rozpocz generowanie tego cigu, potrzebujemy dwch zmiennych: a ma warto 0, b ma warto 1. 2. a to bieca warto w cigu, wic zwracamy j 3. b jest kolejn wartoci, wic przypisujemy j do a, jednoczenie obliczajc kolejn warto (a+b) i przypisujc j do b do pniejszego uycia. Zauwacie, e dzieje si to rwnolegle; jeli a ma warto 3 a b ma warto 5, wwczas w wyniku wartociowania wyraenia a, b = b, a+b do zmiennej a zostanie przypisana warto 5 (wczeniejsza warto b), a do b warto 8 (suma poprzednich wartoci a i b). Mamy wic funkcj, ktra wyrzuca z siebie kolejne wartoci cigu Fibonacciego. Oczywicie moglibymy to samo osign przy pomocy rekurencji, jednak ten sposb jest znacznie prostszy w zapisie. No i wietnie si sprawdza w przypadku ptli: Przykad 17.20. Generatory w ptlach >>> for n in fibonacci(1000): #(1) ... print n, #(2) 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

18.7. PLURAL.PY, ETAP 6

383

1. Generatory (takie jak fibonacci) mog by uywane bezporednio w ptlach. Ptla for utworzy obiekt generatora i bdzie wywoywa na nim metod next(), przypisujc j do zmiennej sterujcej ptli (n). 2. W kadym przebiegu ptli for n bdzie miao now warto, zwrcon przez generator w instrukcji yield funkcji fibonacci, i ta warto zostanie wypisana. Jeli generatorowi fibonacci skocz si generowane wartoci (zmienna a przekroczy max, ktre w tym przypadku jest rwne 1000), ptla for zakoczy dziaanie. OK, wrmy teraz do funkcji plural i sprawdmy, jak tam zosta uyty generator. Przykad 17.21. Generator tworzcy dynamicznie funkcje def rules(language): for line in file(rules.%s % language): #(1) pattern, search, replace = line.split() #(2) yield lambda word: re.search(pattern, word) and re.sub(search, replace, word) #(3) def plural(noun, language=en): for applyRule in rules(language): result = applyRule(noun) if result: return result

#(4)

1. for line in file(...) to czsto spotykany w jzyku Python idiom sucy wczytaniu po jednej wszystkich linii z pliku. Dziaa on w ten sposb, e file zwraca generator, ktrego metoda next() zwraca kolejn lini z pliku. To szalenie fajna sprawa, robi si cay mokry, kiedy tylko o tym pomyl. 2. Tu nie ma adnej magii. Pamitajcie, e kada linia w pliku z reguami zawiera trzy wartoci oddzielone biaym znakiem, a wic line.split zwrci trzyelementow krotk, a jej trzy wartoci s tu przypisywane do trzech zmiennych lokalnych. 3. A teraz nastpuje instrukcja yield. Co zwraca yield? Zwraca ona funkcj zbudowan dynamicznie przy uyciu notacji lambda, bdc w rzeczywistoci domkniciem (uywa bowiem zmiennych lokalnych pattern, search oraz replace jako staych). Innymi sowy, rules jest generatorem ktry generuje funkcje reprezentujce reguy. 4. Jeli rules jest generatorem, to znaczy, e moemy uy go bezporednio w ptli for. W pierwszym przebiegu ptli wywoanie funkcji rules spowoduje otworzenie pliku z reguami, przeczytanie pierwszej linijki oraz dynamiczne zbudowanie funkcji, ktra dopasowuje i modykuje na podstawie pierwszej odczytanej reguy z pliku, po czym nastpi zwrcenie zbudowanej w ten sposb funkcji w instrukcji yield. W drugim przebiegu ptli for wykonywanie funkcji rules rozpocznie si tam, gdzie si ostatnio zakoczyo (a wic w rodku ptli for line in file(...)), zostanie wic odczytana druga linia z pliku z reguami, zostanie dynamicznie zbudowana inna funkcja dopasowujca i modykujca na podstawie zapisanej w pliku reguy, po czym ta funkcja zostanie zwrcona w instrukcji yield. I tak dalej.

384

ROZDZIA 18. FUNKCJE DYNAMICZNE

Co takiego osignlimy w porwnaniu z etapem 5? W etapie 5 wczytywalimy cay plik z reguami i budowalimy list wszystkich moliwych regu zanim nawet wyprbowalimy pierwsz z nich. Teraz, przy uyciu generatora, podchodzimy do sprawy w sposb leniwy: otwieramy plik i wczytujemy pierwsz lini w celu zbudowania funkcji, jednak jeli ta funkcja zadziaa (regua zostanie dopasowana, a rzeczownik zmodykowany), nie bdziemy niepotrzebnie czyta dalej linii z pliku ani tworzy jakichkolwiek innych funkcji.

18.8. PODSUMOWANIE

385

18.8

Funkcje dynamiczne - podsumowanie

W tym rozdziale rozmawialimy o wielu zaawansowanych technikach programowania. Naley pamita, e nie wszystkie nadaj si do stosowania w kadej sytuacji. Powinnicie teraz dobrze orientowa si w nastpujcych technikach: Zastpowanie napisw przy pomocy wyrae regularnych. Traktowanie funkcji jak obiektw, przechowywanie ich na listach, przypisywanie ich do zmiennych i wywoywanie ich przy pomocy tych zmiennych. Budowanie funkcji dynamicznych przy uyciu notacji lambda. Budowanie dopenie funkcji dynamicznych, ktrych denicja uywa otaczajcych je zmiennych w charakterze wartoci staych. Budowanie generatorw funkcji, ktre mona kontynuowa, dziki ktrym realizowana jest pewna przyrostowa logika, a za kadym ich wywoaniem moe zosta zwrcona inna warto. Dodawanie abstrakcji, dynamiczne budowanie funkcji, tworzenie domkni i uywanie generatorw moe znacznie uproci kod, moe sprawi, e stanie si on czytelniejszy i bardziej elastyczny. Moe jednak sprawi, e ten sam kod stanie si pniej znacznie trudniejszy do debugowania. Do was naley znalezienie waciwej rwnowagi midzy prostot i moc.

386

ROZDZIA 18. FUNKCJE DYNAMICZNE

Rozdzia 19

Optymalizacja szybkoci

387

388

ROZDZIA 19. OPTYMALIZACJA SZYBKOCI

19.1

Optymalizacja szybkoci

Optymalizacja szybkoci jest niesamowit spraw. Tylko dlatego, e Python jest jzykiem interpretowanym, nie oznacza, e nie powinnimy martwi si o optymalizacj pod ktem szybkoci dziaania. Jednak nie naley si tym a tak bardzo przejmowa.

Nurkujemy
Istnieje sporo puapek zwizanych z optymalizacj kodu, ciko stwierdzi, od czego naleaoby zacz. Zacznijmy wic tutaj: czy jestemy pewni, e w ogle powinnimy si do tego zabiera? Czy nasz kod jest na prawd tak kiepski? Czy warto powici na to czas? Ile tak na prawd czasu podczas dziaania aplikacji bdzie zajmowao wykonywanie kodu, w porwnaniu z czasem powiconym na oczekiwanie na odpowied zdalnej bazy danych czy te akcj uytkownika? Po drugie, czy jestemy pewni, e skoczylimy kodowanie? Przedwczesna optymalizacja jest jak nakadanie lodowej polewy na w p upieczone ciasto. Powicamy godziny czy nawet dni na optymalizacj kodu pod ktem wydajnoci, aby w kocu przekona si, e nie robi on tego, co bymy chcieli. Stracony czas, robota wyrzucona w boto. Nie chodzi o to, e optymalizacja kodu jest bezwartociowa, raczej powinnimy spojrze z dystansu na cay system aby nabra przewiadczenia, czy czas przeznaczony na ni jest najlepsz inwestycj. Kada minuta powicona na optymalizacj kodu jest minut, ktrej nie powicamy na dodawanie nowych funkcjonalnoci, pisanie dokumentacji, zabaw z naszymi dziemi czy te pisanie testw jednostkowych. A no wanie, testy jednostkowe. Nie powinnimy nawet zaczyna optymalizacji nie majc penego zestawu takich testw. Ostatni rzecz jakiej bymy chcieli to pojawienie si nowego bdu podczas dubania w algorytmie. Majc na uwadze powysze porady, zerknijmy na niektre techniki optymalizacji kodu Pythona. Nasz kod to implementacja algorytmu Soundex. Soundex by metod kategoryzowania nazwisk w spisie ludnoci w Stanach Zjednoczonych na pocztku 20 wieku. Grupowa on podobnie brzmice sowa, przez co naukowcy mieli szans na odnalezienie nawet bdnie zapisanych nazwisk. Soundex jest do dzi uywany gwnie z tego samego powodu, lecz oczywici w tym celu uywane s skomputeryzowane bazy danych. Wikszo silnikw baz danych posiada funkcj Soundex. Istnieje kilka nieco rnicych si od siebie wersji algorytmu Soundex. Poniej znajduje si ta, ktrej uywamy w tym rozdziale: 1. We pierwsz liter nazwy. 2. Przekszta pozostae litery na cyfry wedug poniszej listy: B, F, P, oraz V staj si 1. C, G, J, K, Q, S, X, oraz Z staj si 2. D oraz T staj si 3. L staje si 4. M oraz N staj si 5. R staje si 6.

19.1. NURKUJEMY Wszystkie pozostae litery staj si 9. 1. Usu wszystkie duplikaty. 2. Usu wszystkie dziewitki.

389

3. Jeli wynik jest krtszy ni cztery znaki (pierwsza litera plus trzy cyfry), dopenij wynik zerami z prawej strony cigu do czterech znakw. 4. Jeeli wynik jest duszy ni cztery znaki, pozbd si wszystkiego po czwartym znaku. Na przykad, moje imi, Pilgrim, staje si P942695. Nie posiada nastpujcych po sobie duplikatw, wic nic tutaj nie robimy. Nastpnie usuwamy 9tki pozostawiajc P4265. Jednak znakw jest za duo, wic pomijamy ich nadmiar w wyniku otrzymujc P426. Inny przykad: Woo staje si W99, ktre staje si W9, ktre staje si W, ktre natomiast dopeniamy zerami z prawej strony do czterech znakw aby otrzyma W000. A oto pierwsze podejcie do funkcji Soundex: Przykad 18.1. soundex/stage1/soundex1a.py import string, re charToSoundex = {"A": "B": "C": "D": "E": "F": "G": "H": "I": "J": "K": "L": "M": "N": "O": "P": "Q": "R": "S": "T": "U": "V": "W": "X": "Y": "Z": def soundex(source): "9", "1", "2", "3", "9", "1", "2", "9", "9", "2", "2", "4", "5", "5", "9", "1", "2", "6", "2", "3", "9", "1", "9", "2", "9", "2"}

390

ROZDZIA 19. OPTYMALIZACJA SZYBKOCI "convert string to Soundex equivalent" # Soundex requirements: # source string must be at least 1 character # and must consist entirely of letters allChars = string.uppercase + string.lowercase if not re.search(^[%s]+$ % allChars, source): return "0000" # Soundex algorithm: # 1. make first character uppercase source = source[0].upper() + source[1:] # 2. translate all other characters to Soundex digits digits = source[0] for s in source[1:]: s = s.upper() digits += charToSoundex[s] # 3. remove consecutive duplicates digits2 = digits[0] for d in digits[1:]: if digits2[-1] != d: digits2 += d # 4. remove all "9"s digits3 = re.sub(9, , digits2) # 5. pad end with "0"s to 4 characters while len(digits3) < 4: digits3 += "0" # 6. return first 4 characters return digits3[:4]

if __name__ == __main__: from timeit import Timer names = (Woo, Pilgrim, Flingjingwaller) for name in names: statement = "soundex(%s)" % name t = Timer(statement, "from __main__ import soundex") print name.ljust(15), soundex(name), min(t.repeat())

19.2. KORZYSTANIE Z MODUU TIMEIT

391

19.2

Korzystanie z moduu timeit

Najwaniejsz rzecz, jak powinnicie wiedzie na temat optymalizacji kodu w jzyku Python jest to, e nie powinnicie pisa wasnej funkcji mierzcej czas wykonania kodu. Pomiar czasu wykonania niewielkich fragmentw kodu to zagadnienie niezwykle zoone. Jak duo czasu procesora zuywa wasz komputer podczas dziaania tego kodu? Czy istniej jakie inne procesy dziaajce w tym samym czasie w tle? Czy jestecie tego pewni? Na kadym nowoczesnym komputerze dziaaj jakie procesy w tle, niektre przez cay czas a niektre okresowo. Zadania w cronie zostaj uruchomione w dokadnie okrelonych porach; usugi dziaajce w tle co jaki czas budz si, aby wykona rne poyteczne prace, takie jak sprawdzenie nowej poczty, poczenie si z serwerami typu instant messaging, sprawdzanie, czy istniej ju aktualizacje zainstalowanych programw, skanowanie antywirusowe, sprawdzanie, czy w cigu ostatnich 100 nanosekund do napdu CD zostaa woona pyta i tak dalej. Zanim rozpoczniecie testy z pomiarami czasu, wyczcie wszystko i odczcie komputer od sieci. Nastpnie wyczcie to wszystko, o czym zapomnielicie za pierwszym razem, pniej wyczcie usug, ktra sprawdza, czy nie przyczylicie si ponownie do sieci, a nastpnie... Do tego dochodz jeszcze rne aspekty wprowadzane przez mechanizm pomiaru czasu. Czy interpreter jzyka Python zapamituje raz wyszukane nazwy metod? Czy zapamituje bloki skompilowanego kodu? Wyraenia regularne? Czy w waszym kodzie objawi si jakie skutki uboczne, gdy uruchomicie go wicej ni jeden raz? Nie zapominajcie, e mamy tutaj do czynienia z maymi uamkami sekundy, wic mae bdy w mechanizmie pomiaru czasu przeo si na nienaprawialne zafaszowanie rezultatw tych pomiarw. W spoecznoci Pythona funkcjonuje powiedzenie: Python z bateriami w zestawie. Nie piszcie wasnego mechanizmu mierzcego czas. Python 2.3 posiada sucy do tego celu doskonay modu o nazwie timeit. Przykad 18.2. Wprowadzenie do moduu timeit >>> import timeit >>> t = timeit.Timer("soundex.soundex(Pilgrim)", ... "import soundex") #(1) >>> t.timeit() #(2) 8.21683733547 >>> t.repeat(3, 2000000) #(3) [16.48319309109, 16.46128984923, 16.44203948912] 1. W module timeit zdeniowana jest jedna klasa, Timer, ktrej konstruktor przyjmuje dwa argumenty. Obydwa argumenty s napisami. Pierwszy z nich to instrukcja, ktrej czas wykonania chcemy zmierzy; w tym przypadku mierzymy czas wywoania funkcji soundex z moduu soundex z parametrem Pilgrim. Drugi argument przekazywany do konstruktora obiektu Timer to instrukcja import, ktra ma przygotowa rodowisko do wykonywania instrukcji, ktrej czas trwania mierzymy. Wewntrzne dziaanie timeit polega na przygotowaniu wirtualnego, wyizolowanego rodowiska, wykonaniu instrukcji przygotowujcych (zaimportowaniu moduu soundex) oraz kompilacji i wykonaniu instrukcji poddawanej pomiarowi (wywoanie funkcji soundex). 2. Najatwiejsza rzecz, jak mona zrobi z obiektem klasy Timer, to wywoanie na nim metody timeit(), ktra wywouje podan funkcj milion razy i zwraca

392

ROZDZIA 19. OPTYMALIZACJA SZYBKOCI liczb sekund, jak zajo to wywoywanie.

3. Inn wan metod obiektu Timer jest repeat(), ktra pobiera dwa opcjonalne argumenty. Pierwszy argument to liczba okrelajca ile razy ma by powtrzony cay test; drugi argument to liczba okrelajca ile razy ma zosta wykonana mierzona instrukcja w ramach jednego testu. Obydwa argumenty s opcjonalne, a ich wartoci domylne to, odpowiednio, 3 oraz 1000000. Metoda repeat() zwraca list zawierajc czas wykonania kadego testu wyraony w sekundach. Modu timeit mona rwnie wykorzysta, aby z linii polece zmierzy czas trwania dowolnego programu napisanego w jzyku Python, bez koniecznoci modykowania jego rde. Opcje linii polece dostpne s w dokumentacji moduu timeit. Zauwacie, e repeat() zwraca list czasw. Czasy te prawie nigdy nie bd identyczne; jest to spowodowane faktem, e interpreter jzyka Python nie zawsze otrzymuje tak sam ilo czasu procesora (oraz istnieniem rnych procesw w tle, ktrych nie tak atwo si pozby). Wasza pierwsza myl moe by taka: Obliczmy redni i uzyskajmy Prawdziw Warto. W rzeczywistoci takie podejcie jest prawie na pewno ze. Testy, ktre trway duej, nie trway duej z powodu odchyle w waszym kodzie albo kodzie interpretera jzyka; trway duej, poniewa w tym samym czasie w systemie dziaay w tle inne procesy albo z powodw niezalenych od interpretera jzyka, ktrych nie mona byo cakowicie wyeliminowa. Jeli rnice w pomiarach czasu dla rnych testw rni si o wicej ni kilka procent, wwczas s one zbyt znaczne, aby ufa takim pomiarom. W przeciwnym przypadku jako czas trwania testu naley przyj warto najmniejsz, a inne wartoci odrzuci. Python posiada uyteczn funkcj min, ktra zwraca najmniejsz warto z podanej w parametrze listy, a ktr mona w tym przypadku wykorzysta: >>> min(t.repeat(3, 1000000)) 8.22203948912

19.3. OPTYMALIZACJA WYRAE REGULARNYCH

393

19.3

Optymalizacja wyrae regularnych

Pierwsz rzecz, jak musi sprawdzi funkcja Soundex, jest to, czy na wejciu znajduje si niepusty cig znakw. Jak mona to najlepiej zrobi? Jeli odpowiedzielicie: uywajc wyrae regularnych, to idziecie do kta i kontemplujecie tam swoje ze instynkty. Wyraenia regularne prawie nigdy nie stanowi dobrej odpowiedzi; jeli to moliwe, naley ich raczej unika. Nie tylko ze wzgldu na wydajno, lecz take z tego prostego powodu, e s one niezwykle trudne w debugowaniu i w dalszym utrzymaniu. Ale rwnie ze wzgldu na wydajno. Poniszy fragment kodu pochodzi z programu soundex/stage1/soundex1a.py i sprawdza, czy zmienna source bdca argumentem funkcji jest napisem zbudowanym wycznie z liter, przy czym zawiera co najmniej jedn liter (nie jest pustym napisem): allChars = string.uppercase + string.lowercase if not re.search(^[%s]+$ % allChars, source): return "0000" Czy soundex1a.py to wydajny program? Dla uatwienia, w sekcji main skryptu znajduje si poniszy kod, ktry wywouje modu timeit, konstruuje test do pomiaru zoony z trzech rnych napisw, testuje kady napis trzy razy i dla kadego z napisw wywietla minimalny czas: if __name__ == __main__: from timeit import Timer names = (Woo, Pilgrim, Flingjingwaller) for name in names: statement = "soundex(%s)" % name t = Timer(statement, "from __main__ import soundex") print name.ljust(15), soundex(name), min(t.repeat()) A wic czy soundex1a.py uywajcy wyrae regularnych jest wydajny? C:\samples\soundex\stage1>python soundex1a.py Woo W000 19.3356647283 Pilgrim P426 24.0772053431 Flingjingwaller F452 35.0463220884 Moemy si spodziewa, algorytm ten bdzie dziaa znacznie duej, jeli zostanie on wywoany ze znacznie duszymi napisami. Moemy zrobi wiele, aby zmniejszy t szczelin (sprawi, e funkcja bdzie potrzebowaa wzgldnie mniej czasu dla duszych danych wejciowych), jednak natura tego algorytmu wskazuje na to, e nie bdzie on nigdy dziaa w staym czasie. Warto mie na uwadze, e testujemy reprezentatywn prbk imion. Woo to przypadek trywialny; to imi zostanie skrcone do jednej litery i uzupenione zerami. Pilgrim to rednio zoony przypadek, posiadajcy redni dugo i zawierajcy zarwno litery znaczce, jak i te, ktre zostan zignorowane. Flingjingwaller za to wyjtkowo dugie imi zawierajce nastpujce po sobie powtrzenia. Inne testy mogyby rwnie by przydatne, jednak te trzy pokrywaj duy zakres przypadkw. Co zatem z wyraeniem regularnym? C, okazao si ono nieefektywne. Poniewa wyraenie sprawdza zakresy znakw (due litery A-Z oraz mae a-z), moemy uy skrconej skadni wyrae regularnych. Poniej soundex/stage1/soundex1b.py:

394

ROZDZIA 19. OPTYMALIZACJA SZYBKOCI if not re.search(^[A-Za-z]+$, source): return "0000"

Modu timeit mwi, e soundex1b.py jest odrobin szybszy, ni soundex1a.py, nie ma w tym jednak nic, czy mona by si ekscytowa: C:\samples\soundex\stage1>python soundex1b.py Woo W000 17.1361133887 Pilgrim P426 21.8201693232 Flingjingwaller F452 32.7262294509 Jak widzielimy w podrozdziale 15.3 [Python/Refaktoryzacja], wyraenia regularne mog zosta skompilowane i uyte powtrnie, dziki czemu osiga si lepsze rezultaty. Ze wzgldu na to, e nasze wyraenie regularne nie zmienia si midzy wywoaniami, kompilujemy je i uywamy wersji skompilowanej. Poniej znajduje si fragment soundex/stage1/soundex1c.py: isOnlyChars = re.compile(^[A-Za-z]+$).search def soundex(source): if not isOnlyChars(source): return "0000" Uycie skompilowanej wersji wyraenia regularnego jest ju znacznie szybsze: C:\samples\soundex\stage1>python soundex1c.py Woo W000 14.5348347346 Pilgrim P426 19.2784703084 Flingjingwaller F452 30.0893873383 Ale czy to nie jest przypadkiem bdna cieka? Logika jest przecie prosta: napis wejciowy nie moe by pusty i musi by cay zoony z liter. Czy nie byoby szybciej, gdybymy pozbyli si wyraenia regularnego i zapisali ptl, ktra sprawdza kady znak? Poniej soundex/stage1/soundex1d.py: if not source: return "0000" for c in source: if not (A <= c <= Z) and not (a <= c <= z): return "0000" Okazuje si, e ta technika w soundex1d.py nie jest szybsza, ni uycie skompilowanego wyraenia regularnego (ale jest szybsza, ni uycie nieskompilowanego wyraenia regularnego): C:\samples\soundex\stage1>python soundex1d.py Woo W000 15.4065058548 Pilgrim P426 22.2753567842 Flingjingwaller F452 37.5845122774

19.3. OPTYMALIZACJA WYRAE REGULARNYCH

395

Dlaczego soundex1d.py nie jest szybszy? Odpowied ley w naturze jzyka Python, ktry jest jzykiem interpretowanym. Silnik wyrae regularnych napisany jest w C i skompilowany odpowiednio do architektury waszego komputera. Z drugiej strony, ptla napisana jest w jzyku Python i jest uruchamiana przez interpreter jzyka Python. Cho ptla jest stosunkowo prosta, jej prostota nie wystarcza, aby pozby si narzutu zwizanego z tym, e jest ona interpretowana. Wyraenia regularne nigdy nie s dobrym rozwizaniem... chyba, e akurat s. Okazuje si, e Python posiada pewn star metod w klasie string. Jestecie cakowicie usprawiedliwieni, jeli o niej nie wiedzielicie, bowiem nie wspominaem jeszcze o niej w tej ksice. Metoda nazywa si isalpha() i sprawdza, czy napis skada si wycznie z liter. Oto soundex/stage1/soundex1e.py: if (not source) and (not source.isalpha()): return "0000" Czy zyskalimy co uywajc tej specycznej metody w soundex1e.py? Troch zyskalimy. C:\samples\soundex\stage1>python soundex1e.py Woo W000 13.5069504644 Pilgrim P426 18.2199394057 Flingjingwaller F452 28.9975225902 Przykad 18.3. Dotychczas najlepszy rezultat: soundex/stage1/soundex1e.py import string, re charToSoundex = {"A": "B": "C": "D": "E": "F": "G": "H": "I": "J": "K": "L": "M": "N": "O": "P": "Q": "R": "S": "T": "U": "V": "W": "9", "1", "2", "3", "9", "1", "2", "9", "9", "2", "2", "4", "5", "5", "9", "1", "2", "6", "2", "3", "9", "1", "9",

396 "X": "2", "Y": "9", "Z": "2"}

ROZDZIA 19. OPTYMALIZACJA SZYBKOCI

def soundex(source): if (not source) and (not source.isalpha()): return "0000" source = source[0].upper() + source[1:] digits = source[0] for s in source[1:]: s = s.upper() digits += charToSoundex[s] digits2 = digits[0] for d in digits[1:]: if digits2[-1] != d: digits2 += d digits3 = re.sub(9, , digits2) while len(digits3) < 4: digits3 += "0" return digits3[:4] if __name__ == __main__: from timeit import Timer names = (Woo, Pilgrim, Flingjingwaller) for name in names: statement = "soundex(%s)" % name t = Timer(statement, "from __main__ import soundex") print name.ljust(15), soundex(name), min(t.repeat())

19.4. OPTYMALIZACJA PRZESZUKIWANIA SOWNIKA

397

19.4

Optymalizacja przeszukiwania sownika

Kolejnym krokiem w algorytmie Soundex jest przeksztacenie znakw w cyfry wedug pewnego szczeglnego wzorca. Jaki jest najlepszy sposb, aby to zrobi? Najbardziej oczywiste rozwizanie polega na zdeniowaniu sownika, w ktrym poszczeglne znaki s kluczami, a wartociami odpowiadajce im cyfry, a nastpnie przeszukiwaniu sownika dla kadego pojawiajcego si znaku. Tak wanie dziaa program soundex/stage1/soundex1c.py (ktry osiga najlepsze jak do tej pory rezultaty wydajnociowe): charToSoundex = {"A": "B": "C": "D": "E": "F": "G": "H": "I": "J": "K": "L": "M": "N": "O": "P": "Q": "R": "S": "T": "U": "V": "W": "X": "Y": "Z": "9", "1", "2", "3", "9", "1", "2", "9", "9", "2", "2", "4", "5", "5", "9", "1", "2", "6", "2", "3", "9", "1", "9", "2", "9", "2"}

def soundex(source): # ... input check omitted for brevity ... source = source[0].upper() + source[1:] digits = source[0] for s in source[1:]: s = s.upper() digits += charToSoundex[s] Mierzylimy ju czas wykonania soundex1c.py; oto, jak si przestawiaj pomiary: C:\samples\soundex\stage1>python soundex1c.py Woo W000 14.5341678901 Pilgrim P426 19.2650071448 Flingjingwaller F452 30.1003563302

398

ROZDZIA 19. OPTYMALIZACJA SZYBKOCI

Powyszy kod jest prosty, ale czy stanowi najlepsze moliwe rozwizanie? Nieefektywne wydaje si w szczeglnoci wywoywanie na kadym znaku metody upper(); zrobilibymy prawdopodobnie lepiej wywoujc upper() na caym napisie wejciowym. Dochodzi do tego kwestia przyrostowego budowania napisu zoonego z cyfr. Takie przyrostowe budowanie napisu jest niezwykle niewydajne; wewntrznie interpreter jzyka musi w kadym przebiegu ptli utworzy nowy napis, a nastpnie zwolni stary. Wiadomo jednak, e Python wietnie sobie radzi z listami. Moe automatycznie potraktowa napis jako list znakw. List za atwo przeksztaci z powrotem do napisu przy uyciu metody join(). Poniej soundex/stage2/soundex2a.py, ktry konwertuje litery na cyfry przy uyciu metody join i notacji lambda: def soundex(source): # ... source = source.upper() digits = source[0] + "".join(map(lambda c: charToSoundex[c], source[1:])) Co ciekawe, program soundex2a.py nie jest wcale szybszy: C:\samples\soundex\stage2>python soundex2a.py Woo W000 15.0097526362 Pilgrim P426 19.254806407 Flingjingwaller F452 29.3790847719 Narzut wprowadzony przez funkcj anonimow utworzon przy pomocy notacji lambda przekroczy cay zysk wydajnoci, jaki osignlimy uywajc napisu w charakterze listy znakw. Program soundex/stage2/soundex2b.py w miejsce notacji lambda uywa wyrae listowych: source = source.upper() digits = source[0] + "".join([charToSoundex[c] for c in source[1:]]) Uycie wyrae listowych w soundex2b.py jest szybsze ni uywanie notacji lambda, ale wci nie jest szybsze, ni oryginalny kod (soundex1c.py, w ktrym napis wynikowy jest budowany przyrostowo): C:\samples\soundex\stage2>python soundex2b.py Woo W000 13.4221324219 Pilgrim P426 16.4901234654 Flingjingwaller F452 25.8186157738 Nadszed czas na wprowadzenie radykalnej zmiany w naszym podejciu. Przeszukiwanie sownika to narzdzie oglnego zastosowania. Kluczami w sownikach mog by napisy dowolnej dugoci (oraz wiele innych typw danych), jednak w tym przypadku posugiwalimy sie jedynie napisami o dugoci jednego znaku, zarwno w charakterze klucza, jak i wartoci. Okazuje si, e jzyk Python posiada specjalizowan funkcj suc do obsugi dokadnie tej sytuacji, funkcj o nazwie string.maketrans. Oto program soundex/stage2/soundex2c.py:

19.4. OPTYMALIZACJA PRZESZUKIWANIA SOWNIKA

399

allChar = string.uppercase + string.lowercase charToSoundex = string.maketrans(allChar, "91239129922455912623919292" * 2) def soundex(source): # ... digits = source[0].upper() + source[1:].translate(charToSoundex) Co waciwie si tu dzieje? Funkcja string.maketrans tworzy wektor przeksztace midzy dwoma napisami: pierwszym i drugim argumentem. W tym przypadku pierwszym argumentem jest napis ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz, a drugim napis 9123912992245591262391929291239129922455912623919292. Rozpoznajecie ten wzorzec? To ten sam, ktrego uywalimy w przypadku sownika: A jest przeksztacane do 9, B do 1, C do 2 i tak dalej. Nie jest to jednak sownik; to specjalizowana struktura danych, do ktrej mamy dostp przy uyciu metody translate(), przeksztacajcej kady znak argumentu w odpowiadajc mu cyfr, zgodnie z wektorem przeksztace zdeniowanym w string.maketrans. Modu timeit pokazuje, e program soundex2c.py jest znacznie szybszy ni ten, w ktrym deniowalimy sownik, iterowalimy w ptli po napisie wejciowym i budowalimy przyrostowo napis wyjciowy: C:\samples\soundex\stage2>python soundex2c.py Woo W000 11.437645008 Pilgrim P426 13.2825062962 Flingjingwaller F452 18.5570110168 Nie uda si nam osign o wiele lepszych rezultatw ni pokazany wyej. Specjalizowana funkcja jzyka Python robi dokadnie to, czego potrzebujemy; uywamy wic jej i idziemy dalej. Przykad 18.4. Dotychczas najlepszy rezultat: soundex/stage2/soundex2c.py import string, re allChar = string.uppercase + string.lowercase charToSoundex = string.maketrans(allChar, "91239129922455912623919292" * 2) isOnlyChars = re.compile(^[A-Za-z]+$).search def soundex(source): if not isOnlyChars(source): return "0000" digits = source[0].upper() + source[1:].translate(charToSoundex) digits2 = digits[0] for d in digits[1:]: if digits2[-1] != d: digits2 += d digits3 = re.sub(9, , digits2) while len(digits3) < 4: digits3 += "0" return digits3[:4] if __name__ == __main__: from timeit import Timer

400

ROZDZIA 19. OPTYMALIZACJA SZYBKOCI names = (Woo, Pilgrim, Flingjingwaller) for name in names: statement = "soundex(%s)" % name t = Timer(statement, "from __main__ import soundex") print name.ljust(15), soundex(name), min(t.repeat())

19.5. OPTYMALIZACJA OPERACJI NA LISTACH

401

19.5

Optymalizacja operacji na listach

Optymalizacja operacji na listach


Trzecim krokiem a optymalizacji algorytmu soundex jest eliminacja kolejnych powtarzajcych si cyfr. Jak najlepiej to zrobi? Taki kod otrzymalimy dotd, znajduje si on w soundex/stage2/soundex2c.py: digits2 = digits[0] for d in digits[1:]: if digits2[-1] != d: digits2 += d Takie wyniki wydajnociowe otrzymujemy dla soundex2c.py: C:\samples\soundex\stage2>python soundex2c.py Woo W000 12.6070768771 Pilgrim P426 14.4033353401 Flingjingwaller F452 19.7774882003 Pierwsz rzecz do rozwaenia jest efektywno kontrola digits[-1] w kadej iteracji ptli. Czy indeksowanie listy jest kosztowne? A moe lepiej przechowywa ostatni cyfr w oddzielnej zmiennej i sprawdza ni zamiast listy? Odpowied na to pytanie pomoe nam znale soundex/stage3/soundex3a.py: digits2 = last_digit = for d in digits: if d != last_digit: digits2 += d last_digit = d soundex3a.py nie dziaa ani troch szybciej ni soundex2c.py, a nawet moe by troszeczk wolniejsze (aczkolwiek jest to za maa rnica, aby co powiedzie pewnie): C:\samples\soundex\stage3>python soundex3a.py Woo W000 11.5346048171 Pilgrim P426 13.3950636184 Flingjingwaller F452 18.6108927252 Dlaczego soundex3a.py nie jest szybsze? Okazuje si, e indeksowanie list w Pythonie jest ekstremalnie efektywne. Powtarzenie dostpu do digits2[-1] nie stanowi w ogle problemu. Z innej strony, kiedy manualnie zarzdzamy ostatni cyfr w oddzielnej zmiennej, korzystamy z dwch przypisa do zmiennych dla kadej przechowywanej cyfry, a te operacje zastpuj may koszt zwizany z korzystania z listy. Sprbujemy teraz czego radykalnie innego. Jest moliwe, aby traktowa dany napis jako list znakw, zatem mona by byo wykorzysta wyraenie listowe, aby przeiterowa list znakw. Jednak wystpuje problem zwizany z tym, e potrzebujemy dostpu do poprzedniego znaku w licie, a to nie jest proste w przypadku prostych wyrae listowych. Jakkolwiek jest moliwe tworzenie listy liczbowych indeksw za pomoc wbudowanej funkcji range(), a nastpnie wykorzysta te indeksy do stopniowego przeszukiwania listy i wkadania kadego znaku rnego od znaku poprzedzajcego. Dziki temu

402

ROZDZIA 19. OPTYMALIZACJA SZYBKOCI

otrzymamy list znakw, a nastpnie moemy wykorzysta metod acucha znakw join(), a by zrekonstruowa z tego list. Poniej mamy soundex/stage3/soundex3b.py: digits2 = "".join([digits[i] for i in range(len(digits)) if i == 0 or digits[i-1] != digits[i]]) Czy jest to szybsze? Jednym sowem, nie. C:\samples\soundex\stage3>python soundex3b.py Woo W000 14.2245271396 Pilgrim P426 17.8337165757 Flingjingwaller F452 25.9954005327 By moe szybkie techniki powinny si skupia wok acuchw znakw. Python moe konwertowa acuch znakw na list znakw za pomoc jednego polecenia: list(abc), ktre zwrci [a, b, c]. Ponadto listy mog by bardzo szybko modykowane w miejscu. Zamiast zwiksza liczb tworzonych nowych list (lub acuchw znakw) z naszego pocztkowego acucha, dlaczego by nie przenie wszystkich elementw do pojedynczej listy? Poniej przedstawiono soundex/stage3/soundex3c.py, ktry modykuje list w miejscu i usuwa kolejno duplujce si elementy: digits = list(source[0].upper() + source[1:].translate(charToSoundex)) i=0 for item in digits: if item==digits[i]: continue i+=1 digits[i]=item del digits[i+1:] digits2 = "".join(digits) Czy jest to szybsze od soundex3a.py lub soundex3b.py? Nie, w rzeczywistoci jest to jeszcze wolniejsze: C:\samples\soundex\stage3>python soundex3c.py Woo W000 14.1662554878 Pilgrim P426 16.0397885765 Flingjingwaller F452 22.1789341942 Cigle nie wykonalimy tutaj adnego podstpu, z wyjtkiem tego, e wykorzystalimy i wyprbowalimy kilka mdrych technik. Najszybszym kodem, ktry jak dotd widzielimy nadal pozosta orygina, najbardziej prosta metoda (soundex2c.py). Czasami nie popaca by mdrym. Przykad 18.5 Najlepszy wynik do tej pory: soundex/stage2/soundex2c.py import string, re allChar = string.uppercase + string.lowercase charToSoundex = string.maketrans(allChar, "91239129922455912623919292" * 2) isOnlyChars = re.compile(^[A-Za-z]+$).search

19.5. OPTYMALIZACJA OPERACJI NA LISTACH

403

def soundex(source): if not isOnlyChars(source): return "0000" digits = source[0].upper() + source[1:].translate(charToSoundex) digits2 = digits[0] for d in digits[1:]: if digits2[-1] != d: digits2 += d digits3 = re.sub(9, , digits2) while len(digits3) < 4: digits3 += "0" return digits3[:4] if __name__ == __main__: from timeit import Timer names = (Woo, Pilgrim, Flingjingwaller) for name in names: statement = "soundex(%s)" % name t = Timer(statement, "from __main__ import soundex") print name.ljust(15), soundex(name), min(t.repeat())

404

ROZDZIA 19. OPTYMALIZACJA SZYBKOCI

19.6

Optymalizacja operacji na napisach

Ostatnim krokiem algorytmu Soundex jest dopenienie krtkich napisw wynikowych zerami oraz przycicie napisw zbyt dugich. W jaki sposb mona to zrobi najlepiej? Dotychczasowy kod w programie soundex/stage2/soundex2c.py wyglda nastpujco: digits3 = re.sub(9, , digits2) while len(digits3) < 4: digits3 += "0" return digits3[:4] Oto rezultaty pomiarw wydajnoci w soundex2c.py: C:\samples\soundex\stage2>python soundex2c.py Woo W000 12.6070768771 Pilgrim P426 14.4033353401 Flingjingwaller F452 19.7774882003 Pierwsz rzecz, jak naley rozway, jest zastpienie wyraenia regularnego ptl. Oto kod programu soundex/stage4/soundex4a.py: digits3 = for d in digits2: if d != 9: digits3 += d Czy soundex4a.py jest szybszy? Oczywicie: C:\samples\soundex\stage4>python soundex4a.py Woo W000 6.62865531792 Pilgrim P426 9.02247576158 Flingjingwaller F452 13.6328416042 Czekajcie chwil. Ptla, ktra usuwa znaki z napisu? Moemy uy do tego prostej metody z klasy string. Oto soundex/stage4/soundex4b.py: digits3 = digits2.replace(9, ) Czy soundex4b.py jest szybszy? To interesujce pytanie. Zaley to od dancyh wejciowych: C:\samples\soundex\stage4>python soundex4b.py Woo W000 6.75477414029 Pilgrim P426 7.56652144337 Flingjingwaller F452 10.8727729362 Dla wikszoci nazw z programu soundex4b.py metoda z klasy string jest szybsza ni ptla, jest jednak odrobin wolniejsza ni soundex4a.py dla przypadku trywialnego (dla bardzo krtkiej nazwy). Optymalizacje wydajnociowe nie zawsze s jednorodne; poprawki, ktre sprawi, e w pewnych przypadkach program bdzie dziaa szybciej, mog sprawi, e w innych przypadkach ten sam program bdzie dziaa wolniej. W

19.6. OPTYMALIZACJA OPERACJI NA NAPISACH

405

naszym programie uzyskujemy popraw dla wikszoci przypadkw, wic zostawimy t poprawk, warto jednak na przyszo mie t wan zasad na uwadze. Na koniec, cho to wcale nie jest sprawa najmniejszej wagi, przeledmy dwa ostatnie kroki algorytmu: uzupenianie zerami krtkich napisw wynikowych oraz przycinanie napisw zbyt dugich do czterech znakw. Kod, ktry widzicie w soundex4b.py robi dokadnie to, co trzeba, jednak robi to w bardzo niewydajny sposb. Spjrzmy na soundex/stage4/soundex4c.py, aby przekona si, dlaczego tak si dzieje. digits3 += 000 return digits3[:4] Dlaczego potrzebujemy ptli while do wyrwnania napisu wynikowego? Wiemy przecie z gry, e zamierzamy obci napis do czterech znakw; wiemy rwnie, e mamy juz przynajmniej jeden znak (pierwsz liter zmiennej source, ktra w wyniku tego algorytmu nie zmienia si). Oznacza to, e moemy po prostu doda trzy zera do napisu wyjciowego, a nastpnie go przyci. Nie dajcie si nigdy zbi z tropu przez dosowne sformuowanie problemu; czasami wystarczy spojrze na ten problem z troch innej perspektywy, a ju pojawia si prostsze rozwizanie. Czy usuwajc ptl while zyskalimy na prdkoci programu soundex4c.py? Nawet znacznie: C:\samples\soundex\stage4>python soundex4c.py Woo W000 4.89129791636 Pilgrim P426 7.30642134685 Flingjingwaller F452 10.689832367 Na koniec warto zauway, e jest jeszcze jedna rzecz, jak mona zrobi, aby przyspieszy te trzy linijki kodu: mona przeksztaci je do jednej linii. Spjrzmy na soundex/stage4/soundex4d.py: return (digits2.replace(9, ) + 000)[:4] Umieszczenie tego kodu w jednej linii sprawio, e sta si on zaledwie odrobin szybszy, ni soundex4c.py: C:\samples\soundex\stage4>python soundex4d.py Woo W000 4.93624105857 Pilgrim P426 7.19747593619 Flingjingwaller F452 10.5490700634 Za cen tego niewielkiego wzrostu wydajnoci sta si przy okazji znacznie mniej czytelny. Czy warto byo to zrobi? Mam nadziej, e potracie to dobrze skomentowa. Wydajno to nie wszystko. Wysiki zwizane z optymalizacj kodu musz by zawsze rwnowaone deniem do tego, aby kod by czytelny i atwy w utrzymaniu.

406

ROZDZIA 19. OPTYMALIZACJA SZYBKOCI

19.7

Optymalizacja szybkoci - podsumowanie

Podsumowanie
Rozdzia ten zilustrowa kilka wanych aspektw dotyczcych zoptymalizowania czasu dziaania programu w Pythonie, jak i w oglnoci optymalizacji czasu dziaania. Jeli masz wybra midzy wyraeniami regularnymi, a pisanie wasnej ptli, wybierz wyraenia regularne. Wyraenia regularne s przekompilowane w C, wic bd si wykonyway bezporednio przez twj komputer; twoja ptla jest pisana w Pythonie i dziaa za porednictwem interpretera Pythona. Jeli masz wybra midzy wyraeniami regularnymi, a metodami acucha znakw, wybierz metody acucha znakw. Obydwa s przekompilowane w C, wic wybieramy prostsze wersje. Oglne zastosowanie zwizane z przeszukiwaniem sownikw jest szybkie, jednak specjalistyczne funkcja takie jak string.maketrans, czy te metody takie jak isalpha() s szybsze. Jeli Python posiada funkcj specjalnie przystosowan dla ciebie, wykorzystaj j. Nie bd za mdry. Czasami najbardziej oczywiste algorytmy s najszybsze, jakie bymy wymylili. Nie mcz si za bardzo. Szybko wykonywania nie jest wszystkim. Trzeba podkreli, e ostatni punkt jest dosy znaczcy. Przez cay kurs tego rozdziau, przerobilimy t funkcj tak, aby dziaaa trzy razy szybciej i zaoszczdzilimy 20 sekund na 1 milion wywoa tej funkcji. Wspaniale! Ale pomylmy teraz: podczas wykonywania miliona wywoa tej funkcji, jak dugo bdziemy musieli czeka na poczenie bazy danych? Albo jak dugo bdziemy czeka wykonujc operacje wejcia/wyjcia na dysku? Albo jak dugo bdziemy czeka na wejcie uytkownika? Nie marnuj za duo czasu na czasow optymalizacj jednego algorytmu. Pobaw si bardziej nad istotnymi fragmentami kodu, ktre maj dziaa szybko, nastpnie popraw wyapane bdy, a reszt zostaw w spokoju.

Dodatek A

Informacje o pliku i historia


A.1 Historia

Ta ksika zostaa stworzona na polskojzycznej wersji projektu Wikibooks przez autorw wymienionych poniej w sekcji Autorzy. Najnowsza wersja podrcznika jest dostpna pod adresem http://pl.wikibooks.org/wiki/Zanurkuj_w_Pythonie.

A.2

Informacje o pliku PDF i historia

PDF zosta utworzony przez Derbetha dnia 17 lutego 2008 na podstawie wersji z 9 lutego 2008 podrcznika na Wikibooks. Wykorzystany zosta poprawiony program Wiki2LaTeX autorstwa uytkownika angielskich Wikibooks, Hagindaza. Wynikowy kod po rcznych poprawkach zosta przeksztacony w ksik za pomoc systemu A skadu L TEX. Najnowsza wersja tego PDF-u jest postpna pod adresem http://pl.wikibooks. org/wiki/Image:Zanurkuj_w_Pythonie.pdf.

A.3

Autorzy

Akira, Ciastek, Derbeth, Diodac, Farin mag, Fred 4, Fservant, JaroslawZabiello, Jau, Kabturek, KamilaChyla, Kamils, Kangel, Kj, Kocio, Koko, Krzysiu Jarzyna, Kubik, Lewico, Lwh, MTM, Migol, Myki, Nata, Neverous, Pepson, Pietras1988, Piotr, Prasuk historyk, Rafa Rawicki, Robwolfe, Rofrol, Roman 92, Salmon, Sasek, Skalee vel crensh, Sqrll, Sznurek, Tertulian, Turbofart, Tyfus, Warszk, Yurez, Zeo, Zyx i anonimowi autorzy.

A.4

Graki

Autorzy i licencje grak: graka na okadce: Python molurus z Fauna of British India G. A. Boulengera, rdo Wikimedia Commons, public domain logo Wikibooks: zastrzeony znak towarowy, c & media Foundation, Inc. 407
TM

All rights reserved, Wiki-

408

DODATEK A. INFORMACJE O PLIKU

Dodatek B

Dalsze wykorzystanie tej ksiki


B.1 Wstp

Ide Wikibooks jest swobodne dzielenie si wiedz, dlatego te uregulowania Wikibooks maj na celu jak najmniejsze ograniczanie moliwoci osb korzystajcych z serwisu i zapewnienie, e treci dostpne tutaj bd mogy by atwo kopiowane i wykorzystywane dalej. Prosimy wszystkich odwiedzajcych, aby powicili chwil na zaznajomienie si z poniszymi zasadami, by unikn w przyszoci nieporozumie.

B.2

Status prawny

Caa zawarto Wikibooks (o ile w danym miejscu nie jest zaznaczone inaczej; szczegy niej) jest udostpniona na nastpujcych warunkach: Udziela si zezwolenia na kopiowanie, rozpowszechnianie i/lub modykacj treci artykuw polskich Wikibooks zgodnie z zasadami Licencji GNU Wolnej Dokumentacji (GNU Free Documentation License) w wersji 1.2 lub dowolnej pniejszej opublikowanej przez Free Software Foundation; bez Sekcji Niezmiennych, Tekstu na Przedniej Okadce i bez Tekstu na Tylnej Okadce. Kopia tekstu licencji znajduje si na stronie GNU Free Documentation License. Zasadniczo oznacza to, e artykuy pozostan na zawsze dostpne na zasadach open source i mog by uywane przez kadego z obwarowaniami wyszczeglnionymi poniej, ktre zapewniaj, e artykuy te pozostan wolne. Graki i pliku multimedialne wykorzystane na Wikibooks mog by udostpnione na warunkach innych ni GNU FDL. Aby sprawdzi warunki korzystania z graki, naley przej do jej strony opisu, klikajc na grace.

B.3

Wykorzystywanie materiaw z Wikibooks

Jeli uytkownik polskich Wikibooks chce wykorzysta materiay w niej zawarte, musi to zrobi zgodnie z GNU FDL. Warunki te to w skrcie i uproszczeniu: 409

410

DODATEK B. DALSZE WYKORZYSTANIE TEJ KSIKI

Publikacja w Internecie
1. dobrze jest wymieni Wikibooks jako rdo (jest to nieobowizkowe, lecz jest dobrym zwyczajem) 2. naley poda list autorw lub wyranie opisany i funkcjonujcy link do oryginalnej treci na Wikibooks lub historii edycji (wypenia to obowizek podania autorw oryginalnego dziea) 3. trzeba jasno napisa, e tre publikowanego dziea jest objta licencj GNU FDL 4. naley poda link to tekstu licencji (najlepiej zachowanej na wasnym serwerze) 5. posta tekstu nie moe ogranicza moliwoci jego kopiowania

Druk
1. dobrze jest wymieni Wikibooks jako rdo (jest to nieobowizkowe, lecz jest dobrym zwyczajem) 2. naley wymieni 5 autorw z listy w rozdziale Autorzy danego podrcznika (w przypadku braku podobnego rozdziau lista autorw jest dostpna pod odnonikiem historia na grze strony). Gdy podrcznik ma mniej ni 5 autorw, naley wymieni wszystkich. 3. trzeba jasno napisa na przynajmniej jednej stronie, e tre publikowanego dziea jest objta licencj GNU FDL 4. peny tekst licencji, w oryginale i bez zmian, musi by zawarty w ksice 5. jeli zostanie wykonanych wicej niz 100 kopii ksiki konieczne jest: (a) dostarczenie pyt CD, DVD, dyskw lub innych nonikw danych z trecia ksiki w formie moliw do komputerowego przetwarzania; lub: (b) dostarczenie linku do strony z czyteln dla komputera form ksiki (link musi by aktywny przynajmniej przez rok od publikacji; mona uy linku do spisu treci danego podrcznika na Wikibooks) Nie ma wymogu pytania o zgod na wykorzystanie tekstu jakichkolwiek osb z Wikibooks. Autorzy nie mog zabroni nikomu wykorzystywania ich tekstw zgodnie z licencj GNU FDL. Mona korzysta z ksiek jako caoci albo z ich fragmentw. Materiay bazujce na treci z Wikibooks mog by bez przeszkd sprzedawane; zyskami nie trzeba dzieli si z autorami oryginalnego dziea. Jeeli w wykorzystanych materiaach z polskich Wikibooks s treci objte innymi ni GNU FDL licencjami, uytkownik musi speni wymagania zawarte w tych licencjach (dotyczy to szczeglnie grak, ktre mog mie rne licencje).

Dodatek C

GNU Free Documentation License


Version 1.2, November 2002 Copyright c 2000,2001,2002 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

Preamble
The purpose of this License is to make a manual, textbook, or other functional and useful document free in the sense of freedom: to assure everyone the eective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modications made by others. This License is a kind of copyleft, which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.

1. APPLICABILITY AND DEFINITIONS


This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The Document, below, refers to any such manual or work. Any member of the public is a licensee, and 411

412

DODATEK C. GNU FREE DOCUMENTATION LICENSE

is addressed as you. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A Modied Version of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modications and/or translated into another language. A Secondary Section is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Documents overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The Invariant Sections are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not t the above denition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The Cover Texts are certain short passages of text that are listed, as FrontCover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A Transparent copy of the Document means a machine-readable copy, represented in a format whose specication is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent le format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modication by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not Transparent is called Opaque. Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modication. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only. The Title Page means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, Title Page means the text near the most prominent appearance of the works title, preceding the beginning of the body of the text. A section Entitled XYZ means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specic section name mentioned below, such as Acknowledgements, Dedications, Endorsements, or

413 History.) To Preserve the Title of such a section when you modify the Document means that it remains a section Entitled XYZ according to this denition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no eect on the meaning of this License.

2. VERBATIM COPYING
You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies.

3. COPYING IN QUANTITY
If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Documents license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to t legibly, you should put the rst ones listed (as many as t reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.

414

DODATEK C. GNU FREE DOCUMENTATION LICENSE

4. MODIFICATIONS
You may copy and distribute a Modied Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modied Version under precisely this License, with the Modied Version lling the role of the Document, thus licensing distribution and modication of the Modied Version to whoever possesses a copy of it. In addition, you must do these things in the Modied Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modications in the Modied Version, together with at least ve of the principal authors of the Document (all of its principal authors, if it has fewer than ve), unless they release you from this requirement. C. State on the Title page the name of the publisher of the Modied Version, as the publisher. D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modied Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Documents license notice. H. Include an unaltered copy of this License. I. Preserve the section Entitled History, Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modied Version as given on the Title Page. If there is no section Entitled History in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modied Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the History section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. For any section Entitled Acknowledgements or Dedications, Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein.

415 L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section Entitled Endorsements. Such a section may not be included in the Modied Version. N. Do not retitle any existing section to be Entitled Endorsements or to conict in title with any Invariant Section. O. Preserve any Warranty Disclaimers. If the Modied Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modied Versions license notice. These titles must be distinct from any other section titles. You may add a section Entitled Endorsements, provided it contains nothing but endorsements of your Modied Version by various partiesfor example, statements of peer review or that the text has been approved by an organization as the authoritative denition of a standard. You may add a passage of up to ve words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modied Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modied Version.

5. COMBINING DOCUMENTS
You may combine the Document with other documents released under this License, under the terms dened in section 4 above for modied versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodied, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but dierent contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled History in the various original documents, forming one section Entitled History; likewise combine any sections Entitled Acknowledgements, and any sections Entitled Dedications. You must delete all sections Entitled Endorsements.

416

DODATEK C. GNU FREE DOCUMENTATION LICENSE

6. COLLECTIONS OF DOCUMENTS
You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.

7. AGGREGATION WITH INDEPENDENT WORKS


A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an aggregate if the copyright resulting from the compilation is not used to limit the legal rights of the compilations users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Documents Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.

8. TRANSLATION
Translation is considered a kind of modication, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled Acknowledgements, Dedications, or History, the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.

9. TERMINATION
You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under

417 this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

10. FUTURE REVISIONS OF THIS LICENSE


The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may dier in detail to address new problems or concerns. See http://www.gnu.org/copyleft/. Each version of the License is given a distinguishing version number. If the Document species that a particular numbered version of this License or any later version applies to it, you have the option of following the terms and conditions either of that specied version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation.

ADDENDUM: How to use this License for your documents


To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright c YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled GNU Free Documentation License.

If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the with . . . Texts. line with this:

with the Invariant Sections being LIST THEIR TITLES, with the FrontCover Texts being LIST, and with the Back-Cover Texts being LIST.

If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.

Você também pode gostar