Zanurkuj w Pythonie/Wprowadzanie do dialect.py: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Piotr (dyskusja | edycje)
Piotr (dyskusja | edycje)
poprawki
Linia 2:
== Wprowadzenie do dialect.py ==
 
<ttcode>Dialectizer</ttcode> jest prostym (i niezbyt mądrym) potomkiem klasy <ttcode>BaseHTMLProcessor</ttcode>. Dokonuje on na bloku tekstu serii podstawień, ale wszystko co znajduje się wewnątrz bloku <tt><nowiki><pre>...</pre></nowiki></tt> pozostawia niezmienione.
 
Aby obsłużyć bloki <tt><nowiki><pre></nowiki></tt> definiujemy w klasie <ttcode>Dialectizer</ttcode> metody: <ttcode>start_pre</ttcode> i <ttcode>end_pre</ttcode>.
 
'''Przykład 8.17. Obsługa określonych znaczników'''
 
{{Python/Przykład
'''Przykład |8.17. |Obsługa określonych znaczników'''
|tekst=
def start_pre(self, attrs): #(1)
self.verbatim += 1 #(2)
Linia 16 ⟶ 17:
self.verbatim -= 1 #(6)
 
# <ttcode>start_pre</ttcode> jest wołanawywoływany za każdym razem, gdy <ttcode>SGMLParser</ttcode> znajdzie znacznik <tt><nowiki><pre></nowiki></tt> w źródle HTML-a. (Za chwilę zobaczyszzobaczymy dokładnie, jak to się dzieje.) Ta metoda przyjmuje jeden parametr: <ttcode>attrs</ttcode>, który zawiera atrybuty znacznika (jeśli jakieś są). <ttcode>attrs</ttcode> jest listą krotek postaci klucz/wartość, taką samą jaką przyjmuje <ttcode>unknown_starttag</ttcode>.
# W metodzie <code>reset</code>, inicjalizujemy atrybut, który służy jako licznik znaczników <tt><nowiki><pre></nowiki></tt>. Za każdym razem, gdy natrafiamy na znacznik <tt><nowiki><pre></nowiki></tt>, zwiększamy licznik, natomiast gdy natrafiamy na znacznik <tt><nowiki></pre></nowiki></tt> zmniejszamy licznik. (Moglibyśmy też użyć po prostu flagi i ustawiać ją na wartość 1<code>True</code>, a następnie 0<code>False</code>, ale nasz sposób jest równie łatwy, a dodatkowo obsługujemy dziwny (ale możliwy) przypadek zagnieżdżonych znaczników <tt><nowiki><pre></nowiki></tt>.) Za chwilę zobaczyszzobaczymy jak można wykorzystać ten licznik.
# To jest ta jedyna akcja wykonywana dla znaczników <code><nowiki><pre></nowiki></code>. Przekazujemy tu listę atrybutów do metody <code>unknown_starttag</code>, aby wykonała ona domyślną akcję.
# Metoda <ttcode>end_pre</ttcode> jest wołanawywoływana za każdym razem, gdy <ttcode>SGMLParser</ttcode> znajdzie znacznik <tt><nowiki></pre></nowiki></tt>. 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 <tt><nowiki><pre></nowiki></tt>.
}}
 
W tym momencie warto się zagłębić nieco bardziej w klasę <ttcode>SGMLParser</ttcode>. TwierdziłemWielokrotnie wielokrotnie (a Ty jak na razie musiałeś mi zaufać)stwierdzaliśmy, że <ttcode>SGMLParser</ttcode> wyszukuje i wywołuje specyficzne metody dla każdego znacznika, jeśli takowe istnieją. Np.Na przykład właśnie widziałeśzobaczyliśmy definicje metod <ttcode>start_pre</ttcode> i <ttcode>end_pre</ttcode> do obsługi <tt><nowiki><pre></nowiki></tt> i <tt><nowiki></pre></nowiki></tt>. Ale jak to się dzieje? No cóż, to nie jest żadna magia. To jest po prostu dobry kawałek kodu w Pythonie.
 
 
Przykład 8.18. <tt>SGMLParser</tt>
 
