7. Entrada y salida
*******************

Hay diferentes métodos de presentar la salida de un programa; los
datos pueden ser impresos de una forma legible por humanos, o escritos
a un archivo para uso futuro. Este capítulo discutirá algunas de las
posibilidades.


7.1. Formateo elegante de la salida
===================================

Hasta ahora encontramos dos maneras de escribir valores:
*declaraciones de expresión* y la función "print()".  (Una tercera
manera es usando el método "write()" de los objetos tipo archivo; el
archivo de salida estándar puede referenciarse como "sys.stdout".
Mirá la Referencia de la Biblioteca para más información sobre esto).

Often you'll want more control over the formatting of your output than
simply printing space-separated values.  There are two ways to format
your output; the first way is to do all the string handling yourself;
using string slicing and concatenation operations you can create any
layout you can imagine.  The string type has some methods that perform
useful operations for padding strings to a given column width; these
will be discussed shortly.  The second way is to use formatted string
literals, or the "str.format()" method.

The "string" module contains a "Template" class which offers yet
another way to substitute values into strings.

One question remains, of course: how do you convert values to strings?
Luckily, Python has ways to convert any value to a string: pass it to
the "repr()" or "str()" functions.

La función "str()" devuelve representaciones de los valores que son
bastante legibles por humanos, mientras que "repr()" genera
representaciones que pueden ser leídas por el intérprete (o forzarían
un "SyntaxError" si no hay sintáxis equivalente).  Para objetos que no
tienen una representación en particular para consumo humano, "str()"
devolverá el mismo valor que "repr()".  Muchos valores, como números o
estructuras como listas y diccionarios, tienen la misma representación
usando cualquiera de las dos funciones.  Las cadenas, en particular,
tienen dos representaciones distintas.

Algunos ejemplos:

   >>> s = 'Hello, world.'
   >>> str(s)
   'Hello, world.'
   >>> repr(s)
   "'Hello, world.'"
   >>> str(1/7)
   '0.14285714285714285'
   >>> x = 10 * 3.25
   >>> y = 200 * 200
   >>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
   >>> print(s)
   The value of x is 32.5, and y is 40000...
   >>> # The repr() of a string adds string quotes and backslashes:
   ... hello = 'hello, world\n'
   >>> hellos = repr(hello)
   >>> print(hellos)
   'hello, world\n'
   >>> # The argument to repr() may be any Python object:
   ... repr((x, y, ('spam', 'eggs')))
   "(32.5, 40000, ('spam', 'eggs'))"

Here are two ways to write a table of squares and cubes:

   >>> for x in range(1, 11):
   ...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
   ...     # Note use of 'end' on previous line
   ...     print(repr(x*x*x).rjust(4))
   ...
    1   1    1
    2   4    8
    3   9   27
    4  16   64
    5  25  125
    6  36  216
    7  49  343
    8  64  512
    9  81  729
   10 100 1000

   >>> for x in range(1, 11):
   ...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
   ...
    1   1    1
    2   4    8
    3   9   27
    4  16   64
    5  25  125
    6  36  216
    7  49  343
    8  64  512
    9  81  729
   10 100 1000

(Note that in the first example, one space between each column was
added by the way "print()" works: by default it adds spaces between
its arguments.)

This example demonstrates the "str.rjust()" method of string objects,
which right-justifies a string in a field of a given width by padding
it with spaces on the left.  There are similar methods "str.ljust()"
and "str.center()".  These methods do not write anything, they just
return a new string.  If the input string is too long, they don't
truncate it, but return it unchanged; this will mess up your column
lay-out but that's usually better than the alternative, which would be
lying about a value.  (If you really want truncation you can always
add a slice operation, as in "x.ljust(n)[:n]".)

Hay otro método, "str.zfill()", el cual rellena una cadena numérica a
la izquierda con ceros. Entiende signos positivos y negativos:

   >>> '12'.zfill(5)
   '00012'
   >>> '-3.14'.zfill(7)
   '-003.14'
   >>> '3.14159265359'.zfill(5)
   '3.14159265359'

El uso básico del método "str.format()" es como esto:

   >>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
   We are the knights who say "Ni!"

