Zanurkuj w Pythonie/Abstrakcyjne źródła wejścia: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
zamiana {{Nawigacja|Python|->{{Nawigacja|Zanurkuj w Pythonie| Python/->../
Piotr (dyskusja | edycje)
poprawki
Linia 1:
{{Podświetl|py}}
== Abstrakcyjne źródła wejścia ==
 
Linia 6 ⟶ 5:
Wiele funkcji, które wymagają jakiegoś źródła wejścia, mogłyby po prostu przyjmować jako argument nazwę pliku, następnie go otwierać, czytać, a na końcu go zamykać. Jednak tego nie robią. Zamiast działać w ten sposób, jako argument przyjmują obiekt pliku lub ''obiekt pliko-podobny''.
 
W najprostszym przypadku ''obiekt pliko-podobny'' jest dowolnym obiektem z metodą <ttcode>read</ttcode>, która przyjmuje opcjonalny parametr wielkości, <ttcode>size</ttcode>, a następnie zwraca łańcuch znaków. Kiedy wywołujemy go bez parametru <ttcode>size</ttcode>, odczytuje wszystko, co jest do przeczytania ze źródła wejścia, a potem zwraca te wszystkie dane jako pojedynczy łańcuch znaków. Natomiast kiedy wywołamy metodę <ttcode>read</ttcode> z parametrem <ttcode>size</ttcode>, to odczyta ona tyle bajtów ze źródła wejścia, ile wynosi wartość <ttcode>size</ttcode>, a następnie zwróci te dane. Kiedy ponownie wywołamy tę metodę, zostanie odczytana i zwrócona dalsza porcja danych (czyli dane będą czytane od miejsca, w którym wcześniej skończono czytać).
 
Powyżej opisaliśmy, w jaki sposób działają prawdziwe pliki. Jednak nie musimy się ograniczać do prawdziwych plików. Źródłem wejścia może być wszystko: plik na dysku, strona internetowa, czy nawet jakiś łańcuch znaków. Dopóki przekazujeszprzekazujemy do funkcji ''obiekt pliko-podobny'', a funkcja ta po prostu wywołuje metodę <ttcode>read</ttcode>, to funkcja może obsłużyć dowolny rodzaj wejścia, bez posiadania jakiegoś specjalnego kodu dla każdego rodzaju wejścia.
 
Może się zastanawiaszzastanawiamy, co ma to wspólnego z przetwarzaniem XML-a? Otóż <ttcode>minidom.parse</ttcode> jest taką funkcją, do której możemy przekazać ''obiekt pliko-podobny''.
 
'''{{Python/Przykład
|10.1. |Parsowanie XML-u z pliku'''
|tekst=
 
<nowiki>
>>> from xml.dom import minidom
>>> fsock = open('binary.xml') #(1)
Linia 20 ⟶ 19:
>>> fsock.close() #(3)
>>> print xmldoc.toxml() #(4)
{{samp|<nowiki><?xml version="1.0" ?>
<grammar>
<ref id="bit">
<p>0</p>
<p>1</p>
</ref>
<ref id="byte">
<p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
</grammar></nowiki>}}
</nowiki>
 
# Najpierw otwieramy plik z dysku. Otrzymujemy przez to obiekt pliku.
# Przekazujemy obiekt pliku do funkcji <ttcode>minidom.parse</ttcode>, która wywołuje metodę <ttcode>read</ttcode> z <ttcode>fsock</ttcode> i czyta dokument XML z tego pliku.
# Koniecznie wywołujemy metodę <ttcode>close</ttcode> obiektu pliku, jak już skończyliśmy na nim pracę. <ttcode>minidom.parse</ttcode> nie zrobi tego za nas.
# Wywołując ze zwróconego dokumentu XML metodę <ttcode>toxml()</ttcode>, wypisujemy cały dokument.
}}
 
Dobrze, to wszystko wygląda jak kolosalne marnotrawstwo czasu. W końcu już wcześniej widzieliśmy, że <tt>minidom.parse</tt> może przyjąć jako argument nazwę pliku i wykonać całą robotę z otwieraniem i zamykaniem automatycznie. Prawdą jest, że jeśli chcesz sparsować lokalny plik, możesz przekazać nazwę pliku do <tt>minidom.parse</tt>, a funkcja ta będzie umiała mądrze to wykorzystać. Lecz zauważmy jak podobne i łatwe jest także parsowanie dokumentu XML pochodzącego bezpośrednio z Internetu.
 
Dobrze, to wszystko wygląda jak kolosalne marnotrawstwo czasu. W końcu już wcześniej widzieliśmy, że <code>minidom.parse</code> może przyjąć jako argument nazwę pliku i wykonać całą robotę z otwieraniem i zamykaniem automatycznie. Prawdą jest, że jeśli chcemy sparsować lokalny plik, możemy przekazać nazwę pliku do <code>minidom.parse</code>, a funkcja ta będzie umiała mądrze to wykorzystać. Lecz zauważmy jak podobne i łatwe jest także parsowanie dokumentu XML pochodzącego bezpośrednio z Internetu.
'''Przykład 10.2. Parsowanie XML-a z URL-a '''
 
{{Python/Przykład
<nowiki>
|10.2|Parsowanie XML-a z URL-a
|tekst=
>>> import urllib
>>> usock = urllib.urlopen(<nowiki>'http://slashdot.org/slashdot.rdf'</nowiki>) #(1)
>>> xmldoc = minidom.parse(usock) #(2)
>>> usock.close() #(3)
>>> print xmldoc.toxml() #(4)
{{samp|<nowiki><?xml version="1.0" ?>
<rdf:RDF xmlns="http://my.netscape.com/rdf/simple/0.9/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<channel>
<title>Slashdot</title>
<link>http://slashdot.org/</link>
<description>News for nerds, stuff that matters</description>
</channel>
<image>
<title>Slashdot</title>
<url>http://images.slashdot.org/topics/topicslashdot.gif</url>
<link>http://slashdot.org/</link>
</image>
<item>
<title>To HDTV or Not to HDTV?</title>
<link>http://slashdot.org/article.pl?sid=01/12/28/0421241</link>
</item>
[...ciach...]</nowiki>}}
</nowiki>
 
# Jak już zaobserwowaliśmy w poprzednim rozdziale, <ttcode>urlopen</ttcode> przyjmuje adres URL strony internetowej i zwraca ''obiekt pliko-podobny''. Ponadto, co jest bardzo ważne, obiekt ten posiada metodę <ttcode>read</ttcode>, która zwraca źródło danej strony internetowej.
# Teraz przekazujemy ten ''obiekt pliko-podobny'' do <ttcode>minidom.parse</ttcode>, która posłusznie wywołuje metodę <ttcode>read</ttcode> i parsuje dane XML, które zostają zwrócone przez <ttcode>read</ttcode>. Fakt, że te dane przychodzą teraz bezpośrednio z Internetu, jest kompletnie nieistotny. <ttcode>minidom.parse</ttcode> nie ma o stronach internetowych żadnego pojęcia; on tylko wie coś o ''obiektach pliko-podobnych''.
# Jak tylko ''obiekt pliko-podobny'', który podarował nam <ttcode>urlopen</ttcode>, nie będzie potrzebny, koniecznie zamykamy go.
# Przy okazji, ten URL jest prawdziwy i on naprawdę jest dokumentem XML. Reprezentuje on aktualne nagłówki, techniczne newsy i plotki w [http://slashdot.org/ Slashdot].
}}
 
{{Python/Przykład
'''Przykład 10.3. Parsowanie XML-a z łańcucha znaków (prosty sposób, ale mało elastyczny)'''
|10.3|Parsowanie XML-a z łańcucha znaków (prosty sposób, ale mało elastyczny)
|tekst=
 
>>> contents = "<nowiki><grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar></nowiki>"
<nowiki>
>>> contents = "<grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar>"
>>> xmldoc = minidom.parseString(contents) #(1)
>>> print xmldoc.toxml()
{{samp|<nowiki><?xml version="1.0" ?>
<grammar><ref id="bit"><p>0</p><p>1</p></ref></grammar></nowiki>}}
</nowiki>
 
# <ttcode>minidom</ttcode> posiada metodę <ttcode>parseString</ttcode>, która przyjmuje cały dokument XML w postaci łańcucha znaków i parsuje go. MożeszMożemy ją wykorzystać, zamiast <ttcode>minidom.parse</ttcode>, jeśli wieszwiemy, że posiadaszposiadamy cały dokument w formie łańcucha znaków.
}}
 
OK, to możemy korzystać z funkcji <ttcode>minidom.parse</ttcode> zarówno do parsowania lokalnych plików jak i odległych URL-ów, ale do parsowania łańcuchów znaków wykorzystujemy... inną funkcję. Oznacza to, że jeśli chciałbyśchcielibyśmy, aby nasz program mócmógł dać wyjście z pliku, adresu URL lub łańcucha znaków, potrzebujeszpotrzebujemy specjalnej logiki, aby sprawdzić czy mamy do czynienia z łańcuchem znaków, a jeśli tak, to wywołać funkcję <ttcode>parseString</ttcode> zamiast <ttcode>parse</ttcode>. Jakie to niesatysfakcjonujące...
 
Gdyby tylko był sposób, aby zmienić łańcuch znaków na obiekt ''pliko-podobny'', to moglibyśmy po prostu przekazać ten obiekt do <ttcode>minidom.parse</ttcode>. I rzeczywiście, istnieje moduł specjalnie zaprojektowany do tego: <code>StringIO</code>.
 
{{Python/Przykład
'''Przykład 10.4. Wprowadzenie do StringIO'''
|10.4|Wprowadzenie do <code>StringIO</code>
<nowiki>
|tekst=
>>> contents = "<grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar>"
>>> contents = "<nowiki><grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar></nowiki>"
>>> import StringIO
>>> ssock = StringIO.StringIO(contents) #(1)
>>> ssock.read() #(2)
{{samp|<nowiki>"<grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar>"</nowiki>}}
>>> ssock.read() #(3)
{{samp|<nowiki>''</nowiki>}}
''
>>> ssock.seek(0) #(4)
>>> ssock.read(15) #(5)
{{samp|<nowiki>'<grammar><ref i'</nowiki>}}
>>> ssock.read(15)
{{samp|<nowiki>"d='bit'><p>0</p"</nowiki>}}
>>> ssock.read()
{{samp|<nowiki>'><p>1</p></ref></grammar>'</nowiki>}}
>>> ssock.close() #(6)
</nowiki>
 
# Moduł <ttcode>StringIO</ttcode> zawiera tylko jedną klasę, także nazwaną <ttcode>StringIO</ttcode>, która pozwala zamienić napis w ''obiekt pliko-podobny''. Klasa <ttcode>StringIO</ttcode>, podczas tworzenia instancji, przyjmuje jako parametr łańcuch znaków.
# Teraz już mamy ''obiekt pliko-podobny'' i możemy robić wszystkie możliwe ''pliko-podobne'' operacje. Na przykład <ttcode>read</ttcode>, która zwraca oryginalny łańcuch.
# Wywołując ponownie <ttcode>read</ttcode> otrzymamy pusty napis. W ten sposób działa prawdziwy obiekt pliku; kiedy już zostanie przeczytany cały plik, nie można czytać więcej bez wyraźnego przesunięcia do początku pliku. Obiekt <ttcode>StringIO</ttcode> pracuje w ten sam sposób.
# Możemy jawnie przesunąć się do początku napisu, podobnie jak możemy się przesunąć w pliku, wykorzystując metodę <ttcode>seek</ttcode> obiektu klasy <ttcode>StringIO</ttcode>.
# Możemy także czytać fragmentami łańcuch znaków, przekazującdzięki przekazaniu parametr wielkości <ttcode>size</ttcode> do metody <ttcode>read</ttcode>.
# Za każdym razem, kiedy wywołamy <ttcode>read</ttcode>, zostanie nam zwrócona pozostała część napisu, która nie została jeszcze przeczytana. W dokładnie ten sam sposób działa obiekt pliku.
}}
 
{{Python/Przykład
'''Przykład 10.5. Parsowanie XML-a z łańcucha znaków (sposób z obiektem pliko-podobnym)'''
|10.5|Parsowanie XML-a z łańcucha znaków (sposób z obiektem pliko-podobnym)
<nowiki>
|tekst=
>>> contents = "<grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar>"
>>> contents = "<nowiki><grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar></nowiki>"
>>> ssock = StringIO.StringIO(contents)
>>> xmldoc = minidom.parse(ssock) #(1)
>>> ssock.close()
>>> print xmldoc.toxml()
{{samp|<nowiki><?xml version="1.0" ?></nowiki>}}
{{samp|<nowiki><grammar><ref id="bit"><p>0</p><p>1</p></ref></grammar></nowiki>}}
</nowiki>
 
# Teraz możemy przekazać obiekt pliko-podobny (w rzeczywistości instancję <ttcode>StringIO</ttcode>) do funkcji <ttcode>minidom.parse</ttcode>, któryktóra z kolei wywoła metodę <ttcode>read</ttcode> z tego ''obiektu pliko-podobnego'' i szczęśliwie wszystko przeparsuje, nie zdając sobie nawet sprawy, że wejście to pochodzi z łańcucha znaków.
}}
 
To już wiemy, jak za pomocą pojedynczej funkcji, <tt>minidom.parse</tt>, sparsować dokument XML przechowywany na stronie internetowej, lokalnym pliku, czy w łańcuchu znaków. Dla strony internetowej wykorzystamy <tt>urlopen</tt>, aby dostać ''obiekt pliko-podobny''; dla lokalnego pliku, wykorzystamy <tt>open</tt>; a w przypadku łańcucha znaków skorzystamy z <tt>StringIO</tt>. Lecz teraz pójdźmy trochę do przodu i uogólnijmy też te różnice.
 
To już wiemy, jak za pomocą pojedynczej funkcji, <code>minidom.parse</code>, sparsować dokument XML przechowywany na stronie internetowej, lokalnym pliku, czy w łańcuchu znaków. Dla strony internetowej wykorzystamy <code>urlopen</code>, aby dostać ''obiekt pliko-podobny''; dla lokalnego pliku, wykorzystamy <code>open</code>; a w przypadku łańcucha znaków skorzystamy z <code>StringIO</code>. Lecz teraz pójdźmy trochę do przodu i uogólnijmy też te różnice.
'''Przykład 10.6. <tt>openAnything</tt>'''
 
{{Python/Przykład
<nowiki>
def |10.6|<code>openAnything(source): #(1)</code>
|tekst=
# try to open with urllib (if source is http, ftp, or file URL)
<pre>
import urllib
def tryopenAnything(source): #(1)
# próbuje otworzyć za returnpomocą urllib.urlopen (jeśli source) jest URL-em do http, ftp #(2itp.)
import urllib except (IOError, OSError):
try: pass
return urllib.urlopen(source) #(2)
except (IOError, OSError):
pass
# trypróbuje tootworzyć openza withpomocą nativewbudowanej funkcji open function (ifgdy source isjest ścieżką do pathnamepliku)
try:
return open(source) #(3)
except (IOError, OSError):
pass
# treat source as string
import StringIO
return StringIO.StringIO(str(source)) #(4)
</nowiki>
 
# traktuje source jako łańcuch znaków z danymi
# Funkcja <tt>openAnything</tt> przyjmuje pojedynczy argument, <tt>source</tt>, i zwraca ''obiekt pliko-podobny''. <tt>source</tt> jest łańcuchem znaków o różnym charakterze. Może się odnosić do adresu URL (np. <tt>'http://slashdot.org/slashdot.rdf'</tt>), może być globalną lub lokalną ścieżką do pliku (np. <tt>'binary.xml'</tt>), czy też łańcuchem znaków przechowującym dokument XML, który ma zostać sparsowany.
import StringIO
# Najpierw sprawdzamy, czy <tt>source</tt> jest URL-em. Robimy to brutalnie: próbujemy otworzyć to jako URL i cicho pomijamy błędy spowodowane próbą otworzenia czegoś, co nie jest URL-em. Jest to właściwie eleganckie w tym sensie, że jeśli <tt>urllib</tt> będzie kiedyś obsługiwał nowe typy URL-i, nasz program także je obsłuży i to bez konieczności zmiany kodu. Jeśli <tt>urllib</tt> jest w stanie otworzyć <tt>source</tt>, to <tt>return</tt> wykopie nas bezpośrednio z funkcji i poniższe instrukcje <tt>try</tt> nie będą nigdy wykonywane.
return StringIO.StringIO(str(source)) #(4)
# W innym przypadku, gdy <tt>urllib</tt> wrzasnął na nas i powiedział, że <tt>source</tt> nie jest poprawnym URL-em, zakładamy, że jest to ścieżka do pliku znajdującym się na dysku i próbujemy go otworzyć. Ponownie, nic nie robimy, by sprawdzić, czy <tt>source</tt> jest poprawną nazwą pliku (zasady określające poprawność nazwy pliku są szalenie różne na różnych platformach, dlatego prawdopodobnie i tak byśmy to źle zrobili). Zamiast tego, na ślepo otwieramy plik i cicho pomijamy wszystkie błędy.
</pre>
# W tym miejscu zakładamy, że <tt>source</tt> jest łańcuchem znaków, który przechowuje dokument XML (ponieważ nic innego nie zadziałało), dlatego wykorzystujemy <tt>StringIO</tt>, aby utworzyć ''obiekt pliko-podobny'' i zwracamy go. (Tak naprawdę, ponieważ wykorzystujemy funkcję <tt>str</tt>, <tt>source</tt> nie musi być nawet łańcuchem znaków; może być nawet dowolnym obiektem, a z którego zostanie wykorzystana jego tekstowa <ref>łańcuchowo-znakowa</ref> reprezentacja, a która jest zdefiniowana przez specjalną metodę <tt>__str__</tt>.)
 
# Funkcja <code>openAnything</code> przyjmuje pojedynczy argument, <code>source</code>, i zwraca ''obiekt pliko-podobny''. <code>source</code> jest łańcuchem znaków o różnym charakterze. Może się odnosić do adresu URL (np. <code><nowiki>'http://slashdot.org/slashdot.rdf'</nowiki></code>), może być globalną lub lokalną ścieżką do pliku (np. <code>'binary.xml'</code>), czy też łańcuchem znaków przechowującym dokument XML, który ma zostać sparsowany.
Teraz możemy wykorzystać funkcję <tt>openAnything</tt> w połączeniu z <tt>minidom.parse</tt>, aby utworzyć funkcję, która przyjmuje źródło <tt>source</tt>, które w jakiś sposób odwołuje się do dokumentu XML (może to robić za pomocą adresu URL, lokalnego pliku, czy też dokumentu przechowywanego jako łańcuch znaków), i parsuje je.
# Najpierw sprawdzamy, czy <code>source</code> jest URL-em. Robimy to brutalnie: próbujemy otworzyć to jako URL i cicho pomijamy błędy spowodowane próbą otworzenia czegoś, co nie jest URL-em. Jest to właściwie eleganckie w tym sensie, że jeśli <code>urllib</code> będzie kiedyś obsługiwał nowe typy URL-i, nasz program także je obsłuży i to bez konieczności zmiany kodu. Jeśli <code>urllib</code> jest w stanie otworzyć <code>source</code>, to <code>return</code> wykopie nas bezpośrednio z funkcji i poniższe instrukcje <code>try</code> nie będą nigdy wykonywane.
# W innym przypadku, gdy <code>urllib</code> wrzasnął na nas i powiedział, że <code>source</code> nie jest poprawnym URL-em, zakładamy, że jest to ścieżka do pliku znajdującego się na dysku i próbujemy go otworzyć. Ponownie, nic nie robimy, by sprawdzić, czy <code>source</code> jest poprawną nazwą pliku (zasady określające poprawność nazwy pliku są znacząco różne na różnych platformach, dlatego prawdopodobnie i tak byśmy to źle zrobili). Zamiast tego, na ślepo otwieramy plik i cicho pomijamy wszystkie błędy.
# W tym miejscu zakładamy, że <code>source</code> jest łańcuchem znaków, który przechowuje dokument XML (ponieważ nic innego nie zadziałało), dlatego wykorzystujemy <code>StringIO</code>, aby utworzyć ''obiekt pliko-podobny'' i zwracamy go. (Tak naprawdę, ponieważ wykorzystujemy funkcję <code>str</code>, <code>source</code> nie musi być nawet łańcuchem znaków; może być nawet dowolnym obiektem, a z którego zostanie wykorzystana jego tekstowa reprezentacja, a która jest zdefiniowana przez specjalną metodę <code>__str__</code>.)
}}
 
Teraz możemy wykorzystać funkcję <code>openAnything</code> w połączeniu z <code>minidom.parse</code>, aby utworzyć funkcję, która przyjmuje źródło <code>source</code>, które w jakiś sposób odwołuje się do dokumentu XML (może to robić za pomocą adresu URL, lokalnego pliku, czy też dokumentu przechowywanego jako łańcuch znaków), i parsuje je.
'''Przykład 10.7. Wykorzystanie <tt>openAnything</tt> w kgp.py'''
 
<nowiki>
{{Python/Przykład
class KantGenerator:
|10.7|Wykorzystanie <code>openAnything</code> w {{Python/Src|kgp/kgp.py|kgp.py}}
def _load(self, source):
|tekst=
sock = toolbox.openAnything(source)
<pre>
xmldoc = minidom.parse(sock).documentElement
class KantGenerator:
sock.close()
def _load(self, source):
return xmldoc
sock = toolbox.openAnything(source)
</nowiki>
xmldoc = minidom.parse(sock).documentElement
sock.close()
return xmldoc
</pre>
}}
 
<noinclude>
{{Nawigacja|Zanurkuj w Pythonie|
[[../Przetwarzanie XML-a - podsumowanie|Przetwarzanie XML-a - podsumowaniePodsumowanie]]|
[[../Standardowy strumień wejścia, wyjścia i błędów|Standardowy strumień wejścia, wyjścia i błędów/]]|
}}
{{Podświetl|py}}
</noinclude>