10.2. Entrada, salida y error estándar

Los usuarios de UNIX ya estarán familiarizados con el concepto de entrada estándar, salida estándar y salida estándar de error. Esta sección es para los demás.

La salida estándar y la salida estándar de error (se suelen abreviar stdout y stderr) son tuberías[13] incorporadas en todo sistema UNIX. Cuando se imprime algo, sale por la tubería stdout; cuando el programa aborta e imprime información de depuración (como el traceback de Python), sale por la tubería stderr. Ambas se suelen conectar a la ventana terminal donde está trabajando usted para que cuando el programa imprima, usted vea la salida, y cuando un programa aborte, pueda ver la información de depuración. (Si está trabajando en un sistema con un IDE de Python basado en ventanas, stdout y stderr descargan por omisión en la “Ventana interactiva”).

Ejemplo 10.8. Presentación de stdout y 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 Como pudo ver en Ejemplo 6.9, “Contadores simples”, se puede utilizar la función incorporada range de Python para construir bucles contadores simples que repitan algo un número determinado de veces.
2 stdout es un objeto de tipo fichero; llamar a su función write imprimirá la cadena que se le pase. De hecho, esto es lo que hace realmente la función print; añade un retorno de carro al final de la cadena que esté imprimiendo e invoca a sys.stdout.write.
3 En el caso más simple stdout y stderr envían su salida al mismo lugar: al IDE de Python (si está en uno), o a la terminal (si ejecuta Python desde la línea de órdenes). Igual que stdout, stderr no añade un retorno de carro por sí sola; si lo desea, añádalo usted mismo.

stdout y stderr son ambos objetos de tipo fichero, como aquellos de los que hablamos en Sección 10.1, “Abstracción de fuentes de datos”, pero ambos son sólo de escritura. No tienen método read, sólo write. Aún así son objetos de tipo fichero, y puede asignarles cualquier otro objeto de tipo fichero para redirigir su salida.

Ejemplo 10.9. Redirección de la saliad

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

(On Windows, you can use type instead of cat to display the contents of a file.)

Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este 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 Esto imprimirá en la “Ventana interactiva” del IDE (o la terminal, si ejecuta el script desde la línea de órdenes).
2 Guarde siempre stdout antes de redireccionarlo, para que pueda devolverlo luego a la normalidad.
3 Abrimos un fichero para escribir. El fichero será creado en caso de no existir. Si existe quedará sobrescrito.
4 Redirige la salida futura al nuevo fichero acabado de crear.
5 Esto será “impreso” sólo en el fichero de registro; no será visible en la ventana del IDE o en la pantalla.
6 Deja stdout tal como estaba antes de que jugase con él.
7 Cierre el fichero de registro.

La redirección de stderr funciona exactamente igual, usando sys.stderr en lugar de sys.stdout.

Ejemplo 10.10. Redirección de información de error

[usted@localhost kgp]$ python stderr.py
[usted@localhost 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

Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este 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 Abra el fichero de registro donde quiera almacenar la información de depuración.
2 Redirija la salida de error asignándole el objeto del fichero acabado de crear a stderr.
3 Lance una excepción. Observe por la salida de pantalla que esto no imprime nada. Toda la información normal del volcado de pila se ha escrito en error.log.
4 Advierta también que no está cerrando de forma explícita el fichero de registro, ni poniendo en stderr su valor original. No pasa nada, ya que una vez que el programa aborte (debido a la excepción), Python hará limpieza y cerrará el fichero por nosotros, y no afecta para nada que no restauremos el valor de stderr ya que, como mencioné, el programa aborta y Python se detiene. Restaurar el original es importante con stdout si espera hacer cosas más adelante dentro del mismo script.

Dado que es tan común escribir los mensajes de error a la salida de errores, hay una sintaxis abreviada que puede usar en lugar de tomarse las molestias de hacer la redirección explícita.

Ejemplo 10.11. Imprimir en stderr

>>> print 'entering function'
entering function
>>> import sys
>>> print >> sys.stderr, 'entering function' 1
entering function
1 Esta sintaxis abreviada de la sentencia print se puede usar para escribir en cualquier objeto de fichero o de tipo fichero que haya abierto. En este caso puede redirigir una sola sentencia print a stderr sin afectar las siguientes sentencias print.

La entrada estándar, por otro lado, es un objeto de fichero de sólo lectura, y representa los datos que fluyen dentro del programa desde alguno previo. Esto no tendrá mucho sentido para los usuarios del Mac OS clásico, o incluso de Windows que no se manejen de forma fluida en la línea de órdenes de MS-DOS. La manera en que trabaja es que se pueden construir cadenas de órdenes en una única línea, de manera que la salida de un programa se convierte en la entrada del siguiente en la cadena. El primer programa se limita a imprimir en la salida estándar (sin hacer ningún tipo de redirección explícita, sólo ejecuta sentencias print normales, o lo que sea), y el siguiente programa lee de la entrada estándar, y es el sistema operativo el que se encarga de conectar la salida de un programa con la entrada del siguiente.

Ejemplo 10.12. Encadenamiento de órdenes

[usted@localhost kgp]$ python kgp.py -g binary.xml         1
01100111
[usted@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>
[usted@localhost kgp]$ cat binary.xml | python kgp.py -g - 3 4
10110001
1 Como vio en Sección 9.1, “Inmersión”, esto imprimirá una cadena de ocho bits al azar, 0 o 1.
2 Esto imprime simplemente el contenido entero de binary.xml. (Los usuarios de Windows deberían usar type en lugar de cat).
3 Esto imprime el contenido de binary.xml, pero el carácter “|”, denominado carácter de “tubería”, implica que el contenido no se imprimirá en la pantalla. En su lugar se convertirá en la entrada estándar de la siguiente orden, que en este caso llama al script de Python.
4 En lugar de especificar un módulo (como binary.xml), especificamos “-”, que hace que el script carge la gramática desde la entrada estándar en lugar de un fichero específico en el disco (más sobre esto en el siguiente ejemplo). Así que el efecto es el mismo que en la primera sintaxis, donde especificábamos directamente el nombre de fichero de la gramática, pero piense el aumento de posibilidades aquí. En lugar de hacer simplemente cat binary.xml, podría ejecutar un script que genere la gramática de forma dinámica, y entonces pasarlo a otro script mediante una tubería. Puede venir de cualquier parte: una base de datos o algún tipo de meta-script generador de gramáticas, o lo que sea. La idea es que no hace falta cambiar el script kgp.py para incorporar esta funcionalidad. Todo lo que necesita es la capacidad de obtener todos los ficheros de gramática por la entrada estándar, y así podrá desplazar toda la lógica a otro programa.

Entonces, ¿cómo “sabe” el script que ha de leer de la entrada estándar cuando el fichero de la gramática es “-”? No es magia; es código.

Ejemplo 10.13. Lectura de la entrada estándar con 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:

[... corte ...]
1 Ésta es la función openAnything de toolbox.py, que examinamos antes en Sección 10.1, “Abstracción de fuentes de datos”. Todo lo que hemos hecho es añadir tres líneas de código al principio de la función para comprobar si la fuente es “-”; y en ese caso devolvemos sys.stdin. ¡Sí, eso es todo! Recuerde, stdin es un objeto de tipo fichero con método read, así que el resto del código (en kgp.py, donde llama a openAnything) no cambia un ápice.

Footnotes

[13]