Cesar Systems

Herramientas Informaticas

10.3. Asignación de alias y copiado

Debe usted estar atento a los alias a causa de la mutabilidad de los diccionarios.

Si dos variables se refieren al mismo objeto los cambios en una afectan a la otra.

Si quiere modificar un diccionario y mantener una copia del original, use el metodo copy. Por ejemplo, opuestos es un diccionario que contiene pares de opuestos:

   1: >>> opuestos = {'arriba': 'abajo', 'derecho': 'torcido',

   2: ...             'verdadero': 'falso'}

   3: >>> alias = opuestos

   4: >>> copia = opuestos.copy()

alias y opuestos se refieren al mismo objeto; copia hace referencia a una copia nueva del mismo diccionario. Si modificamos alias, opuestos también resulta cambiado:

   1: >>> alias['derecho'] = 'sentado'

   2: >>> opuestos['derecho']

   3: 'sentado'

Si modificamos copia, opuestos no varía:

   1: >>> copia['derecho'] = 'privilegio'

   2: >>> opuestos['derecho']

   3: 'sentado'

10.4. Matrices dispersas

En la Sección 8.14 usamos una lista de listas para representar una matriz. Es una buena opción para una matriz en la que la mayoría de los valores es diferente de cero, pero piense en una matriz como esta:

0 2 0 0 0
0 0 0 1 0
0 0 0 0 0
0 0 0 3 0
0 0 0 0 0

La representación de la lista contiene un montón de ceros:

 

   1: matriz = [ [0,0,0,1,0],

   2:            [0,0,0,0,0],

   3:            [0,2,0,0,0],

   4:            [0,0,0,0,0],

   5:            [0,0,0,3,0] ]

Una posible alternativa es usar un diccionario. Como claves, podemos usar tuplas que contengan los números de fila y columna. Esta es la representación de la misma matriz por medio de un diccionario:

   1: matriz = {(0,3): 1, (2, 1): 2, (4, 3): 3}

Solo hay tres pares clave-valor, una para cada elemento de la matriz diferente de cero. Cada clave es una tupla, y cada valor es un entero.

Para acceder a un elemento de la matriz, podemos usar el operador []:

   1: matriz[0,3]

   2: 1

Fíjese en que la sintaxis para la representación por medio del diccionario no es la misma de la representación por medio de la lista anidada. En lugar de dos índices enteros, usamos un índice que es una tupla de enteros.

Hay un problema. Si apuntamos a un elemento que es cero, se produce un error porque en el diccionario no hay una entrada con esa clave:

   1: >>> matriz[1,3]

   2: KeyError: (1, 3)

El metodo get soluciona este problema:

   1: >>> matriz.get((0,3), 0)

   2: 1

El primer argumento es la clave; el segundo argumento es el valor que debe devolver get en caso de que la clave no este en el diccionario:

   1: >>> matriz.get((1,3), 0)

   2: 0

get mejora sensiblemente la semántica del acceso a una matriz dispersa. Lástima de sintaxis.

10.5. Pistas

Si estuvo jugando con la función fibonacci de la Sección 5.7, es posible que haya notado que cuanto mas grande es el argumento que le da, mas tiempo le cuesta ejecutarse. Mas aun, el tiempo de ejecución aumenta muy rápidamente.

En nuestra maquina, fibonacci(20) acaba instantáneamente, fibonacci(30) tarda mas o menos un segundo, y fibonacci(40) tarda una eternidad.

Para entender por que, observe este grafico de llamadas de fibonacci con n=4:

Sin título

Un grafico de llamadas muestra un conjunto de cajas de función con líneas que conectan cada caja con las cajas de las funciones a las que llama. En lo alto del grafico, fibonacci con n=4 llama a fibonacci con n=3 y n=2. A su vez, fibonacci con n=3 llama a fibonacci con n=2 y n=1. Y así sucesivamente.

Cuente cuantas veces se llama a fibonacci(0) y fibonacci(1). Es una solución ineficaz al problema, y empeora mucho tal como crece el argumento.

