Zanurkuj w Pythonie/Tworzenie oddzielnych funkcji obsługi względem typu węzła: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Piotr (dyskusja | edycje)
mNie podano opisu zmian
Piotr (dyskusja | edycje)
poprawki
Linia 1:
{{Podświetl|py}}
== Tworzenie oddzielnych funkcji obsługi względem typu węzła ==
 
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 <ttcode>Document</ttcode>. Z kolei <ttcode>Document</ttcode> zawiera jeden lub więcej obiektów klasy <ttcode>Element</ttcode> (wreprezentujące rzeczywistości znacznikówznaczniki XML-a), a każdy z nich może zawierać inne obiekty klasy <ttcode>Element</ttcode>, obiekty klasy <ttcode>Text</ttcode> (fragmenty tekstu), czy obiektów <ttcode>Comment</ttcode> (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.
 
'''Przykład 10.17. Nazwy klas parsowanych obiektów XML'''
 
{{Python/Przykład
'''Przykład |10.17. |Nazwy klas parsowanych obiektów XML'''
|tekst=
>>> from xml.dom import minidom
>>> xmldoc = minidom.parse('kant.xml') #(1)
>>> xmldoc
{{samp|<xml.dom.minidom.Document instance at 0x01359DE8>}}
>>> xmldoc.__class__ #(2)
{{samp|<class xml.dom.minidom.Document at 0x01105D40>}}
>>> xmldoc.__class__.__name__ #(3)
{{samp|'Document'}}
 
# Załóżmy na moment, że <tt>{{Python/Src|kgp/kant.xml</tt>|kant.xml}} jest w bieżącym katalogu.
# Jak powiedzieliśmy w ''podrozdziale 9[[.2'',./Pakiety|podrozdziale “Pakiety”"Pakiety"]], obiekt zwrócony przez parsowany dokument jest instancją klasy <ttcode>Document</ttcode>, która została zdefiniowana w <tt>minidom.py</tt> w pakiecie <ttcode>xml.dom</ttcode>. Jak zobaczyliśmy w ''Podrozdziale 5[[.4'',./Tworzenie “Tworzenieinstancji klasy|podrozdziale "Tworzenie instancji klasy”klasy"]], <ttcode>__class__</ttcode> jest wbudowanym atrybutem każdego obiektu Pythona.
# Ponadto, <ttcode>__name__</ttcode> 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ł 5[[.3'',./Definiowanie klas|podrozdział “Definiowanie"Definiowanie klas”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 <ttcode>getattr</ttcode>, który pierwszy raz zobaczyliśmy w ''podrozdziale 4[[.4'',./Funkcja “Pobieraniegetattr|podrozdziale referencji"Funkcja do obiektu”<code>getattr</code>"]].
 
{{Python/Przykład
'''Przykład |10.18. |<ttcode>parse</ttcode>, ogólna funkcja pośrednicząca dla węzła XML'''
|tekst=
<pre>
def parse(self, node):
parseMethod = getattr(self, "parse_%s" % node.__class__.__name__) #(1) (2)
parseMethod(node) #(3)
</pre>
 
# Od razu, zauważzauważmy, że konstruujemy dłuższy napis oparty na nazwie klasy przekazanego węzła (jako argument <ttcode>node</ttcode>). Zatem, jeśli przekażemy węzeł <ttcode>Document</ttcode>-u, konstruujemy napis <ttcode>'parse_Document'</ttcode> itd.
def parse(self, node):
# Teraz, jeśli potraktujemy tę nazwę jako nazwę funkcji, otrzymamy dzięki <ttcode>getattr</ttcode> referencję do funkcji.
parseMethod = getattr(self, "parse_%s" % node.__class__.__name__) #(1) (2)
# Ostatecznie, możemy wywołać tę funkcję, przekazując sam <ttcode>node</ttcode> jako argument. Następny przykład przedstawia definicję tych funkcji.
parseMethod(node) #(3)
}}
 
{{Python/Przykład
# Od razu, zauważ, że konstruujemy dłuższy napis oparty na nazwie klasy przekazanego węzła (jako argument <tt>node</tt>). Zatem, jeśli przekażemy węzeł <tt>Document</tt>-u, konstruujemy napis <tt>'parse_Document'</tt> itd.
'''Przykład |10.19. |Funkcje wywoływane przez funkcję pośredniczącą <ttcode>parse</ttcode>'''
# Teraz, jeśli potraktujemy tę nazwę jako nazwę funkcji, otrzymamy dzięki <tt>getattr</tt> referencję do funkcji.
|tekst=
# Ostatecznie, możemy wywołać tę funkcję, przekazując sam <tt>node</tt> jako argument. Następny przykład przedstawia definicję tych funkcji.
<pre>
def parse_Commentparse_Document(self, node): #(31)
self.parse(node.documentElement)
 
def parse_Text(self, node): #(2)
'''Przykład 10.19. Funkcje wywoływane przez funkcję pośredniczącą <tt>parse</tt>'''
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_Documentparse_Comment(self, node): #(13)
pass
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)
 
def parse_Element(self, node): #(4)
# <tt>parse_Document</tt> jest wywołany tylko raz, ponieważ jest tylko jeden węzeł klasy <tt>Document</tt> w dokumencie XML i tylko jeden obiekt klasy <tt>Document</tt> w przeparsowanej reprezentacji XML-a. Tu po prostu idziemy dalej i parsujemy część główną pliku gramatyki.
handlerMethod = getattr(self, "do_%s" % node.tagName)
# <tt>parse_Text</tt> 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.
handlerMethod(node)
# <tt>parse_Comment</tt> 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ż, że nadal musimy zdefniować funkcję i wyraźnie stwierdzić, żeby nic nie robiła. Jeśli funkcja nie będzie istniała, funkcja <tt>parse</tt> nawali tak szybko, jak napotka się na komentarz, ponieważ będzie próbowała znaleźć nieistniejącą funkcję <tt>parse_Comment</tt>. 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ą.
</pre>
# Metoda <tt>parse_Element</tt> jest faktycznie 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 <tt>'do_xref'</tt> (dla znacznika <tt><xref></tt>), znajdujemy funkcję o takiej nazwie i wywołujemy ją. I tak dalej, dla każdego innej nazwy znacznika, która zostanie znaleziona, oczywiście w pliku gramatyki (czyli znaczniki <tt><nowiki><p></nowiki></tt>, czy też <tt><choice></tt>).
# <ttcode>parse_Document</ttcode> jest wywołany tylko raz, ponieważ jest tylko jeden węzeł klasy <ttcode>Document</ttcode> w dokumencie XML i tylko jeden obiekt klasy <ttcode>Document</ttcode> w przeparsowanej reprezentacji XML-a. Tu po prostu idziemy dalej i parsujemy część główną pliku gramatyki.
# <ttcode>parse_Text</ttcode> 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.
# <ttcode>parse_Comment</ttcode> 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żzauważmy, że nadal musimy zdefniowaćzdefiniować funkcję i wyraźnie stwierdzić, żeby nic nie robiła. Jeśli funkcja nie będzie istniała, funkcja <ttcode>parse</ttcode> nawali tak szybko, jak napotka się na komentarz, ponieważ będzie próbowała znaleźć nieistniejącą funkcję <ttcode>parse_Comment</ttcode>. 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 <ttcode>parse_Element</ttcode> jest faktyczniew 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 <ttcode>'do_xref'</ttcode> (dla znacznika <tt><nowiki><xref></nowiki></tt>), znajdujemy funkcję o takiej nazwie i wywołujemy ją. I takrobimy dalej,podobnie dla każdegokażdej innej nazwy znacznika, która zostanie znaleziona, oczywiście w pliku gramatyki (czyli znaczniki <tt><nowiki><p></nowiki></tt>, czy też <tt><nowiki><choice></nowiki></tt>).
}}
 
W tym przykładzie funkcja pośrednicząca <ttcode>parse</ttcode> i <ttcode>parse_Element</ttcode> 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), powinieneś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 16'',funkcyjne|rozdziale ''"Programowanie funkcyjne''"]].
 
<noinclude>
{{Nawigacja|Zanurkuj w Pythonie|
[[../Wyszukanie bezpośrednich elementów potomnych/]]|
[[../Obsługa argumentów linii poleceń|Obsługa argumentów linii poleceń/]]|
}}
{{Podświetl|py}}
</noinclude>