{{Python/Przykład
Przykład |8.18. |<ttcode>SGMLParser</ttcode>
|tekst=
def finish_starttag(self, tag, attrs): #(1)
try:
Linia 48 ⟶ 50:
method(attrs) #(8)
 
# W tym momencie <ttcode>SGMLParser</ttcode> 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 (<ttcode>unknown_starttag</ttcode>).
# Za "magią" klasy <ttcode>SGMLParser</ttcode> nie kryje się nic więcej niż nasz stary przyjaciel <ttcode>getattr</ttcode>. Może jeszcze tego wcześniej nie zauważyłeśzauważyliśmy, ale <ttcode>getattr</ttcode> poszukuje metod zdefiniowanych zarówno w danym obiekcie jak i w jego potomkach. Tutaj obiektem jest <ttcode>self</ttcode>, czyli bieżąca instancja. A więc jeśli znacznikiem<code>tag</code> jestprzyjmie wartość <code>'pre'</code>, to wywołanie <ttcode>getattr</ttcode> będzie poszukiwało metody <ttcode>start_pre</ttcode> w bieżącej instancji, którą jest instancja klasy <ttcode>Dialectizer</ttcode>.
# Metoda <ttcode>getattr</ttcode> zgłaszarzuca wyjątek <ttcode>AttributeError</ttcode>, 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ż otoczyliśmy wywołanie <ttcode>getattr</ttcode> zostało otoczone blokiem <code>try...except</code> i przechwyciliśmy wyjątek <ttcode>AttributeError</ttcode> zostaje przechwycony.
# Ponieważ nie znaleźliśmy metody <ttcode>start_xxx</ttcode>, sprawdzamy jeszcze metodę <ttcode>do_xxx</ttcode> zanim się poddamy. Ta alternatywna grupa metod generalnie służy do obsługi znaczników samodzielnych, jak np. <code><nowiki><br></nowiki></code>, które nie mają znacznika końcowego. Jednak możemy używać metod z obu grup. Jak widać <code>SGMLParser</code> sprawdza obie grupy dla każdego znacznika. (Jednak nie powinieneś definiować obu metod obsługi <ttcode>start_xxx</ttcode> i <ttcode>do_xxx</ttcode> dla tego samego znacznika; wtedy i tak zostanie wywołana tylko <ttcode>start_xxx</ttcode>.)
# Następny wyjątek <ttcode>AttributeError</ttcode>, który oznacza, że kolejne wywołanie <ttcode>getattr</ttcode> odnośnieodnoszące się doe <ttcode>do_xxx</ttcode> także zawiodło. Ponieważ nie znaleźliśmy ani metody <ttcode>start_xxx</ttcode>, ani <ttcode>do_xxx</ttcode> dla tego znacznika, przechwytujemy wyjątek i wycofujemy się do metody domyślnej <ttcode>unknown_starttag</ttcode>.
# PamiętajPamiętajmy, bloki <ttcode>try...except</ttcode> mogą mieć także klauzulę <ttcode>else</ttcode>, która jest wywoływana jeśli nie wystąpi żaden wyjątek wewnątrz bloku <ttcode>try...except</ttcode>. Logiczne, to oznacza, że znaleźliśmy metodę <ttcode>do_xxx</ttcode> 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 <ttcode>self.stack.append(tag)</ttcode>; <ttcode>SGMLParser</ttcode> ś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 <ttcode>start_xxx</ttcode> i <ttcode>do_xxx</ttcode> nie są wywoływane bezpośrednio;. Znacznik <code>tag</code>, metoda <code>method</code> i atrybuty <code>attrs</code> są przekazywane do tej funkcji:, czyli do <ttcode>handle_starttag</ttcode>, 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 (<ttcode>start_xxx</ttcode> lub <ttcode>do_xxx</ttcode>) z listą atrybutów. PamiętajPamiętajmy, argument <ttcode>method</ttcode> jest funkcją zwróconą przez <ttcode>getattr</ttcode>, a funkcje są obiektami. (Wiem, wiem, zaczynasz mieć dość słuchania tego w kółko. ObiecujęPrzestaniemy żeo natychmiast przestanę totym powtarzać, jak tylko zabraknie mi sposobów na wykorzystanie tego faktu.) Tutaj obiekt funkcji <code>method</code> 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, <ttcode>attrs</ttcode>.
}}
 
