Introducción a la programación en Python
clase 8

Escritura y lectura de archivos

Hay muchas formas de presentar la salida de un programa y en algunos casos conviene escribirla a un archivo. A su vez, los datos a procesar pueden ser extraídos también desde un archivo existente. A continuación veremos algunas de las funciones provistas por el lenguaje para escritura y lectura de archivos.

f = open('./tmp/archivo.ext', 'w')

La función open devuelve un objeto del tipo file (i.e. archivo), y se invoca habitualmente con dos argumentos: 'nombre de archivo' y 'modo'. El primero es una cadena de caracteres que indica el nombre de archivo. El segundo es otra cadena de unos pocos caracteres describiendo la forma en la que se usará el archivo, como se indica a continuación.

'r' el archivo se abre en modo de solo lectura, no se puede escribir (argumento por defecto).
'w' modo de solo escritura (si existe un archivo con el mismo nombre, se borra).
'a' modo de agregado (append), los datos escritos se agregan al final del archivo.
'r+' el archivo se abre para lectura y escritura al mismo tiempo.
'wb'

el archivo se abre en modo binario, para almacenar cualquier cosa que no sea texto

f.read()

La función read(size) permite leer el contenido del archivo. El argumento size es opcional y si no se especifica (o es -1) devuelve el contenido de todo el archivo. Una vez que se leyó todo el archivo, una nueva llamada a la función devuelve un string vacío ('').

In [1]:
f = open('./tmp/archivo_una_sola_linea.txt')
f.read()
Out[1]:
'Esto es todo el archivo\n'

f.readline()

Lee una sola línea del archivo, devuelve al final de la línea el caracter de nueva línea () y solo se omite en la última línea del archivo (si no termina con el caracter de nueva línea). Esto hace que el valor de retorno no sea ambigüo. Si retorna una cadena de caracteres vacía se alcanzó el fin del archivo, mientras que una línea en blanco se representa con un caracter de nueva línea ().

In [2]:
f = open('./tmp/archivo_dos_lineas.txt')
print(f.readline())
print(f.readline())
print(f.readline())
Esta es la primera línea del archivo.

Y esta es la segunda línea.


f.readlines()

Devuelve una lista que contiene todas las líneas del archivo.

In [1]:
f = open('./tmp/archivo_tres_lineas.txt')
lines = f.readlines()
print(lines)
['Esta es la primera línea del archivo.\n', 'Esta es la segunda línea.\n', 'Y esta es la tercera línea.\n']

Una forma de hacer lo mismo, pero más rápida y más eficiente en cuanto a la memoria, es con un bucle de repetición que recorra las líneas del archivo.

In [3]:
f = open('./tmp/archivo_tres_lineas.txt')
for line in f:
    print(line, end='')
Esta es la primera línea del archivo.
Esta es la segunda línea.
Y esta es la tercera línea.

f.write(s)

Escribe el contenido de la cadena de caracteres s al archivo, y devuelve la cantidad de caracteres escritos.

Para escribir algo que no sea una cadena de caracteres, antes se debe convertir a cadena de caracteres.

In [4]:
f = open('./tmp/archivo_nuevo.txt','w')
f.write('Esto es una prueba\n')

valor = ('LA4', 440)
s = str(valor)
f.write(s)
Out[4]:
12

f.close()

Una vez que se terminó de usar el archivo es necesario cerrarlo, para liberar los recursos tomados por el manejo del archivo. Eso se hace con la instrucción f.close(), luego de lo cual una invocación a la función f.read() devuelve un error, porque el archivo está cerrado.

In [5]:
f = open('./tmp/archivo_dos_lineas.txt')
f.close()
f.read()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-fa3ec08f91e6> in <module>()
      1 f = open('./tmp/archivo_dos_lineas.txt')
      2 f.close()
----> 3 f.read()

ValueError: I/O operation on closed file.

Una buena práctica para el manejo de archivos es el uso de la palabra clave with, porque tiene la ventaja de que el archivo se cierra correctamente sin necesidad de preocuparnos por ello, incluso cuando ocurre un error y se lanza una excepción.

In [6]:
with open('./tmp/archivo_dos_lineas.txt', 'r') as f:
    datos = f.read()
f.closed
Out[6]:
True

El módulo pickle

Las cadenas de caractéres se escriben y se leen de archivos de forma sencilla. Pero otro tipo de datos como los números enteros dan un poco más de trabajo porque hay que convertirlos a cadena de caracteres para escribirlos con la función f.write y luego al leerlos con la función f.read es necesario volver a convertirlos en enteros por medio de la función int(). Y las cosas son aún más complicadas si se quiere almacenar estructuras de datos más complejas como listas o diccionarios.

En lugar de forzar a los programadores a escribr complejas secuencias de código que conviertan cualquier tipo de dato a cadena de texto, luego verifiquen y vuelvan a restituír el tipo de dato original, el procedimiento correcto es usar el módulo pickle.

