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).

A menudo se querrá tener más control sobre el formato de la salida, y
no  simplemente imprimir valores separados por espacios. Para ello,
hay varias maneras de dar formato a la salida.

* Para usar formatted string literals, comience una cadena con "f" o
  "F" antes de la comilla de apertura o comillas triples. Dentro de
  esta cadena,  se puede escribir una expresión de Python entre los
  caracteres "{" y "}" que pueden hacer referencia a variables o
  valores literales.

     >>> year = 2016
     >>> event = 'Referendum'
     >>> f'Results of the {year} {event}'
     'Results of the 2016 Referendum'

* El método  "str.format()" requiere más esfuerzo manual.  Se seguirá
  usando "{" y "}" para marcar dónde se sustituirá una variable y
  puede proporcionar directivas de formato detalladas, pero también se
  debe proporcionar la información de lo que se va a formatear.

     >>> yes_votes = 42_572_654
     >>> no_votes = 43_132_495
     >>> percentage = yes_votes / (yes_votes + no_votes)
     >>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
     ' 42572654 YES votes  49.67%'

* Por último, puede realizar todo el control de cadenas usted mismo
  mediante operaciones de concatenación y segmentación de cadenas para
  crear cualquier diseño que se pueda imaginar.  El tipo de cadena
  tiene algunos métodos que realizan operaciones útiles para rellenar
  cadenas a un ancho de columna determinado.

Cuando no necesita una salida elegante, pero solo desea una
visualización rápida de algunas variables con fines de depuración,
puede convertir cualquier valor en una cadena con las funciones
"repr()" o "str()".

La función "str()" retorna 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 sintaxis equivalente).  Para objetos que no
tienen una representación en particular para consumo humano, "str()"
retornará 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'))"

