Zanurkuj w Pythonie/Tworzenie oddzielnych funkcji obsługi względem typu węzła
Tworzenie oddzielnych funkcji obsługi względem typu węzła
[edytuj]Trzecim użytecznym chwytem podczas przetwarzania XML-a jest podzielenie kodu w logiczny sposób na funkcje oparte na typie węzła i nazwie elementu. Parsując dokument przetwarzamy rozmaite typy węzłów, które są reprezentowane przez obiekty Pythona. Poziom główny dokumentu jest bezpośrednio reprezentowany przez obiekt klasy Document. Z kolei Document zawiera jeden lub więcej obiektów klasy Element (reprezentujące znaczniki XML-a), a każdy z nich może zawierać inne obiekty klasy Element, obiekty klasy Text (fragmenty tekstu), czy obiektów Comment (osadzone komentarze w dokumencie). Python pozwala w łatwy sposób napisać funkcję pośredniczącą, która rozdziela logikę dla każdego rodzaju węzła.
>>> from xml.dom import minidom
>>> xmldoc = minidom.parse('kant.xml') #(1)
>>> xmldoc
<xml.dom.minidom.Document instance at 0x01359DE8>
>>> xmldoc.__class__ #(2)
<class xml.dom.minidom.Document at 0x01105D40>
>>> xmldoc.__class__.__name__ #(3)
'Document'
- Załóżmy na moment, że kant.xml jest w bieżącym katalogu.
- Jak powiedzieliśmy w podrozdziale "Pakiety", obiekt zwrócony przez parsowany dokument jest instancją klasy
Document, która została zdefiniowana w minidom.py w pakieciexml.dom. Jak zobaczyliśmy w podrozdziale "Tworzenie instancji klasy",__class__jest wbudowanym atrybutem każdego obiektu Pythona. - Ponadto
__name__jest wbudowanym atrybutem każdej klasy Pythona. Atrybut ten przechowuje napis, a napis ten nie jest niczym tajemniczym, jest po prostu nazwą danej klasy. (Zobacz podrozdział "Definiowanie klas".)
To fajnie, możemy pobrać nazwę klasy dowolnego węzła XML-a (ponieważ węzły są reprezentowane przez Pythonowe obiekty). Jak można wykorzystać tę zaletę, aby rozdzielić logikę parsowania dla każdego typu węzła? Odpowiedzią jest getattr, który pierwszy raz zobaczyliśmy w podrozdziale "Funkcja getattr".
parse, ogólna funkcja pośrednicząca dla węzła XML
def parse(self, node):
parseMethod = getattr(self, "parse_%s" % node.__class__.__name__) #(1) (2)
parseMethod(node) #(3)
- Od razu, zauważmy, że konstruujemy dłuższy napis oparty na nazwie klasy przekazanego węzła (jako argument
node). Zatem, jeśli przekażemy węzełDocument-u, konstruujemy napis'parse_Document'itd. - Teraz, jeśli potraktujemy tę nazwę jako nazwę funkcji, otrzymamy dzięki
getattrreferencję do funkcji. - Ostatecznie, możemy wywołać tę funkcję, przekazując sam
nodejako argument. Następny przykład przedstawia definicję tych funkcji.
parse
def parse_Document(self, node): #(1)
self.parse(node.documentElement)
def parse_Text(self, node): #(2)
text = node.data
if self.capitalizeNextWord:
self.pieces.append(text[0].upper())
self.pieces.append(text[1:])
self.capitalizeNextWord = 0
else:
self.pieces.append(text)
def parse_Comment(self, node): #(3)
pass
def parse_Element(self, node): #(4)
handlerMethod = getattr(self, "do_%s" % node.tagName)
handlerMethod(node)
parse_Documentjest wywołany tylko raz, ponieważ jest tylko jeden węzeł klasyDocumentw dokumencie XML i tylko jeden obiekt klasyDocumentw przeparsowanej reprezentacji XML-a. Tu po prostu idziemy dalej i parsujemy część główną pliku gramatyki.parse_Textjest wywoływany tylko na węzłach reprezentujących fragmenty tekstu. Funkcja wykonuje kilka specjalnych operacji związanych z automatycznym wstawianiem dużej litery na początku słowa pierwszego zdania, ale w innym wypadku po prostu dodaje reprezentowany tekst do listy.parse_Commentjest tylko "przejażdżką"; metoda ta nic nie robi, ponieważ nie musimy się troszczyć o komentarze wstawione w plikach definiującym gramatykę. Pomimo tego, zauważmy, że nadal musimy zdefiniować funkcję i wyraźnie stwierdzić, żeby nic nie robiła. Jeśli funkcja nie będzie istniała, funkcjaparsenawali tak szybko, jak napotka się na komentarz, ponieważ będzie próbowała znaleźć nieistniejącą funkcjęparse_Comment. Definiując oddzielną funkcję dla każdego typu węzła, nawet jeśli nam ta funkcja nie jest potrzebna, pozwalamy ogólnej funkcji parsującej być prostą i krótką.- Metoda
parse_Elementjest w rzeczywistości funkcją pośredniczącą, opartą na nazwie znacznika elementu. Idea jest taka sama: weź odróżniające się od siebie elementy (elementy, które różnią się nazwą znacznika) i wyślij je do odpowiedniej, odrębnej funkcji. Konstruujemy napis typu'do_xref'(dla znacznika <xref>), znajdujemy funkcję o takiej nazwie i wywołujemy ją. I robimy podobnie dla każdej innej nazwy znacznika, która zostanie znaleziona, oczywiście w pliku gramatyki (czyli znaczniki <p>, czy też <choice>).
W tym przykładzie funkcja pośrednicząca parse i parse_Element po prostu znajdują inne metody w tej samej klasie. Jeśli przetwarzanie jest bardzo złożone (lub mamy bardzo dużo nazw znaczników), powinniśmy rozdzielić swój kod na kilka oddzielnych modułów i wykorzystać dynamiczne importowanie, aby zaimportować każdy moduł, a następnie wywołać wszystkie potrzebne nam funkcje. Dynamiczne importowanie zostanie omówione w rozdziale "Programowanie funkcyjne".