Zanurkuj w Pythonie/Wprowadzanie do dialect.py
Wprowadzenie do dialect.py
[edytuj]Dialectizer 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_prejest wywoływany za każdym razem, gdySGMLParserznajdzie 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ą).attrsjest 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_prejest wywoływana za każdym razem, gdySGMLParserznajdzie 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
SGMLParserznalazł 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
SGMLParsernie kryje się nic więcej niż nasz stary przyjacielgetattr. Może jeszcze tego wcześniej nie zauważyliśmy, alegetattrposzukuje metod zdefiniowanych zarówno w danym obiekcie jak i w jego potomkach. Tutaj obiektem jestself, czyli bieżąca instancja. A więc jeślitagprzyjmie wartość'pre', to wywołaniegetattrbędzie poszukiwało metodystart_prew bieżącej instancji, którą jest instancja klasyDialectizer. - Metoda
getattrrzuca 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łaniegetattrzostało otoczone blokiemtry...excepti wyjątekAttributeErrorzostaje przechwycony. - Ponieważ nie znaleźliśmy metody
start_xxx, sprawdzamy jeszcze metodędo_xxxzanim 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ćSGMLParsersprawdza obie grupy dla każdego znacznika. (Jednak nie powinieneś definiować obu metod obsługistart_xxxido_xxxdla tego samego znacznika; wtedy i tak zostanie wywołana tylkostart_xxx.) - Następny wyjątek
AttributeError, który oznacza, że kolejne wywołaniegetattrodnoszące się dodo_xxxtakże zawiodło. Ponieważ nie znaleźliśmy ani metodystart_xxx, anido_xxxdla tego znacznika, przechwytujemy wyjątek i wycofujemy się do metody domyślnejunknown_starttag. - Pamiętajmy, bloki
try...exceptmogą 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_xxxdla 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_xxxido_xxxnie są wywoływane bezpośrednio. Znaczniktag, metodamethodi atrybutyattrssą 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_xxxlubdo_xxx) z listą atrybutów. Pamiętajmy, argumentmethodjest 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 funkcjimethodjest 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_datajest wywoływana z tylko jednym argumentem, tekstem do przetworzenia. - W klasie nadrzędnej
BaseHTMLProcessormetodahandle_datapo 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.verbatimbędzie miało jakąś wartość większą od0i 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ł.