A teraz wracającwróćmy do naszego początkowego programu: <ttcode>Dialectizer</ttcode>. Gdy go zostawiliśmy, byliśmy w trakcie definiowania metod obsługi dla znaczników <tt><nowiki><pre></nowiki></tt> i <tt><nowiki></pre></nowiki></tt>. Pozostała już tylko jedna rzecz do zrobienia, a mianowicie przetworzenie bloków tekstu przy pomocy zdefiniowanych podstawień. W tym celu musimy nadpisać metodę <ttcode>handle_data</ttcode>.
 
Przykład 8.19. Nadpisanie metody <tt>handle_data</tt>
 
{{Python/Przykład
Przykład |8.19. |Nadpisanie metody <ttcode>handle_data</ttcode>
|tekst=
def handle_data(self, text): #(1)
self.pieces.append(self.verbatim and text or self.process(text)) #(2)
 
# Metoda <ttcode>handle_data</ttcode> jest wołanawywoływana z tylko jednym argumentem, tekstem do przetworzenia.
# W klasie nadrzędnej <ttcode>BaseHTMLProcessor</ttcode> metoda <ttcode>handle_data</ttcode> po prostu dodawaładodaje tekst do wyjściowego bufora <ttcode>self.pieces</ttcode>. Tutaj logikazasada działania jest tylko trochę bardziej skomplikowana. Jeśli jesteśmy w bloku <tt><nowiki><pre>...</pre></nowiki></tt>, <ttcode>self.verbatim</ttcode> będzie miało jakąś wartość większą od <code>0</code> 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. ToWykorzystujemy jesttutaj jednolinijkowiec, (one-liner)który wwykorzystuje Pythonie[[../Operatory wykorzystującyand triki or#Sztuczka and-or|sztuczkę <code>and</code>-<code>or</code>]].
}}
 
Już jesteś blisko całkowitego zrozumienia <tt>Dialectizer</tt>. 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 <tt>dialect.py</tt> definiują serię wyrażeń regularnych, które operują na tekście pomiędzy znacznikami HTML. Ale Ty już czytałeś cały rozdział o wyrażeniach regularnych. Zapewne nie masz ochoty znowu mozolić się z wyrażeniami regularnymi, prawda? Nauczyłeś się już wystarczająco jak na jeden rozdział.
 
Już jesteś blisko całkowitego zrozumienia <ttcode>Dialectizer</ttcode>. 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 <tt>{{Python/Src|dialect.py</tt>}} definiujądefiniuje serię wyrażeń regularnych, które operują na tekście pomiędzy znacznikami HTML. Ale TyMy już czytałeśmamy przeanalizowany cały rozdział o wyrażeniach regularnych. Zapewne nie masz ochoty znowu mozolić się z wyrażeniami regularnymi, prawda? NauczyłeśJuż sięwystarczająco jużdużo wystarczającosię nauczyliśmy, jak na jeden rozdział.
 
<noinclude>
{{Nawigacja|Zanurkuj w Pythonie|
[[../Dodawanie cudzysłowów do wartości atrybutów|Dodawanie cudzysłowów do wartości atrybutów/]]|
[[../Przetwarzanie HTML-a - wszystko razem|Wszystko razem]]|
}}