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.

Przykład. Obsługa określonych znaczników
     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)
  1. start_pre jest wywoływany za każdym razem, gdy SGMLParser 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ą przyjmuje unknown_starttag.
  2. 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ępnie False, 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.
  3. To jest ta jedyna akcja wykonywana dla znaczników <pre>. Przekazujemy tu listę atrybutów do metody unknown_starttag, aby wykonała ona domyślną akcję.
  4. Metoda end_pre jest wywoływana za każdym razem, gdy SGMLParser znajdzie znacznik </pre>. Ponieważ znaczniki końcowe nie mogą mieć atrybutów, ta metoda nie przyjmuje żadnych parametrów.
  5. Po pierwsze, chcemy wykonać domyślną akcję dla znacznika końcowego.
  6. 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.

Przykład. 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)
  1. 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).
  2. Za "magią" klasy SGMLParser nie kryje się nic więcej niż nasz stary przyjaciel getattr. Może jeszcze tego wcześniej nie zauważyliśmy, ale getattr poszukuje metod zdefiniowanych zarówno w danym obiekcie jak i w jego potomkach. Tutaj obiektem jest self, czyli bieżąca instancja. A więc jeśli tag przyjmie wartość 'pre', to wywołanie getattr będzie poszukiwało metody start_pre w bieżącej instancji, którą jest instancja klasy Dialectizer.
  3. Metoda getattr rzuca wyjątek AttributeError, 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łanie getattr zostało otoczone blokiem try...except i wyjątek AttributeError zostaje przechwycony.
  4. 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ługi start_xxx i do_xxx dla tego samego znacznika; wtedy i tak zostanie wywołana tylko start_xxx.)
  5. Następny wyjątek AttributeError, który oznacza, że kolejne wywołanie getattr odnoszące się do do_xxx także zawiodło. Ponieważ nie znaleźliśmy ani metody start_xxx, ani do_xxx dla tego znacznika, przechwytujemy wyjątek i wycofujemy się do metody domyślnej unknown_starttag.
  6. 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 bloku try...except. Logiczne, to oznacza, że znaleźliśmy metodę do_xxx dla tego znacznika, a więc wywołujemy ją.
  7. 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.
  8. Metody start_xxx i do_xxx nie są wywoływane bezpośrednio. Znacznik tag, metoda method i atrybuty attrs są przekazywane do tej funkcji, czyli do handle_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 lub do_xxx) z listą atrybutów. Pamiętajmy, argument method jest funkcją zwróconą przez getattr, 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 funkcji method 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.

Przykład. Nadpisanie metody handle_data
     def handle_data(self, text):                                             #(1)
         self.pieces.append(self.verbatim and text or self.process(text)) #(2)
  1. Metoda handle_data jest wywoływana z tylko jednym argumentem, tekstem do przetworzenia.
  2. W klasie nadrzędnej BaseHTMLProcessor metoda handle_data po prostu dodaje tekst do wyjściowego bufora self.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ą od 0 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ł.