Una buena solución es llevar un registro de los valores que ya se han calculado almacenándolos en un diccionario. A un valor que ya ha sido calculado y almacenado para un uso posterior se le llama pista. Aquí hay una implementación de fibonacci con pistas:

   1: anteriores = {0:1, 1:1}

   2: def fibonacci(n):

   3:         if anteriores.has_key(n):

   4:             return anteriores[n]

   5:         else:

   6:         nuevoValor = fibonacci(n-1) + fibonacci(n-2)

   7:         anteriores[n] = nuevoValor

   8:         return nuevoValor

El diccionario llamado anteriores mantiene un registro de los valores de Fibonacci que ya conocemos. El programa comienza con solo dos pares: 0 corresponde a 1 y 1 corresponde a 1.

Siempre que se llama a fibonacci comprueba si el diccionario contiene el resultado ya calculado. Si esta ahí, la función puede devolver el valor inmediatamente sin hacer mas llamadas recursivas. Si no, tiene que calcular el nuevo valor. El nuevo valor se añade al diccionario antes de que la función vuelva.

Con esta versión de fibonacci, nuestra maquina puede calcular fibonacci(40) en un abrir y cerrar de ojos. Pero cuando intentamos calcular fibonacci(50), nos encontramos con otro problema:

   1: >>> fibonacci(50)

   2: OverflowError: integer addition

La respuesta, como vera en un momento, es 20.365.011.074. El problema es que este numero es demasiado grande para caber en un entero de Python. Se desborda. Afortunadamente, hay una solución fácil para este problema.

 

10.6. Enteros largos

Python proporciona un tipo llamado long int que puede manejar enteros de cualquier tamaño. Hay dos formas de crear un valor long int. Una es escribir un entero con una L mayúscula al final:

   1: >>> type(1L)

   2: <type 'long int'>

La otra es usar la función long para convertir un valor en long int. long acepta cualquier tipo numérico e incluso cadenas de dígitos:

   1: >>> long(1)

   2: 1L

   3: >>> long(3.9)

   4: 3L

   5: >>> long('57')

   6: 57L

Todas las operaciones matemáticas funcionan sobre los long ints, así que no tenemos que hacer mucho para adaptar fibonacci:

   1: >>> previous = {0:1L, 1:1L}

   2: >>> fibonacci(50)

   3: 20365011074L

Simplemente cambiando el contenido inicial de anteriores cambiamos el comportamiento de fibonacci. Los primeros dos números de la secuencia son long ints, así que todos los números subsiguientes lo serán también.

Como ejercicio, modifique factorial de forma que produzca un
long int como resultado.

10.7. Contar letras

En el capítulo 7 escribimos una función que contaba el numero de apariciones de una letra en una cadena. Una versión mas genérica de este problema es crear un histograma de las letras de la cadena, o sea, cuantas veces aparece cada letra.

Ese histograma podría ser útil para comprimir un archivo de texto. Como las diferentes letras aparecen con frecuencias distintas, podemos comprimir un archivo usando códigos cortos para las letras mas habituales y códigos mas largos para las que aparecen con menor frecuencia.

Los diccionarios facilitan una forma elegante de generar un histograma:

   1: >>> cuentaLetras = {}

   2: >>> for letra in "Mississippi":

   3: ... cuentaLetras[letra] = cuentaLetras.get (letra, 0) + 1

   4: ...

   5: >>> cuentaLetras

   6: {'M': 1, 's': 4, 'p': 2, 'i': 4}

   7: >>>

 

Inicialmente, tenemos un diccionario vacío. Para cada letra de la cadena, buscamos el recuento actual (posiblemente cero) y lo incrementamos. Al final, el diccionario contiene pares de letras y sus frecuencias.

Puede ser mas atractivo mostrar el histograma en orden alfabético. Podemos hacerlo con los métodos items y sort:

   1: >>> itemsLetras = cuentaLetras.items()

   2: >>> itemsLetras.sort()

   3: >>> print itemsLetras

   4: [('M', 1), ('i', 4), ('p', 2), ('s', 4)]

Ya había visto usted el metodo items, pero sort es el primer metodo aplicable a listas que hemos visto. Hay varios mas, como append, extend, y reverse.