El módulo "string" contiene una clase "Template" que ofrece otra forma
de sustituir valores en cadenas, utilizando marcadores de posición
como *$x`* y reemplazarlos con valores desde un diccionario, pero esto
ofrece mucho menos control en el formato.


7.1.1. Formatear cadenas literales
----------------------------------

Formatted string literals (también llamados f-strings para abreviar)
le permiten incluir el valor de las expresiones de Python dentro de
una cadena prefijando la cadena con "f" o "F" y escribiendo
expresiones como "{expresion}".

La expresión puede ir seguida de un especificador de formato opcional
. Esto permite un mayor control sobre cómo se formatea el valor. En el
ejemplo siguiente se redondea pi a tres lugares después del decimal:

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

Pasar un entero después de "':'" hará que ese campo sea un número
mínimo de caracteres de ancho. Esto es útil para hacer que las
columnas se alineen.

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

Se pueden utilizar otros modificadores para convertir el valor antes
de formatearlo. "'!a'" se aplica "ascii()", "'!s'" se aplica "str()",
y "'!r'" se aplica "repr()":

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

Para obtener una referencia sobre estas especificaciones de formato,
consulte la guía de referencia para Especificación de formato Mini-
Lenguaje.


7.1.2. El método format() de cadenas
------------------------------------

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 "str.format()".

   >>> 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.

Si tiene una cadena de caracteres de formato realmente larga que no
desea dividir, sería bueno si pudiera hacer referencia a las variables
que se formatearán por nombre en lugar de por posición. Esto se puede
hacer simplemente pasando el dict 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 retorna un diccionario conteniendo todas las variables
locales.

Como ejemplo, las siguientes lineas producen un conjunto de columnas
alineadas ordenadamente que dan enteros y sus cuadrados y cubos:

   >>> 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

Para una completa descripción del formateo de cadenas con
"str.format()", mirá en Formato de cadena de caracteres personalizado.


7.1.3. Formateo manual de cadenas
---------------------------------

Aquí está la misma tabla de cuadrados y cubos, formateados
manualmente:

   >>> 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

(Resaltar que el espacio existente entre cada columna es añadido
debido a como funciona "print()": siempre añade espacios entre sus
argumentos.)

El método "str.rjust()" de los objetos cadena justifica a la derecha
en un campo de anchura predeterminada  rellenando con espacios a la
izquierda. Métodos similares a este son "str.ljust()" y
"str.center()". Estos métodos no escriben nada, simplemente retornan
una nueva cadena. Si la cadena de entrada es demasiado larga no la
truncarán sino que la retornarán sin cambios; esto desordenará la
disposición de la columna que es, normalmente, mejor que la
alternativa, la cual podría dejar sin usar un valor. (Si realmente
deseas truncado siempre puedes añadir una operación de rebanado, como
en "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'


7.1.4. Viejo formateo de cadenas
--------------------------------

El operador % (módulo) también se puede utilizar para formatear
cadenas de caracteres. Dados los "'cadena de caracteres' % valores",
las instancias de "%" en "cadena de caracteres" se reemplazan con cero
o más elementos de "valores". Esta operación se conoce comúnmente como
interpolación de cadenas. 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 Formateo de cadenas al
estilo *printf*.


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

"open()" returns a *file object*, and is most commonly used with two
positional arguments and one keyword argument: "open(filename, mode,
encoding=None)"

   >>> f = open('workfile', 'w', encoding="utf-8")

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.

Normally, files are opened in *text mode*, that means, you read and
write strings from and to the file, which are encoded in a specific
*encoding*. If *encoding* is not specified, the default is platform
dependent (see "open()"). Because UTF-8 is the modern de-facto
standard, "encoding="utf-8"" is recommended unless you know that you
need to use a different encoding. Appending a "'b'" to the mode opens
the file in *binary mode*. Binary mode data is read and written as
"bytes" objects. You can not specify *encoding* when opening file in
binary mode.

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.

Es una buena práctica usar la declaración "with" cuando manejamos
objetos archivo.  Tiene la ventaja que el archivo es cerrado
apropiadamente luego de que el bloque termina, incluso si se generó
una excepción.  También es mucho más corto que escribir los
equivalentes bloques "try"-"finally"

   >>> with open('workfile', encoding="utf-8") as f:
   ...     read_data = f.read()

   >>> # We can check that the file has been automatically closed.
   >>> f.closed
   True

Si no está utilizando la palabra clave "with", entonces debe llamar a
"f.close()" para cerrar el archivo y liberar inmediatamente los
recursos del sistema utilizados por él.

Advertencia:

  Al llamar a "f.write()" sin usar la palabra clave :keyword::*!with*
  o llamar a "f.close()" **podría** dar como resultado los argumentos
  de "f.write()" no se escribe completamente en el disco, incluso si
  el programa se cierra correctamente.

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 utiliza "f.read(size)", el cual
lee alguna cantidad de datos y los retorna como una cadena de (en modo
texto) o un objeto de bytes (en modo binario). *size* es un argumento
numérico opcional.  Cuando se omite *size* o es negativo, el contenido
entero del archivo será leído y retornado; es tu problema si el
archivo es el doble de grande que la memoria de tu máquina.  De otra
manera, como máximo *size* caracteres (en modo texto) o *size* bytes
(en modo binario) son leídos y retornados. Si se alcanzó el fin del
archivo, "f.read()" retornará una cadena vacía ("''").

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

"f.readline()" lee una sola linea del archivo; el carácter 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()"
retorna 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,
retornando la cantidad de caracteres escritos.

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

Otros tipos de objetos necesitan ser convertidos -- 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()" retorna 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, utiliza "f.seek(offset,
whence)".  La posición es calculada agregando el *offset* a un punto
de referencia; el punto de referencia se selecciona del argumento
*whence*.  Un valor *whence* 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.  *whence* puede omitirse, el valor por defecto 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 fácilmente escribirse y leerse de un archivo.  Los
números toman algo más de esfuerzo, ya que el método "read()" sólo
retorna cadenas, que tendrán que ser pasadas a una función como
"int()", que toma una cadena como "'123'" y retorna 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 estándar 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
   >>> x = [1, 'simple', 'list']
   >>> json.dumps(x)
   '[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)

To decode the object again, if "f" is a *binary file* or *text file*
object which has been opened for reading:

   x = json.load(f)

Nota:

  JSON files must be encoded in UTF-8. Use "encoding="utf-8"" when
  opening JSON file as a *text file* for both of reading and writing.

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 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.
