Zanurkuj w Pythonie/Wprowadzanie do dialect.py
Wprowadzenie do dialect.py
edytujDialectizer
jest prostym (i niezbyt mądrym) potomkiem klasy BaseHTMLProcessor
. Dokonuje on na bloku tekstu serii podstawień, ale wszystko co znajduje się wewnątrz bloku <pre>...</pre> pozostawia niezmienione.
Aby obsłużyć bloki <pre> definiujemy w klasie Dialectizer
metody: start_pre
i end_pre
.
def start_pre(self, attrs): #(1)
self.verbatim += 1 #(2)
self.unknown_starttag("pre", attrs) #(3)
def end_pre(self): #(4)
self.unknown_endtag("pre") #(5)
self.verbatim -= 1 #(6)
start_pre
jest wywoływany za każdym razem, gdySGMLParser
znajdzie znacznik <pre> w źródle HTML-a. (Za chwilę zobaczymy dokładnie, jak to się dzieje.) Ta metoda przyjmuje jeden parametr:attrs
, który zawiera atrybuty znacznika (jeśli jakieś są).attrs
jest listą krotek postaci klucz/wartość, taką samą jaką przyjmujeunknown_starttag
.- W metodzie
reset
, inicjalizujemy atrybut, który służy jako licznik znaczników <pre>. Za każdym razem, gdy natrafiamy na znacznik <pre>, zwiększamy licznik, natomiast gdy natrafiamy na znacznik </pre> zmniejszamy licznik. (Moglibyśmy też użyć po prostu flagi i ustawiać ją na wartośćTrue
, a następnieFalse
, ale nasz sposób jest równie łatwy, a dodatkowo obsługujemy dziwny (ale możliwy) przypadek zagnieżdżonych znaczników <pre>.) Za chwilę zobaczymy jak można wykorzystać ten licznik. - To jest ta jedyna akcja wykonywana dla znaczników
<pre>
. Przekazujemy tu listę atrybutów do metodyunknown_starttag
, aby wykonała ona domyślną akcję. - Metoda
end_pre
jest wywoływana za każdym razem, gdySGMLParser
znajdzie znacznik </pre>. Ponieważ znaczniki końcowe nie mogą mieć atrybutów, ta metoda nie przyjmuje żadnych parametrów. - Po pierwsze, chcemy wykonać domyślną akcję dla znacznika końcowego.
- Po drugie, zmniejszamy nasz licznik, co sygnalizuje nam zamknięcie bloku <pre>.
W tym momencie warto się zagłębić nieco bardziej w klasę SGMLParser
. Wielokrotnie stwierdzaliśmy, że SGMLParser
wyszukuje i wywołuje specyficzne metody dla każdego znacznika, jeśli takowe istnieją. Na przykład właśnie zobaczyliśmy definicje metod start_pre
i end_pre
do obsługi <pre> i </pre>. Ale jak to się dzieje? No cóż, to nie jest żadna magia. To jest po prostu dobry kawałek kodu w Pythonie.
SGMLParser
def finish_starttag(self, tag, attrs): #(1)
try:
method = getattr(self, 'start_' + tag) #(2)
except AttributeError: #(3)
try:
method = getattr(self, 'do_' + tag) #(4)
except AttributeError:
self.unknown_starttag(tag, attrs) #(5)
return -1
else:
self.handle_starttag(tag, method, attrs) #(6)
return 0
else:
self.stack.append(tag)
self.handle_starttag(tag, method, attrs)
return 1 #(7)
def handle_starttag(self, tag, method, attrs):
method(attrs) #(8)
- W tym momencie
SGMLParser
znalazł już początkowy znacznik i sparsował listę atrybutów. Ostatnia rzecz jaka została do zrobienia, to ustalenie czy istnieje specjalna metoda obsługi dla tego znacznika lub czy powinniśmy skorzystać z metody domyślnej (unknown_starttag
). - Za "magią" klasy
SGMLParser
nie kryje się nic więcej niż nasz stary przyjacielgetattr
. Może jeszcze tego wcześniej nie zauważyliśmy, alegetattr
poszukuje metod zdefiniowanych zarówno w danym obiekcie jak i w jego potomkach. Tutaj obiektem jestself
, czyli bieżąca instancja. A więc jeślitag
przyjmie wartość'pre'
, to wywołaniegetattr
będzie poszukiwało metodystart_pre
w bieżącej instancji, którą jest instancja klasyDialectizer
. - Metoda
getattr
rzuca wyjątekAttributeError
, jeśli metoda, której szuka nie istnieje w danym obiekcie (oraz w żadnym z jego potomków), ale to jest w porządku, ponieważ wywołaniegetattr
zostało otoczone blokiemtry...except
i wyjątekAttributeError
zostaje przechwycony. - Ponieważ nie znaleźliśmy metody
start_xxx
, sprawdzamy jeszcze metodędo_xxx
zanim się poddamy. Ta alternatywna grupa metod generalnie służy do obsługi znaczników samodzielnych, jak np.<br>
, które nie mają znacznika końcowego. Jednak możemy używać metod z obu grup. Jak widaćSGMLParser
sprawdza obie grupy dla każdego znacznika. (Jednak nie powinieneś definiować obu metod obsługistart_xxx
ido_xxx
dla tego samego znacznika; wtedy i tak zostanie wywołana tylkostart_xxx
.) - Następny wyjątek
AttributeError
, który oznacza, że kolejne wywołaniegetattr
odnoszące się dodo_xxx
także zawiodło. Ponieważ nie znaleźliśmy ani metodystart_xxx
, anido_xxx
dla tego znacznika, przechwytujemy wyjątek i wycofujemy się do metody domyślnejunknown_starttag
. - Pamiętajmy, bloki
try...except
mogą mieć także klauzulęelse
, która jest wywoływana jeśli nie wystąpi żaden wyjątek wewnątrz blokutry...except
. Logiczne, to oznacza, że znaleźliśmy metodędo_xxx
dla tego znacznika, a więc wywołujemy ją. - A tak przy okazji nie przejmuj się tymi różnymi zwracanymi wartościami; teoretycznie one coś oznaczają, ale w praktyce nie są wykorzystywane. Nie martw się także tym
self.stack.append(tag)
;SGMLParser
śledzi samodzielnie, czy znaczniki początkowe są zrównoważone z odpowiednimi znacznikami końcowymi, ale jednocześnie do niczego tej informacji nie wykorzystuje. Teoretycznie moglibyśmy wykorzystać ten moduł do sprawdzania, czy znaczniki są całkowicie zrównoważone, ale prawdopodobnie nie warto i wykracza to poza zakres tego rozdziału. W tej chwili masz lepsze powody do zmartwienia. - Metody
start_xxx
ido_xxx
nie są wywoływane bezpośrednio. Znaczniktag
, metodamethod
i atrybutyattrs
są przekazywane do tej funkcji, czyli dohandle_starttag
, aby klasy potomne mogły ją nadpisać i tym samym zmienić sposób obsługi znaczników początkowych. Nie potrzebujemy aż tak niskopoziomowej kontroli, a więc pozwalamy tej metodzie zrobić swoje, czyli wywołać metody (start_xxx
lubdo_xxx
) z listą atrybutów. Pamiętajmy, argumentmethod
jest funkcją zwróconą przezgetattr
, a funkcje są obiektami. (Wiem, wiem, zaczynasz mieć dość słuchania tego w kółko. Przestaniemy o tym powtarzać, jak tylko zabraknie sposobów na wykorzystanie tego faktu.) Tutaj obiekt funkcjimethod
jest przekazywany do metody jako argument, a ta metoda wywołuje tę funkcję. W tym momencie nie istotne jest co to jest za funkcja, jak się nazywa, gdzie jest zdefiniowana; jedyna rzecz jaka jest ważna, to to że jest ona wywoływana z jednym argumentem,attrs
.
A teraz wróćmy do naszego początkowego programu: Dialectizer
. Gdy go zostawiliśmy, byliśmy w trakcie definiowania metod obsługi dla znaczników <pre> i </pre>. Pozostała już tylko jedna rzecz do zrobienia, a mianowicie przetworzenie bloków tekstu przy pomocy zdefiniowanych podstawień. W tym celu musimy nadpisać metodę handle_data
.
handle_data
def handle_data(self, text): #(1)
self.pieces.append(self.verbatim and text or self.process(text)) #(2)
- Metoda
handle_data
jest wywoływana z tylko jednym argumentem, tekstem do przetworzenia. - W klasie nadrzędnej
BaseHTMLProcessor
metodahandle_data
po prostu dodaje tekst do wyjściowego buforaself.pieces
. Tutaj zasada działania jest tylko trochę bardziej skomplikowana. Jeśli jesteśmy w bloku <pre>...</pre>,self.verbatim
będzie miało jakąś wartość większą od0
i tekst trafi do bufora wyjściowego nie zmieniony. W przeciwnym razie wywołujemy oddzielną metodę do wykonania podstawień i rezultat umieszczamy w buforze wyjściowym. Wykorzystujemy tutaj jednolinijkowiec, który wykorzystuje sztuczkęand
-or
.
Już jesteś blisko całkowitego zrozumienia Dialectizer
. Ostatnim brakującym ogniwem jest sam charakter podstawień w tekście. Jeśli znasz Perla, to wiesz, że kiedy wymagane są kompleksowe zmiany w tekście, to jedynym prawdziwym rozwiązaniem są wyrażenia regularne. Klasy w dalszej części dialect.py definiuje serię wyrażeń regularnych, które operują na tekście pomiędzy znacznikami HTML. My już mamy przeanalizowany cały rozdział o wyrażeniach regularnych. Zapewne nie masz ochoty znowu mozolić się z wyrażeniami regularnymi, prawda? Już wystarczająco dużo się nauczyliśmy, jak na jeden rozdział.