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
edytujTrzecim 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
getattr
referencję do funkcji. - Ostatecznie, możemy wywołać tę funkcję, przekazując sam
node
jako 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_Document
jest wywołany tylko raz, ponieważ jest tylko jeden węzeł klasyDocument
w dokumencie XML i tylko jeden obiekt klasyDocument
w przeparsowanej reprezentacji XML-a. Tu po prostu idziemy dalej i parsujemy część główną pliku gramatyki.parse_Text
jest 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_Comment
jest 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, funkcjaparse
nawali 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_Element
jest 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".