Consulte la documentación de Python para ver los detalles.

10.8. Glosario

diccionario: Una colección de pares clave-valor que establece una correspondencia entre claves y valores. Las claves pueden ser de cualquier tipo inmutable, los valores pueden ser de cualquier tipo.

clave: Un valor que se usa para buscar una entrada en un diccionario.

par clave-valor: Uno de los elementos de un diccionario, también llamado ”asociacion”.

metodo: Un tipo de función al que se llama con una sintaxis diferente y al que se invoca sobre” un objeto.

invocar: Llamar a un metodo.

pista: Almacenamiento temporal de un valor pre-calculado para evitar cálculos redundantes.

desbordamiento: Un resultado numérico que es demasiado grande para representarse en formato numérico.

Capítulo 11 Archivos y excepciones

Cuando un programa se esta ejecutando, sus datos están en la memoria. Cuando un programa termina, o se apaga el computador, los datos de la memoria desaparecen. Para almacenar los datos de forma permanente debe usted ponerlos en
un archivo. Normalmente los archivos se guardan en un disco duro, disquete o CD-ROM.

Cuando hay un gran numero de archivos, suelen estar organizados en directorios (también llamados “carpetas”). Cada archivo se identifica con un nombre único, o una combinación de nombre de archivo y nombre de directorio.

Leyendo y escribiendo archivos, los programas pueden intercambiar información entre ellos y generar formatos imprimibles como PDF.

Trabajar con archivos se parece mucho a trabajar con libros. Para usar un libro, tiene que abrirlo. Cuando ha terminado, tiene que cerrarlo. Mientras el libro esta abierto, puede escribir en el o leer de el. En cualquier caso, sabe en que lugar del libro se encuentra. Casi siempre lee el libro según su orden natural, pero también puede ir saltando de una pagina a otra.

Todo esto sirve también para los archivos. Para abrir un archivo, especifique su nombre e indique si quiere leer o escribir.

La apertura de un archivo crea un objeto archivo. En este ejemplo, la variable f apunta al nuevo objeto archivo.

   1: >>> f = open("test.dat","w")

   2: >>> print f

   3: <open file 'test.dat', mode 'w' at fe820>

La función open toma dos argumentos. El primero es el nombre del archivo y el segundo es el modo. El modo ‘w’ (write) significa que lo estamos abriendo para escribir.

Si no hay un archivo llamado test.dat se creara. Si ya hay uno, el archivo que estamos escribiendo lo reemplazara.

Al imprimir el objeto archivo, vemos el nombre del archivo, el modo y la localización del objeto.

Para meter datos en el archivo invocamos al metodo write sobre el objeto archivo:

   1: >>> f.write("Ya es hora")

   2: >>> f.write("de cerrar el archivo")

El cierre del archivo le dice al sistema que hemos terminado de escribir y deja el archivo listo para leer:

   1: >>> f.close()

Ya podemos abrir el archivo de nuevo, esta vez para lectura, y poner su contenido en una cadena. Esta vez el argumento de modo es ‘r’ (read) para lectura:

   1: >>> f = open("test.dat","r")

Si intentamos abrir un archivo que no existe, recibimos un mensaje de error:

   1: >>> f = open("test.cat","r")

   2: IOError: [Errno 2] No such file or directory: 'test.cat'

Como era de esperar, el metodo read lee datos del archivo. Sin argumentos, lee el archivo completo:

   1: >>> text = f.read()

   2: >>> print text

   3: Ya es horade cerrar el archivo

No hay un espacio entre “hora” y “de” porque no escribimos un espacio entre las cadenas.

read también puede aceptar un argumento que le indica cuantos caracteres leer:

   1: >>> f = open("test.dat","r")

   2: >>> print f.read(7)

   3: Ya es h

Si no quedan suficientes caracteres en el archivo, read devuelve los que haya.

Cuando llegamos al final del archivo, read devuelve una cadena vacía:

   1: >>> print f.read(1000006)

   2: orade cerrar el archivo

   3: >>> print f.read()

   4: >>>