Las llaves y caracteres dentro de las mismas (llamados campos de
formato) son reemplazadas con los objetos pasados en el método
"str.format()".  Un número en las llaves se refiere a la posición del
objeto pasado en el método.

   >>> print('{0} and {1}'.format('spam', 'eggs'))
   spam and eggs
   >>> print('{1} and {0}'.format('spam', 'eggs'))
   eggs and spam

Si se usan argumentos nombrados en el método "str.format()", sus
valores se referencian usando el nombre del argumento.

   >>> print('This {food} is {adjective}.'.format(
   ...       food='spam', adjective='absolutely horrible'))
   This spam is absolutely horrible.

Se pueden combinar arbitrariamente argumentos posicionales y
nombrados:

   >>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
                                                          other='Georg'))
   The story of Bill, Manfred, and Georg.

"'!a'" (apply "ascii()"), "'!s'" (apply "str()") and "'!r'" (apply
"repr()") can be used to convert the value before it is formatted:

   >>> contents = 'eels'
   >>> print('My hovercraft is full of {}.'.format(contents))
   My hovercraft is full of eels.
   >>> print('My hovercraft is full of {!r}.'.format(contents))
   My hovercraft is full of 'eels'.

An optional "':'" and format specifier can follow the field name. This
allows greater control over how the value is formatted.  The following
example rounds Pi to three places after the decimal.

>>> import math
>>> print('The value of PI is approximately {0:.3f}.'.format(math.pi))
The value of PI is approximately 3.142.

Passing an integer after the "':'" will cause that field to be a
minimum number of characters wide.  This is useful for making tables
pretty.

   >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
   >>> for name, phone in table.items():
   ...     print('{0:10} ==> {1:10d}'.format(name, phone))
   ...
   Jack       ==>       4098
   Dcab       ==>       7678
   Sjoerd     ==>       4127

Si tenés una cadena de formateo realmente larga que no querés separar,
podría ser bueno que puedas hacer referencia a las variables a ser
formateadas por el nombre en vez de la posición.  Esto puede hacerse
simplemente pasando el diccionario y usando corchetes "'[]'" para
acceder a las claves

   >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
   >>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
   ...       'Dcab: {0[Dcab]:d}'.format(table))
   Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Esto se podría hacer, también, pasando la tabla como argumentos
nombrados con la notación '**'.

   >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
   >>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
   Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Esto es particularmente útil en combinación con la función integrada
"vars()", que devuelve un diccionario conteniendo todas las variables
locales.

Para una completa descripción del formateo de cadenas con
"str.format()", mirá en Custom String Formatting.


7.1.1. Viejo formateo de cadenas
--------------------------------

El operador "%" también puede usarse para formateo de cadenas.
Interpreta el argumento de la izquierda con el estilo de formateo de
"sprintf()" para ser aplicado al argumento de la derecha, y devuelve
la cadena resultante de esta operación de formateo.  Por ejemplo:

   >>> import math
   >>> print('The value of PI is approximately %5.3f.' % math.pi)
   The value of PI is approximately 3.142.

Podés encontrar más información en la sección printf-style String
Formatting.


7.2. Leyendo y escribiendo archivos
===================================

La función "open()" devuelve un *objeto archivo*, y se usa normalmente
con dos argumentos: "open(nombre_de_archivo, modo)".

   >>> f = open('workfile', 'w')

El primer argumento es una cadena que contiene el nombre del fichero.
El segundo argumento es otra cadena que contiene unos pocos caracteres
describiendo la forma en que el fichero será usado. *mode* puede ser
"'r'" cuando el fichero solo se leerá, "'w'" para solo escritura (un
fichero existente con el mismo nombre se borrará) y "'a'" abre el
fichero  para agregar.; cualquier dato que se escribe en el fichero se
añade automáticamente al final.  "'r+'" abre el fichero tanto para
lectura como para escritura. El argumento *mode* es opcional; se asume
que se usará "'r'" si se omite.

Normalmente, los ficheros se abren en *modo texto*, significa que lees
y escribes caracteres desde y hacia el fichero, el cual se codifica
con una codificación específica. Si no se especifica la codificación
el valor por defecto depende de la plataforma (ver "open()"). "'b'"
agregado al modo abre el fichero en  *modo binario*: y los datos se
leerán y escribirán en forma de objetos de bytes. Este modo debería
usarse en todos los ficheros que no contienen texto.

