6.8. Standard input, output, ed error

Gli utenti UNIX hanno già familiarità con i concetti di standard input, standard output e standard error. Questa sezione è per il resto di voi.

Lo standard output e lo standard error (comunemente abbreviati come stdout e stderr) sono pipe integrate in ogni sistema UNIX. Quando stampate qualcosa, essa va alla pipe stdout; quando il vostro programma crasha e stampa informazioni di debug (come un traceback in Python), esse vanno alla pipe stderr. Entrambe queste pipe sono solitamente connesse al terminale video dove state lavorando, così quando un programma stampa, vedete l'output e quando un programma crasha, vedete informazioni di debug. Se state lavorando su un sistema con un IDE Python basato su finestra, stdout e stderr vanno direttamente alla vostra “Finestra interattiva”.

Esempio 6.32. Introduzione a stdout e stderr

>>> for i in range(3):
...     print 'Dive in'             1
Dive in
Dive in
Dive in
>>> import sys
>>> for i in range(3):
...     sys.stdout.write('Dive in') 2
Dive inDive inDive in
>>> for i in range(3):
...     sys.stderr.write('Dive in') 3
Dive inDive inDive in
1 Come abbiamo visto nell'Esempio 4.28, “Semplici contatori”, possiamo usare la funzione built-in range di Python per costruire semplici cicli contatori che ripetono qualcosa un certo numero di volte.
2 stdout è un oggetto simile a quello di un file; chiamando la sua funzione write stamperà qualsiasi stringa che voi gli diate. In effetti, questo è ciò che la funzione print fa realmente; aggiunge un ritorno di carrello (\r) alla fine della stringa che state stampando e chiama sys.stdout.write.
3 Nel caso più semplice, stdout ed stderr mandano il loro output nello stesso posto: l'IDE Python (se la state usando) o ad un terminale (se state usando Python dalla linea di comando). Come stdout, stderr non aggiunge i ritorni di carrello per voi; se li volete, dovete aggiungerli da soli.

stdout e stderr sono entrambi oggetti di tipo file, come quelli discussi nella sezione Astrarre le sorgenti di ingresso, ma sono entrambi di sola scrittura. Non hanno un metodo read ma solo quello write. Inoltre, sono oggetti di tipo file, potete assegnargli qualunque altro oggetto file o simile a file per redirigere i loro output.

Esempio 6.33. Redigere l'output

[f8dy@oliver kgp]$ python stdout.py
Dive in
[f8dy@oliver kgp]$ cat out.log
This message will be logged instead of displayed

Se non lo avete ancora fatto, potete scaricare questo ed altri esempi usati in questo libro.

#stdout.py
import sys

print 'Dive in'                                          1
saveout = sys.stdout                                     2
fsock = open('out.log', 'w')                             3
sys.stdout = fsock                                       4
print 'This message will be logged instead of displayed' 5
sys.stdout = saveout                                     6
fsock.close()                                            7
1 Questo stamperà nella “finestra interattiva” dell'IDE (o del terminale, se lo script gira dalla linea di comando).
2 Salvate sempre l'stdout prima di redirigerlo, così da poter reimpostarlo alla normalità più tardi.
3 Aprite un nuovo file in scrittura.
4 Redirigete tutto l'ulteriore output al nuovo file appena aperto.
5 Questo sarà “stampato” solo nel file di log; non sarà visibile nella finestra dell'IDE o sullo schermo.
6 Reimpostate stdout come si trovava prima che lo modificassimo.
7 Chiudete il file di log.

Redirigere stderr funziona esattamente allo stesso modo, usando sys.stderr invece di sys.stdout.

Esempio 6.34. Redirigere le informazioni di errore

[f8dy@oliver kgp]$ python stderr.py
[f8dy@oliver kgp]$ cat error.log
Traceback (most recent line last):
  File "stderr.py", line 5, in ?
    raise Exception, 'this error will be logged'
Exception: this error will be logged

Se non lo avete ancora fatto, potete scaricare questo ed altri esempi usati in questo libro.

#stderr.py
import sys