La siguiente función copia un archivo, leyendo y escribiendo los caracteres de cincuenta en cincuenta. El primer argumento es el nombre del archivo original; el segundo es el nombre del archivo nuevo:

   1: def copiaArchivo(archViejo, archNuevo):

   2:     f1 = open(archViejo, "r")

   3:     f2 = open(archNuevo, "w")

   4:     while 1:

   5:         texto = f1.read(50)

   6:         if texto == "":

   7:             break

   8:         f2.write(texto)

   9:     f1.close()

  10:     f2.close()

  11:     return

La sentencia break es nueva. Su ejecución interrumpe el bucle; el flujo de la ejecución pasa a la primera sentencia tras el bucle.

En este ejemplo, el bucle while es infinito porque el valor 1 siempre es verdadero.

La única forma de salir del bucle es ejecutar break, lo que sucede cuando texto es una cadena vacía, lo que sucede cuando llegamos al final del archivo.

11.1. Archivos de texto

Un archivo de texto es un archivo que contiene caracteres imprimibles y espacios organizados en líneas separadas por caracteres de salto de línea. Como Python esta diseñado específicamente para procesar archivos de texto, proporciona métodos que facilitan la tarea.

Para hacer una demostración, crearemos un archivo de texto con tres líneas de texto separadas por saltos de línea:

   1: >>> f = open("test.dat","w")

   2: >>> f.write("línea unonl³nea dosnlínea tresn")

   3: >>> f.close()

El metodo readline lee todos los caracteres hasta e inclusive el siguiente salto de línea:

   1: >>> f = open("test.dat","r")

   2: >>> print f.readline()

   3: línea uno

   4: >>>

readlines devuelve todas las líneas que queden como una lista de cadenas:

   1: >>> print f.readlines()

   2: ['línea dos12', 'línea tres12']

En este caso, la salida esta en forma de lista, lo que significa que las cadenas aparecen con comillas y el carácter de salto de línea aparece como la secuencia de escape 012.

Al final del archivo, readline devuelve una cadena vacía y readlines devuelve una lista vacía:

   1: >>> print f.readline()

   2: >>> print f.readlines()

   3: []

Lo que sigue es un ejemplo de un programa de proceso de líneas. filtraArchivo hace una copia de archViejo, omitiendo las líneas que comienzan por #:

   1: def filtraArchivo(archViejo, archNuevo):

   2:     f1 = open(archViejo, "r")

   3:     f2 = open(archNuevo, "w")

   4:     while 1:

   5:         texto = f1.readline()

   6:         if texto == "":

   7:             break

   8:         if texto[0] == '#':

   9:             continue

  10:         f2.write(texto)

  11:     f1.close()

  12:     f2.close()

  13:     return

La sentencia continue termina la iteración actual del bucle, pero sigue haciendo bucles. El flujo de ejecución pasa al principio del bucle, comprueba la condición y continua en consecuencia.

Así, si texto es una cadena vacía, el bucle termina. Si el primer carácter de texto es una almohadilla, el flujo de ejecución va al principio del bucle. Solo si ambas condiciones fallan copiamos texto en el archivo nuevo.

11.2. Escribir variables

El argumento de write debe ser una cadena, así que si queremos poner otros valores en un archivo, tenemos que convertirlos antes en cadenas. La forma mas fácil de hacerlo es con la función str:

   1: >>> x = 52

   2: >>> f.write (str(x))

Una alternativa es usar el operador de formato %. Cuando aplica a enteros, % es el operador de modulo. Pero cuando el primer operando es una cadena, % es el operador de formato.

El primer operando es la cadena de formato, y el segundo operando es una tupla de expresiones. El resultado es una cadena que contiene los valores de las expresiones, formateados de acuerdo a la cadena de formato.

A modo de ejemplo simple, la secuencia de formato ‘%d’ significa que la primera expresión de la tupla debería formatearse como un entero. Aquí la letra d quiere decir “decimal”:

   1: >>> motos = 52

   2: >>> "%d" % motos

   3: '52'

El resultado es la cadena ’52’, que no debe confundirse con el valor entero 52.