El módulo permite convertir casi cualquier tipo de objeto, guardarlo como una secuencia de bits y luego recuperar el objeto original. La representación del objeto se puede almacenar en un archivo o enviar a través de una conexión de red, etc. Esto recibe el nombre de serialización y es un recurso disponible en diferentes lenguajes de programación. Es la forma estándar en la que se almacenan los objetos para poder ser reutilizados por otros programas o por una nueva invocación del mismo programa. El término técnico para referirse a estos objetos es objetos persistenes.

A continuación un ejemplo del uso de pickle para almacenar un diccionario. Es importante notar la instrucción que importa el módulo al inicio del código, import pickle. Además, el archivo debe abrirse en modo binario, 'b', ya que de esta forma se guardan los datos.

In [7]:
import pickle

notas = {0 : "do", 1 : "do#", 2 : "re", 3 : "mib",
         4 : "mi", 5 : "fa", 6 : "fa#", 7 : "sol",
         8 : "lab", 9 : "la", 10 : "sib", 11 : "si"}

f = open('./tmp/archivo_notas','wb')
pickle.dump(notas,f)
f.close()

f = open('./tmp/archivo_notas','rb')
datos = pickle.load(f)
f.close
print(datos)
{0: 'do', 1: 'do#', 2: 're', 3: 'mib', 4: 'mi', 5: 'fa', 6: 'fa#', 7: 'sol', 8: 'lab', 9: 'la', 10: 'sib', 11: 'si'}

Y el mismo código puede escribirse de forma más compacta y robusta usando la estructura with.

In [8]:
import pickle

notas = {0 : "do", 1 : "do#", 2 : "re", 3 : "mib",
         4 : "mi", 5 : "fa", 6 : "fa#", 7 : "sol",
         8 : "lab", 9 : "la", 10 : "sib", 11 : "si"}

with open('./tmp/archivo_notas', 'wb') as f:
    pickle.dump(notas, f)

with open('./tmp/archivo_notas', 'rb') as f:
    datos = pickle.load(f)
    
print(datos)
{0: 'do', 1: 'do#', 2: 're', 3: 'mib', 4: 'mi', 5: 'fa', 6: 'fa#', 7: 'sol', 8: 'lab', 9: 'la', 10: 'sib', 11: 'si'}

El módulo csv

El formato csv (Comma Separated Values) consiste en valores separados por coma, y es uno de los más utilizados, por ejemplo para importar y exportar archivos de planillas de texto y bases de datos. El módulo csv implementa el manejo de archivos de este tipo. A continuación se presentan algunos ejemplos de su funcionamiento.

In [9]:
import csv
f = open('./tmp/secuencia.csv')
r = csv.reader(f, delimiter=',')
for fila in r:
    print('\t '.join(fila))
f.close()    
i1	  0	  0.75	  72
i1	  0.75	  0.5	  62
i1	  1.25	  1.25	  59
i1	  2.5	  0.25	  56
i1	  2.75	  0.25	  67
i1	  3.0	  0.25	  70
i1	  3.25	  0.5	  66

También es posible guardar un iterable como un elemento en cada fila.

In [10]:
import csv

secuencia =  [[0,    0.75,  72],
              [0.75,  0.5,  62],
              [1.25, 1.25,  59]]

f = open("./tmp/otra_secuencia.csv", "w")
writer = csv.writer(f)
writer.writerows(secuencia)
f.close()

Y existen funciones dentro del módulo para manejar diccionarios como datos separados por coma.

In [11]:
from csv import DictReader

with open('./tmp/secuencia_dictionary.csv') as f:
    r = DictReader(f)
    for linea in r:
      print(linea)
{' duracion': '0.75', ' altura': '72', 'instrumento': '1', ' inicio': ' 0'}
{' duracion': '0.5', ' altura': '62', 'instrumento': '1', ' inicio': ' 0.75'}
{' duracion': '1.25', ' altura': '59', 'instrumento': '1', ' inicio': ' 1.25'}

In [12]:
import csv

secuencia =  [[0,    0.75,  72],
              [0.75,  0.5,  62],
              [1.25, 1.25,  59],
              [2.5,  0.25,  56]]

campos = ['instrumento', 'inicio', 'duracion', 'altura']

with open('./tmp/nuevo_como_diccionario.csv', 'w') as f:
    test_csv = csv.DictWriter(f, delimiter=',', lineterminator='\n', fieldnames=campos)
    test_csv.writeheader() # escribe el encabezado y es opcional
    for i in range(len(secuencia)):
        test_csv.writerow({'instrumento': '1', 'inicio': secuencia[i][0], 
                           'duracion': secuencia[i][1], 'altura': secuencia[i][2]})
        

Ejercicio 8.1

Modifique el Ejercicio 6.1 (y 6.1b) sobre la generación de melodías de características isorrítmicas, para que la salida se guarde como un archivo .sco (partitura) de Csound de modo que pueda ser utilizado en conjunto con un archivo .orc (orquesta) para implementar la síntesis.

Ejercicio 8.1b

Modifique el ejercicio anterior para que la salida del programa pueda ser un archivo .csv en donde cada fila representa las características de una nota. Controle el tipo de salida (archivo .sco o archivo .csv ) en función de una variable tipo_salida que toma un valor diferente en cada caso.