Zanurkuj w Pythonie/Przetwarzanie HTML-a - wszystko razem
Wszystko razem
[edytuj]Nadszedł czas, aby połączyć w całość wiedzę, którą zdobyliśmy do tej pory.
translate, część 1 def translate(url, dialectName="chef"): #(1) import urllib #(2) sock = urllib.urlopen(url) #(3) htmlSource = sock.read() sock.close() - Funkcja
translateprzyjmuje opcjonalny argumentdialectName, który jest łańcuchem znaków określającym używany dialekt. Zaraz zobaczymy, jak to jest wykorzystywane. - Moment, tam jest ważna instrukcja w tej funkcji! Jest to w pełni dozwolone w Pythonie działanie. Instrukcję
importużywaliśmy zwykle na samym początku programu, aby zaimportowany moduł był dostępny w dowolnym miejscu. Ale możemy także importować moduły w samej funkcji, przez co będą one dostępne tylko z jej poziomu. Jeżeli jakiegoś modułu potrzebujemy użyć tylko w jednej funkcji, jest to najlepszy sposób aby zachować modularność twojego programu. (Docenisz to, gdy okaże się, że twój weekendowy hack wyrósł na ważące 800 linii dzieło sztuki, a ty właśnie zdecydujesz się podzielić to na mniejsze części). - Tutaj otwieramy połączenie i do zmiennej
htmlSourcepobieramy źródło HTML spod wskazanego adresu URL.
translate, część 2: coraz ciekawiej parserName = "%sDialectizer" % dialectName.capitalize() #(1) parserClass = globals()[parserName] #(2) parser = parserClass() #(3) capitalizejest metodą łańcucha znaków, z którą się jeszcze nie spotkaliśmy; zmienia ona pierwszy znak na wielką literę, a wszystkie pozostałe znaki na małe litery. W połączeniu z prostym formatowaniem napisu, nazwa dialektu zamieniana jest na nazwę odpowiadającej mu klasy. JeżelidialectNamema wartość'chef',parserNameprzyjmie wartość'ChefDialectizer'.- W tym miejscu mamy nazwę klasy (w zmiennej
parserName) oraz dostęp do globalnej przestrzeni nazw, poprzez słownikglobals(). Łącząc obie informacje dostajemy referencje do klasy o określonej nazwie. (Pamiętajmy, że klasy są obiektami i mogą być przypisane do zmiennej, jak każdy inny obiekt). JeżeliparserNamema wartość'ChefDialectizer',parserClassbędzie klasąChefDialectizer. - Ostatecznie, mając obiekt klasy (
parserClass) chcemy zainicjować tę klasę. Wiemy już jak zrobić –- po prostu wywołujemy klasę w taki sposób, jakby była to funkcja. Fakt, że klasa jest przechowywana w lokalnej zmiennej nie robi żadnej różnicy, po prostu wywołujemy lokalną zmienną jak funkcję, i na wyjściu wyskakuje instancja klasy. JeżeliparserClassjest klasąChefDialectizer, parser będzie instancją klasyChefDialectizer.
Zastanawiasz się, ponieważ istnieją tylko 3 klasy Dialectizer, dlaczego by nie użyć po prostu instrukcji case? (W porządku, w Pythonie nie ma instrukcji case, ale zawsze można użyć serii instrukcji if). Z jednego powodu: elastyczności programu. Funkcja translate nie ma pojęcia, jak wiele zdefiniowaliśmy podklas Dialectizer-a. Wyobraźmy sobie, że definiujemy jutro nową klasę FooDialectizer -– funkcja translate zadziała bez przeróbek.
Nawet lepiej – wyobraźmy sobie, że umieszczasz klasę FooDialectizer w osobnym module i importujesz ją poprzez from module import. Jak wcześniej mogliśmy się przekonać, taka operacja dołączy to do globals(), więc funkcja translate nadal będzie działać prawidłowo bez konieczności dokonywania modyfikacji, nawet wtedy, gdy FooDialectizer znajdzie się w oddzielnym pliku.
Teraz wyobraźmy sobie, że nazwa dialektu pochodzi skądś spoza programu, może z bazy danych lub z wartości wprowadzonej przez użytkownika. Możemy użyć jakąkolwiek ilość pythonowych skryptów po stronie serwera, aby dynamicznie generować strony internetowe; taka funkcja mogłaby przekazać URL i nazwę dialektu (oba w postaci łańcucha znaków) w zapytania żądania strony internetowej, i zwrócić "przetłumaczoną" stronę.
Na koniec wyobraźmy sobie framework Dialectizer z wbudowaną obsługą plug-inów. Możemy umieścić każdą podklasę Dialectizer-a w osobnym pliku pozostawiając jedynie w pliku dialect.py funkcję translate. Jeżeli zachowasz stały schemat nazewnictwa klas, funkcja translate może dynamicznie importować potrzebną klasę z odpowiedniego pliku, jedynie na podstawie podanej nazwy dialektu. (Dynamicznego importowanie omówimy to w dalszej części tego podręcznika). Aby dodać nowy dialekt, wystarczy, że utworzymy odpowiednio nazwany plik (np. foodialect.py zawierający klasę FooDialectizer) w katalogu z plug-inami. Wywołując funkcję translate z nazwą dialektu 'foo', odnajdzie ona moduł foodialect.py i automatycznie zaimportuje klasę FooDialectizer.
translate, część 3 parser.feed(htmlSource) #(1) parser.close() #(2) return parser.output() #(3) - Po tym całym wyobrażaniu sobie, co robiło się już nudne, mamy funkcję
feed, która przeprowadza całą transformację. Ponieważ całe źródło HTML-a mamy w jednym łańcuchu znaków, więc funkcjęfeedwywołujemy tylko raz. Oczywiście możemy wywoływać ją dowolną ilość razy, a parser za każdym razem przeprowadzi transformację. Na przykład, jeżeli obawiamy się o zużycie pamięci (albo wiemy, że będziemy parsowali naprawdę wielkie strony HTML), możemy umieścić tą funkcję w pętli, w której będziemy odczytywał tylko kawałek HTML-a i karmił nim parser. Efekty będą takie same. - Ponieważ funkcja
feedwykorzystuje wewnętrzny bufor, powinniśmy zawsze po zakończeniu operacji wywołać funkcjęclose()parsera (nawet jeżeli przesłaliśmy parserowi całość za jednym razem). W przeciwnym wypadku możemy stwierdzić, że w otrzymanym wyniku brakuje kilku ostatnich bajtów. - Pamiętajmy, że funkcja
output, którą zdefiniowaliśmy samodzielnie w klasieBaseHTMLProcessor, łączy wszystkie zbuforowane kawałki i zwraca całość w postaci pojedynczego łańcucha znaków.
I właśnie w taki sposób, "przetłumaczyliśmy" stronę internetową, podając jedynie jej adres URL i nazwę dialektu.