Una secuencia de formato puede aparecer en cualquier lugar de la cadena de formato, de modo que podemos incrustar un valor en una frase:

   1: >>> motos = 52

   2: >>> "En julio vendimos %d motos." % motos

   3: 'En julio vendimos 52 motos.'

La secuencia de formato ‘%f’ formatea el siguiente elemento de la tupla como un numero en coma flotante, y ‘%s’ formatea el siguiente elemento como una cadena:

   1: >>> "En %d días ingresamos %f millones de %s."

   2: % (34,6.1,'dolares')

   3: 'En 34 días ingresamose 6.100000 miliones de dolares.'

Por defecto, el formato de coma flotante imprime seis decimales.

El numero de expresiones en la tupla tiene que coincidir con el numero de secuencias de formato de la cadena. Igualmente, los tipos de las expresiones deben coincidir con las secuencias de formato:

   1: >>> "%d %d %d" % (1,2)

   2: TypeError: not enough arguments for format string

   3: >>> "%d" % 'dolares'

   4: TypeError: illegal argument type for built-in operation

En el primer ejemplo, no hay suficientes expresiones; en el segundo, la expresiones de un tipo incorrecto.

Para tener mas control sobre el formato de los números, podemos detallar el numero de dígitos como parte de la secuencia de formato:

   1: >>> "%6d" % 62

   2: ' 62'

   3: >>> "%12f" % 6.1

   4: ' 6.100000'

El numero tras el signo de porcentaje es el numero mínimo de espacios que ocupara el numero. Si el valor necesita menos dígitos, se añaden espacios en blanco delante del numero. Si el numero de espacios es negativo, se añaden los espacios tras el numero:

   1: >>> "%-6d" % 62

   2: '62

También podemos especificar el numero de decimales para los números en coma flotante:

   1: >>> "%12.2f" % 6.1

   2: ' 6.10'

En este ejemplo, el resultado ocupa doce espacios e incluye dos dígitos tras la coma. Este formato es útil para imprimir cantidades de dinero con las comas alineadas.

Imagine, por ejemplo, un diccionario que contiene los nombres de los estudiantes como clave y las tarifas horarias como valores. He aquí una función que imprime el contenido del diccionario como un informe formateado:

   1: def informe (tarifas) :

   2:     estudiantes = tarifas.keys()

   3:     estudiantes.sort()

   4:         for estudiante in estudiantes :

   5:             print "%-20s %12.02f" % (estudiante, tarifas[estudiante])

Para probar la función, crearemos un pequeño diccionario e imprimiremos el contenido:

 

   1: >>> tarifas = {'maría': 6.23, 'josé': 5.45, 'jesús': 4.25}

   2: >>> informe (tarifas)

   3: josé              5.45

   4: jesús             4.25

   5: maría             6.23

Controlando la anchura de cada valor nos aseguramos de que las columnas van a quedar alineadas, siempre que los nombres tengan menos de veintiún caracteres y las tarifas sean menos de mil millones la hora.

 

11.3. Directorios

Cuando usted crea un archivo nuevo abriéndolo y escribiendo, el nuevo archivo va al directorio en uso (aquel en el que estuviese al ejecutar el programa).

Del mismo modo, cuando abre un archivo para leerlo, Python lo busca en el directorio en uso.

Si quiere abrir un archivo de cualquier otro sitio, tiene que especificar la ruta del archivo, que es el nombre del directorio (o carpeta) donde se encuentra este:

   1: >>> f = open("/usr/share/dict/words","r")

   2: >>> print f.readline()

   3: Aarhus

Este ejemplo abre un archivo llamado words que esta en un directorio llamado dict, que esta en share, que esta en usr, que esta en el directorio de nivel superior del sistema, llamado.

No puede usar / como parte del nombre de un archivo; esta reservado como delimitador entre nombres de archivo y directorios.

El archivo /usr/share/dict/words contiene una lista de palabras en orden alfabético, la primera de las cuales es el nombre de una universidad danesa.

Página 117 de 143

Creado con WordPress & Tema de Anders Norén