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
translate
przyjmuje 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ę
import
uż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
htmlSource
pobieramy ź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)
capitalize
jest 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żelidialectName
ma wartość'chef'
,parserName
przyjmie 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żeliparserName
ma wartość'ChefDialectizer'
,parserClass
bę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żeliparserClass
jest 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ęfeed
wywoł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
feed
wykorzystuje 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.