Cuando se lee en modo texto, por defecto se convierten los fines de
lineas que son específicos a las plataformas ("\n" en Unix, "\r\n" en
Windows) a solamente "\n".  Cuando se escribe en modo texto, por
defecto se convierten los "\n" a los finales de linea específicos de
la plataforma. Este cambio automático está bien para archivos de
texto, pero corrompería datos binarios como los de archivos "JPEG" o
"EXE".  Asegurate de usar modo binario cuando leas y escribas tales
archivos.

It is good practice to use the "with" keyword when dealing with file
objects.  The advantage is that the file is properly closed after its
suite finishes, even if an exception is raised at some point.  Using
"with" is also much shorter than writing equivalent "try"-"finally"
blocks:

   >>> with open('workfile') as f:
   ...     read_data = f.read()
   >>> f.closed
   True

Si no estuvieses usando el bloque "with", entonces deberías llamar
"f.close()" para cerrar el archivo e inmediatamente liberar cualquier
recurso del sistema usado por este. Si no cierras explícitamente el
archivo, el «garbage collector» de Python eventualmente destruirá el
objeto y cerrará el archivo por vos, pero el archivo puede estar
abierto por un tiempo. Otro riesgo es que diferentes implementaciones
de Python harán esta limpieza en diferentes momentos.

Después de que un objeto de archivo es cerrado, ya sea por "with" o
llamando a "f.close()", intentar volver a utilizarlo fallará
automáticamente:

   >>> f.close()
   >>> f.read()
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   ValueError: I/O operation on closed file.


7.2.1. Métodos de los objetos Archivo
-------------------------------------

El resto de los ejemplos en esta sección asumirán que ya se creó un
objeto archivo llamado "f".

