Zanurkuj w Pythonie/Standardowy strumień wejścia, wyjścia i błędów
Standardowy strumień wejścia, wyjścia i błędów
edytujUżytkownicy Uniksa są już prawdopodobnie zapoznani z koncepcją standardowego wejścia, standardowego wyjścia i standardowego strumienia błędów. Ten podrozdział jest dla pozostałych osób.
Standardowe wyjście i strumień błędów (powszechnie używana skrócona forma to stdout i stderr) są strumieniami danych wbudowanymi do każdego systemu Unix. Kiedy coś wypisujemy, idzie to do strumienia stdout; kiedy wystąpi błąd w programie, a program wypisze informacje pomocne przy debugowaniu (jak traceback w Pythonie), to wszystko pójdzie do strumienia stderr. Te dwa strumienie są zwykle połączone z oknem terminala, na którym pracujemy, więc jeżeli program coś wypisuje, zobaczymy to na wyjściu, a kiedy program spowoduje błąd, zobaczymy informacje debugujące. (Jeśli pracujemy w systemie z okienkowym IDE Pythona, stdout i stderr domyślnie będą połączone z "interaktywnym oknem".)
>>> for i in range(3): ... print 'Nurkujemy' #(1) Nurkujemy Nurkujemy Nurkujemy >>> import sys >>> for i in range(3): ... sys.stdout.write('Nurkujemy') #(2) NurkujemyNurkujemyNurkujemy >>> for i in range(3): ... sys.stderr.write('Nurkujemy') #(3) NurkujemyNurkujemyNurkujemy
- Jak zobaczyliśmy w przykładzie 6.9, "Prosty licznik", możemy wykorzystać wbudowaną funkcje
range
, aby zbudować prostą pętlę licznikową, która powtarza pewną operację określoną liczbę razy. stdout
jest obiektem plikopodobnym; wywołując jego funkcjęwrite
będziemy wypisywać na wyjście napis, który przekazaliśmy. W rzeczywistość, to właśnie funkcjaprint
naprawdę robi; dodaje ona znak nowej linii do wypisywanego napisu, a następnie wywołujesys.stdout.write
.- W tym prostym przypadku
stdout
istderr
wysyłają wyjście do tego samego miejsca: do IDE Pythona (jeśli jesteśmy w nim) lub do terminala (jeśli mamy uruchomionego Pythona z linii poleceń). Podobnie jakstdout
,stderr
nie dodaje znaku nowej linii za nas; jeśli chcemy, aby ten znak został dodany, musimy to zrobić sami.
Zarówno stdout
i stderr
są obiektami plikopodobnymi, a które omawialiśmy w podrozdziale 10.1, "Abstrakcyjne źródła wejścia", lecz te są tylko do zapisu. Nie posiadają one metody read
, tylko write
. Jednak nadal są one obiektami plikopodobnymi i możemy do nich przypisać inny obiekt pliku lub obiekt plikopodobny, aby przekierować ich wyjście.
[you@localhost kgp]$ python stdout.py Nurkujemy [you@localhost kgp]$ cat out.log Ta wiadomość będzie logowana i nie zostanie wypisana na wyjście
(W Windowsie możemy wykorzystać polecenie type, zamiast cat, aby wyświetlić zawartość pliku.)
#-*- coding: utf-8 -*-
#stdout.py
import sys
print 'Nurkujemy' #(1)
saveout = sys.stdout #(2)
fsock = open('out.log', 'w') #(3)
sys.stdout = fsock #(4)
print 'Ta wiadomość będzie logowana i nie zostanie wypisana na wyjście' #(5)
sys.stdout = saveout #(6)
fsock.close() #(7)
- To zostanie wypisane w interaktywnym oknie IDE (lub w terminalu, jeśli skrypt został uruchomiony z linii poleceń).
- Zawsze, zanim przekierujemy standardowe wyjście, przypisujemy gdzieś
stdout
, dzięki temu, będziemy potem mogli do niego normalnie wrócić. - Otwieramy plik do zapisu. Jeśli plik nie istnieje, zostanie utworzony. Jeśli istnieje, zostanie nadpisany.
- Całe późniejsze wyjście zostanie przekierowane do pliku, który właśnie otworzyliśmy.
- Zostanie to wypisane tylko do pliku out.log; nie będzie widoczne w oknie IDE lub w terminalu.
- Przywracamy
stdout
do początkowej, oryginalnej postaci. - Zamykamy plik out.log.
Dodajmy, że w wypisywanym łańcuchu znaków użyliśmy polskich znaków, a ponieważ nie skorzystaliśmy z unikodu, więc napis ten zostanie wypisany w takiej samej postaci, w jakiej został zapisany w pliku Pythona (czyli wiadomość zostanie zapisana w kodowaniu utf-8). Gdybyśmy skorzystali z unikodu, musielibyśmy wyraźnie zakodować ten napis do jakiegoś kodowania za pomocą metody encode
, ponieważ Python nie wie, z jakiego kodowania chce korzystać utworzony przez nas plik (plik out.log przypisany do zmiennej stdout
).
Przekierowywanie standardowego strumienia błędów (stderr) działa w ten sam sposób, wykorzystując sys.stderr
, zamiast sys.stdout
.
[you@localhost kgp]$ python stderr.py
[you@localhost kgp]$ cat error.log
Traceback (most recent line last):
File "stderr.py", line 6, in ?
raise Exception('ten błąd będzie logowany)
Exception: ten błąd będzie logowany
#stderr.py
#-*- coding: utf-8 -*-
import sys
fsock = open('error.log', 'w') #(1)
sys.stderr = fsock #(2)
raise Exception('ten błąd będzie logowany') #(3) (4)
- Otwieramy plik error.log, gdzie chcemy przechowywać informacje debugujące.
- Przekierowujemy standardowy strumień błędów, dzięki przypisaniu obiektu nowo otwartego pliku do
sys.stderr
. - Rzucamy wyjątek. Zauważmy, że na ekranie wyjściowym nic nie zostanie wypisane. Wszystkie informacje traceback zostały zapisane w error.log.
- Zauważmy także, że nie zamknęliśmy jawnie pliku error.log, a nawet nie przypisaliśmy do
sys.stderr
jego pierwotnej wartości. To jest wspaniałe, że kiedy program się rozwali (z powodu wyjątku), Python wyczyści i zamknie wszystkie pliki za nas. Nie ma żadnej różnicy, czystderr
zostanie przywrócony, czy też nie, ponieważ program się rozwala, a Python kończy działanie. Przywrócenie wartości do oryginalnej, jest bardziej ważne dlastdout
, jeśli zamierzasz później wykonywać jakieś inne operacje w tym samym skrypcie.
Ponieważ powszechnie wypisuje się informacje o błędach na standardowy strumień błędów, Python posiada skrótową składnie, która można wykorzystać do bezpośredniego przekierowywania wyjścia.
stderr
>>> print 'wchodzimy do funkcji' wchodzimy do funkcji >>> import sys >>> print >> sys.stderr, 'wchodzimy do funkcji' #(1) wchodzimy do funkcji
- Ta skrótowa składnia wyrażenia
print
może być wykorzystywana do pisania do dowolnego, otwartego pliku, lub do obiektu plikopodobnego. W tym przypadku, możemy przekierować pojedynczą instrukcjęprint
dostderr
bez wpływu na następne instrukcjeprint
.
Z innej strony, standardowe wejścia jest obiektem pliku tylko do odczytu i reprezentuje dane przechodzące z niektórych wcześniejszych programów. Prawdopodobnie nie jest to zrozumiałe dla klasycznych użytkowników Mac OS-a lub nawet dla użytkowników Windows, którzy nie mieli za wiele do czynienia z linią poleceń MS-DOS-a. Działa to w ten sposób, że konstruujemy ciąg poleceń w jednej linii, w taki sposób, że to co jeden program wypisuje na wyjście, następny w tym ciągu traktuje jako wejście. Pierwszy program prosto wypisuje wszystko na standardowe wyjście (bez korzystania ze specjalnych przekierowań, wykorzystuje normalną instrukcję print
itp.), a następny program czyta ze standardowego wejścia, a system operacyjny udostępnia połączenie pomiędzy wyjściem pierwszego programu, a wyjściem kolejnego.
[you@localhost kgp]$ python kgp.py -g binary.xml #(1)
01100111
[you@localhost kgp]$ cat binary.xml #(2)
<?xml version="1.0"?>
<!DOCTYPE grammar PUBLIC "-//diveintopython.org//DTD Kant Generator Pro v1.0//EN" "kgp.dtd">
<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>
[you@localhost kgp]$ cat binary.xml | python kgp.py -g - #(3) (4)
10110001
- Jak zobaczyliśmy w podrozdziale 9.1, "Nurkujemy", polecenie to wyświetli ciąg ośmiu przypadkowych bitów,
0
i1
. - Dzięki temu po prostu wypiszemy całą zawartość pliku binary.xml. (Użytkownicy Windowsa powinni wykorzystać polecenie type zamiast cat.)
- Polecenie to wypisuje zawartość pliku binary.xml, ale znak "|" (ang. pipe), oznacza, że standardowe wyjście nie zostanie wypisana na ekran. Zamiast tego, zawartość standardowego wyjścia zostanie wykorzystane jako standardowe wejście następnego programu, który w tym przypadku jest skryptem Pythona.
- Zamiast określać modułu (np. binary.xml), dajemy "-", który każe naszemu skryptowi wczytać gramatykę ze standardowego wejścia, zamiast z określonego pliku na dysku. (Więcej o tym, w jaki sposób to się dzieje w następnym przykładzie.) Zatem efekt będzie taki sam, jak w pierwszym poleceniu, gdzie bezpośrednio określamy plik gramatyki, ale tutaj zwróćmy uwagę na rozszerzone możliwości. Zamiast wywoływać cat binary.xml, moglibyśmy uruchomić skrypt, który by dynamicznie generował gramatykę, a następnie mógłby ją doprowadzić do naszego skryptu. Dane mogłyby przyjść skądkolwiek: z bazy danych, innego skryptu generującego gramatykę lub jeszcze inaczej. Zaletą tego jest to, że nie musimy zmieniać w żaden sposób kgp.py, aby dołączyć jakąś funkcjonalność. Jedynie, co potrzebujemy, to możliwość wczytania gramatyki ze standardowego wejścia, a całą logikę dodatkowej funkcjonalności możemy rozdzielić wewnątrz innego programu.
Więc w jaki sposób skrypt "wie", żeby czytać ze standardowego wejścia, gdy plik gramatyki to "-"? To nie jest żadna magia; to tylko właśnie prosty kod.
def openAnything(source):
if source == "-": #(1)
import sys
return sys.stdin
# spróbuj otworzyć za pomocą urllib (jeżeli źródłem jest http, ftp, lub URL)
import urllib
try:
# ... ciach ...
- Jest to funkcja
openAnything
z toolbox.py, którą wcześniej badaliśmy w podrozdziale 10.1, "Abstrakcyjne źródła wejścia”. Wszystko, co musimy zrobić, to dodanie trzech linii kodu na początku, aby sprawdzić, czy źródłem nie jest"-"
; jeśli tak, to zwracamysys.stdin
. Naprawdę, to tylko tyle! Pamiętasz,stdin
jest obiektem plikopodobnym z metodąread
, więc pozostałą część kodu (w kgp.py, gdzie wywołujemy funkcjęopenAnything
) w żaden sposób nie zmieniamy.