fsock = open('error.log', 'w')               1
sys.stderr = fsock                           2
raise Exception, 'this error will be logged' 3 4
1 Aprite il file di log dove volevamo immagazzinare le informazioni di debug.
2 Redirigete lo standar error assegnando l'oggetto file del nostro file di log appena aperto ad stderr.
3 Sollevate un'eccezione. Notate dall'output dello schermo che tutto ciò non stampa nulla sullo schermo. Tutte le usuali informazioni di traceback sono state scritte in error.log.
4 Notate anche che non stiamo chiudendo esplicitamente il nostro file di log, e nemmeno settando stderr al suo valore originale. Questo va bene, dato che una volta che il programmma è crashato (a causa della nostra eccezione), Python cancellerà e chiuderà il file per noi, non fa alcuna differenza che stderr non venga mai reimpostato, visto che, come ho detto, il programma crasha e Python termina. Reimpostare l'originale è molto importante per stdout se prevedete di fare altre modifiche con lo stesso script più tardi.

Standard input, per altri versi, è un oggetto file di sola lettura, rappresenta i dati che scorrono nel programma da qualche altro programma precedente. Questo non avrà molto senso per gli utenti del classico Mac OS, o anche agli utenti Windows che non hanno mai avuto a che fare con la linea di comando dell'MS-DOS. Il suo funzionamento consiste nel costruire una catena di comandi in una singola linea, così che l'output di un programma diventi l'input del prossimo programma della catena. Il primo programma si limita a scrivere sullo standard output (senza fare alcuna redirezione, solo normali istruzioni print o altro), il secondo programma legge dallo standard input ed il sistema operativo si prende cura di connettere l'output di un programma con l'input del seguente.

Esempio 6.35. Concatenare comandi

[f8dy@oliver kgp]$ python kgp.py -g binary.xml         1
01100111
[f8dy@oliver 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>
[f8dy@oliver kgp]$ cat binary.xml | python kgp.py -g - 3 4
10110001
1 Come abbiamo visto nella sezione Immergersi, in questo capitolo, questo codice stamperà una stringa di otto bit casuali, 0 o 1.
2 Questo codice stampa semplicemente l'intero contenuto di binary.xml. Gli utenti Windows dovrebbero usare type invece di cat.
3 Questo codice stampa il contenuto di binary.xml, ma il carattere “|”, chiamato carattere “pipe”, significa che il contenuto non verrà stampato sullo schermo. Al contrario diventerà input per il comando seguente, che in questo caso rappresenta il nostro script Python.
4 Invece di specificare un modulo (come binary.xml), specifichiamo “-”, che farà caricare al nostro script la grammatica dallo standard input invece che da un file sul disco (di più su come accade nel prossimo esempio). Così l'effetto è lo stesso della prima sintassi, dove specificammo il nome del file grammar direttamente, ma pensate alle possibile espansioni. Invece di fare semplicemente cat binary.xml, potremmo lanciare uno script che generi dinamicamente la grammatica, per poi redirigerlo nel nostro script. Può provenire da ovunque: un database, da meta-script generatori di grammatica o altro ancora. Il punto è che non dobbiamo cambiare il nostro script kgp.py completamente, per incorporare ognuna di queste funzionalità. Tutto quello che dobbiamo fare è essere capaci di prendere i file di grammatica dallo standard input, e separare tutta l'altra logica in un altro programma.

Perciò come fa il nostro script “know” per leggere dallo standard input quando il file di grammatica è “-”? Non è magia, è solo codice.

Esempio 6.36. Leggere dallo standard input in kgp.py


def openAnything(source):
    if source == "-":    1
        import sys
        return sys.stdin

    # try to open with urllib (if source is http, ftp, or file URL)
    import urllib
    try:

[... snip ...]
1 Questa è la funzione openAnything da toolbox.py, che abbiamo esaminato nella sezione Astrarre le sorgenti di ingresso. Tutto quello che abbiamo fatto è stato aggiungere tre linee di codice all'inizio della funzione per controllare se il sorgente è “-”; se si, ritorniamo sys.stdin. Veramente, avviene proprio questo! Ricordate, stdin è un oggetto simile ad un file con un metodo read, così il resto del nostro codice (in kgp.py, dove abbiamo chiamato openAnything) non viene modificato di un bit.