Para leer el contenido de una archivo llamá a "f.read(cantidad)", el
cual lee alguna cantidad de datos y los devuelve como una cadena de
(en modo texto) o un objeto de bytes (en modo binario). *cantidad* es
un argumento numérico opcional.  Cuando se omite *cantidad* o es
negativo, el contenido entero del archivo será leido y devuelto; es tu
problema si el archivo es el doble de grande que la memoria de tu
máquina.  De otra manera, a lo sumo una *cantidad* de bytes son leídos
y devueltos. Si se alcanzó el fin del archivo, "f.read()" devolverá
una cadena vacía ("""").

   >>> f.read()
   'This is the entire file.\n'
   >>> f.read()
   ''

"f.readline()" lee una sola linea del archivo; el caracter de fin de
linea ("\n") se deja al final de la cadena, y sólo se omite en la
última linea del archivo si el mismo no termina en un fin de linea.
Esto hace que el valor de retorno no sea ambiguo; si "f.readline()"
devuelve una cadena vacía, es que se alcanzó el fin del archivo,
mientras que una linea en blanco es representada por "'\n'", una
cadena conteniendo sólo un único fin de linea.

   >>> f.readline()
   'This is the first line of the file.\n'
   >>> f.readline()
   'Second line of the file\n'
   >>> f.readline()
   ''

Para leer líneas de un archivo, podés iterar sobre el objeto archivo.
Esto es eficiente en memoria, rápido, y conduce a un código más
simple:

   >>> for line in f:
   ...     print(line, end='')
   ...
   This is the first line of the file.
   Second line of the file

Si querés leer todas las líneas de un archivo en una lista también
podés usar "list(f)" o "f.readlines()".

"f.write(cadena)" escribe el contenido de la *cadena* al archivo,
devolviendo la cantidad de caracteres escritos.

   >>> f.write('This is a test\n')
   15

Otros tipos de objetos necesitan serconvertidos -- tanto a una cadena
(en modo texto) o a un objeto de bytes (en modo binario) -- antes de
escribirlos:

   >>> value = ('the answer', 42)
   >>> s = str(value)  # convert the tuple to string
   >>> f.write(s)
   18

"f.tell()" devuelve un entero que indica la posición actual en el
archivo representada como número de bytes desde el comienzo del
archivo en modo binario y un número opaco en modo texto.

Para cambiar la posición del objeto archivo, usá
"f.seek(desplazamiento, desde_donde)".  La posición es calculada
agregando el *desplazamiento* a un punto de referencia; el punto de
referencia se selecciona del argumento *desde_donde*.  Un valor
*desde_donde* de 0 mide desde el comienzo del archivo, 1 usa la
posición actual del archivo, y 2 usa el fin del archivo como punto de
referencia.  *desde_donde* puede omitirse, el default es 0, usando el
comienzo del archivo como punto de referencia.

   >>> f = open('workfile', 'rb+')
   >>> f.write(b'0123456789abcdef')
   16
   >>> f.seek(5)      # Go to the 6th byte in the file
   5
   >>> f.read(1)
   b'5'
   >>> f.seek(-3, 2)  # Go to the 3rd byte before the end
   13
   >>> f.read(1)
   b'd'

En los archivos de texto (aquellos que se abrieron sin una "b" en el
modo), se permiten solamente desplazamientos con "seek" relativos al
comienzo (con la excepción de ir justo al final con "seek(0, 2)") y
los únicos valores de *desplazamiento* válidos son aquellos retornados
por "f.tell()", o cero. Cualquier otro valor de *desplazamiento*
produce un comportamiento indefinido.

Los objetos archivo tienen algunos métodos más, como "isatty()" y
"truncate()" que son usados menos frecuentemente; consultá la
Referencia de la Biblioteca para una guía completa sobre los objetos
archivo.


7.2.2. Guardar datos estructurados con "json"
---------------------------------------------

Las cadenas pueden facilmente escribirse y leerse de un archivo.  Los
números toman algo más de esfuerzo, ya que el método "read()" sólo
devuelve cadenas, que tendrán que ser pasadas a una función como
"int()", que toma una cadena como "'123'" y devuelve su valor numérico
123.  Sin embargo, cuando querés grabar tipos de datos más complejos
como listas, diccionarios, o instancias de clases, las cosas se ponen
más complicadas.

En lugar de tener a los usuarios constantemente escribiendo y
debugueando código para grabar tipos de datos complicados, Python te
permite usar formato intercambiable de datos popular llamado JSON
(JavaScript Object Notation). El módulo estandar llamado "json" puede
tomar datos de Python con una jerarquía, y convertirlo a
representaciones de cadena de caracteres; este proceso es llamado
*serializing*. Reconstruir los datos desde la representación de cadena
de caracteres es llamado *deserializing*. Entre serialización y
deserialización, la cadena de caracteres representando el objeto
quizás haya sido guardado en un archivo o datos, o enviado a una
máquina distante por una conexión de red.

Nota:

  El formato JSON es comúnmente usando por aplicaciones modernas para
  permitir el intercambio de datos. Muchos programadores ya están
  familiarizados con él, lo cual lo convierte en una buena opción para
  la interoperabilidad.

Si tienes un objeto "x", puedes ver su representación JSON con una
simple línea de código:

   >>> import json
   >>> json.dumps([1, 'simple', 'list'])
   '[1, "simple", "list"]'

Otra variante de la función "dumps()", llamada "dump()", simplemente
serializa el objeto a un *archivo de texto*. Así que, si "f" es un
objeto *archivo de texto* abierto para escritura, podemos hacer:

   json.dump(x, f)

Para decodificar un objeto nuevamente, si "f" es un objeto *archivo de
texto* que fue abierto para lectura:

   x = json.load(f)

La simple técnica de serialización puede manejar listas y
diccionarios, pero serializar instancias de clases arbitrarias en JSON
requiere un poco de esfuerzo extra. La referencia del módulo "json"
contiene una explicación de esto.

Ver también:

  "pickle"  - El  módulo pickle

  Contrariamente a :ref:>>`<<JSON <tut-json>*pickle* es un protocolo
  que permite la serialización de objetos Python arbitrariamente
  complejos.  Como tal, es específico de Python y no se puede utilizar
  para comunicarse con aplicaciones escritas en otros idiomas.
  También es inseguro de forma predeterminada: deserializar los datos
  de *pickle* procedentes de un origen que no es de confianza puede
  ejecutar código arbitrario, si los datos fueron creados por un
  atacante experto.
