Este segundo viaje recorre módulos más avanzados que atienden a necesidades de programación profesional. Estos módulos no aparecen usualmente en los guiones más ligeros.
El módulo repr proporciona una versión de repr() para presentaciones limitadas o contenedores muy anidados:
>>> import repr >>> repr.repr(set('supercalifragilísticoexpialidoso')) "set(['a', 'c', 'd', 'e', 'f', 'g', ...])"
El módulo pprint ofrece un control más fino sobre la presentación tanto con los objetos internos como los definidos por el usuario en un modo legible por el intérprete. Cuando el resultado es más largo que una línea, la ``imprenta estética'' añade cortes de línea y sangrado para hacer evidente la estructura de los datos:
>>> import pprint >>> t = [[[['negro', 'cian'], 'blanco', ['verde', 'rojo']], [['magenta', ... 'amarillo'], 'azul']]] ... >>> pprint.pprint(t, width=30) [[[['negro', 'cian'], 'blanco', ['verde', 'rojo']], [['magenta', 'amarillo'], 'azul']]]
El módulo textwrap da formato a párrafos para encajar en una anchura de pantalla dada:
>>> import textwrap >>> doc = """El método wrap() es como fill(), pero devuelve una lista de ... cadenas en lugar de una cadena larga con saltos de línea para separar ... las líneas cortadas.""" ... >>> print textwrap.fill(doc, width=40) El método wrap() es como fill(), pero devuelve una lista de cadenas en lugar de una cadena larga con saltos de línea para separar las líneas cortadas.
El módulo locale otorga acceso a una base de datos de formatos específicos de cada cultura. El atributo de agrupación de la función de formato de locale proporciona una manera directa de dar formato a números con separadores de grupo:
>>> import locale >>> locale.setlocale(locale.LC_ALL, 'English_United States.1252') 'English_United States.1252' >>> conv = locale.localeconv() # diccionario de convenciones >>> x = 1234567.8 >>> locale.format("%d", x, grouping=True) '1,234,567' >>> locale.format("%s%.*f", (conv['currency_symbol'], ... conv['int_frac_digits'], x), grouping=True) '$1,234,567.80'
El módulo string incluye una versátil clase Template con una sintaxis simplificada adecuada para ser modificada por los usuarios finales. Esto permite que los usuarios personalicen sus aplicaciones sin tener que alterar la aplicación.
El formato utiliza nombres de parámetros formados por "$" con identificadores de Python válidos (caracteres alfanuméricos y guiones de subrayado). Para poder poner parámetros pegados a palabras, hay que poner el nombre del parámetro entre llaves. Si se escribe "$$" se crea un carácter "$" sin significado especial:
>>> from string import Template >>> t = Template('${modo}mente abrazada a $persona.') >>> t.substitute(modo='Suave', persona='tu loco impasible') 'Suavemente abrazada a tu loco impasible.'
El método substitute lanza KeyError cuando falta un parámetro en el diccionario o argumento por nombre. Para las aplicaciones tipo envío masivo, los datos proporcionados por el usuario pueden estar incompletos, por lo que puede resultar más adecuado el método safe_substitute; se dejarán sin tocar los parámetros si faltan datos:
>>> t = Template('Devolver $elemento a $remitente.') >>> d = dict(elemento='golondrina sin carga') >>> t.substitute(d) Traceback (most recent call last): . . . KeyError: 'remitente' >>> t.safe_substitute(d) 'Devolver golondrina sin carga a $remitente.'
Las subclases de Template pueden especificar un delimitador específico. Por ejemplo, un utensilio de renombrado masivo de un visualizador de fotos podría elegir los símbolos de porcentaje para parámetros como la fecha actual, número de secuencia de la imagen o un formato de fichero:
>>> import time, os.path >>> ficheros = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg'] >>> class RenombradoMasivo(Template): ... delimitador = '%' >>> fmt = raw_input('Introducir estilo de renombrado (%d-fecha %n-numsec %f-formato): ') Introducir estilo de renombrado (%d-fecha %n-numsec %f-formato): Blas_%n%f >>> t = RenombradoMasivo(fmt) >>> fecha = time.strftime('%d%b%y') >>> for i, nombrefich in enumerate(ficheros): ... base, ext = os.path.splitext(nombrefich) ... nombrenuevo = t.substitute(d=fecha, n=i, f=ext) ... print '%s --> %s' % (nombrefich, nombrenuevo) img_1074.jpg --> Blas_0.jpg img_1076.jpg --> Blas_1.jpg img_1077.jpg --> Blas_2.jpg
Otra aplicación de las plantillas es separar la lógica de programación de los detalles de los diferentes formatos de salida. Esto permite utilizar plantillas a medida ficheros XML, informes en texto plano e informes para la web en HTML.
El módulo struct proporciona
las funciones pack() y unpack() para trabajar con formatos de
registro binario de longitud variable. El siguiente ejemplo muestra cómo
recorrer la información de cabecera de un fichero ZIP (con códigos de empaquetado
"H"
y "L"
que representan números sin signo de dos y cuatro bytes,
respectivamente):
import struct datos = open('mifich.zip', 'rb').read() inicio = 0 for i in range(3): # muestra las tres primeras cabeceras inicio += 14 campos = struct.unpack('LLLHH', datos[inicio:inicio+16]) crc32, tam_comp, tam_descomp, tamnombrefich, tam_extra = campos inicio += 16 nombrefich = datos[inicio:inicio+tamnombrefich] inicio += tamnombrefich extra = datos[inicio:inicio+tam_extra] print nombrefich, hex(crc32), tam_comp, tam_descomp inicio += tam_extra + tam_comp # saltar hasta la siguiente cabecera
El multihilado es una técnica para independizar tareas que no son secuencialmente dependientes. Se pueden utilizar los hilos para mejorar la respuesta de las aplicaciones que aceptan entrada del usuario mientras otras tareas se ejecutan en segundo plano. Un caso parecido es la ejecución de E/S en paralelo con los cálculos de otro hilo.
El siguiente código muestra cómo el módulo de alto nivel threading puede ejecutar tareas en segundo plano mientras el programa principal prosigue su ejecución:
import threading, zipfile class AsincZip(threading.Thread): def __init__(self, infile, outfile): threading.Thread.__init__(self) self.infile = infile self.outfile = outfile def run(self): f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED) f.write(self.infile) f.close() print 'Finished background zip of: ', self.infile segundoplano = AsincZip('misdatos.txt', 'comprimido.zip') segundoplano.start() print 'El programa principal sigue ejecutándode en primer plano.' segundoplano.join() # Esperar a que termine la tarea en segundo plano print 'El programa principal ha esperado hasta que la tarea en 2º plano ha termindo.'
El mayor reto de las aplicaciones multihilo es coordinar los hilos que comparten datos u otros recursos. A este fin, el módulo de multihilo proporciona varias primitivas de sincronización que incluyen bloqueos, eventos, variables de condición y semáforos.
Aunque estas herramientas son potentes, los errores ligeros de diseño pueden causar problemas difíciles de reproducir. Por ello, el método aconsejado para coordinar tareas es concentrar todo el acceso a un recurso en un solo hilo y utilizar el módulo Queue para alimentar ese hilo con peticiones desde los otros hilos. Las aplicaciones que usan objetos Queue para comunicación y coordinación entre hilos son más fáciles de diseñar, más legibles y más fiables.
El módulo logging ofrece
un sistema potente y flexible de registro de actividad. En su forma
más simple, los mensajes de registro se envían a un fichero o a
sys.stderr
:
import logging logging.debug('Información de depuración') logging.info('Mensaje informativo') logging.warning('Aviso:falta el fichero de configuración %s ', 'server.conf') logging.error('Ha ocurrido un error') logging.critical('Error crítico; apagando')
Esto produce la siguiente salida:
WARNING:root:Aviso:falta el fichero de configuración server.conf ERROR:root:Ha ocurrido un error CRITICAL:root:Error crítico; apagando
De manera predeterminada, los mensajes informativos y de depuración se suprimen cuando la salida se dirige al flujo de error estándar. Otras opciones de salida incluyen encaminar los mensajes a correo electrónico, datagramas, zócalos (sockets) o a un servidor HTTP. Los filtros pueden seleccionar el encaminado basándose en la prioridad del mensaje: DEBUG, INFO, WARNING, ERROR y CRITICAL.
El sistema de registro se puede configurar directamente desde Python o se puede cargar a partir de un fichero de configuración para personalizar el registro sin alterar la aplicación.
Python gestiona la memoria automáticamente (cuenta de referencias en la mayoría de los objetos y recogida de basura para eliminar ciclos). La memoria se libera poco después de que se elimine la última referencia a ella.
Esta técnica funciona bien para la mayoría de las aplicaciones pero ocasionalmente hay necesidad de seguir objetos mientras los usen otros. Desafortunadamente, el hecho de seguirlos crea una referencia a ellos que los hace permanentes. El módulo weakref proporciona herramientas para seguir objetos sin crear una referencia a ellos. Cuando el objeto ya no se necesita, se retira automáticamente de una tabla weakref y se dispara una llamada de respuesta (callback) a los objetos weakref. Las aplicaciones típicas incluyen hacer una caché de objetos de creación costosa:
>>> import weakref, gc >>> class A: ... def __init__(self, value): ... self.value = value ... def __repr__(self): ... return str(self.value) ... >>> a = A(10) # crea una referencia >>> d = weakref.WeakValueDictionary() >>> d['primaria'] = a # no crea una referencia >>> d['primaria'] # obtiene el objeto si está vivo aún 10 >>> del a # elimina la única referencia >>> gc.collect() # recoge la basura ahora mismo 0 >>> d['primaria'] # la entrada se eliminó automáticamente Traceback (most recent call last): File "<pyshell#108>", line 1, in -toplevel- d['primaria'] # la entrada se eliminó automáticamente File "C:/PY24/lib/weakref.py", line 46, in __getitem__ o = self.data[key]() KeyError: 'primaria'
Muchas de las cuestiones relativas a estructuras de datos se pueden solucionar con el tipo de lista interno. Sin embargo, a veces hay necesidad de implementaciones alternativas con diversas opciones de rendimiento.
El módulo array proporciona un
objeto array() que es como una lista que sólo almacena datos homogéneos
pero de una forma más compacta. El siguiente ejemplo muestra cómo guardar
un vector de números almacenados como números de dos bytes sin signo (código de tipo
"H"
) en lugar de los 16 bytes por entrada de las listas normales de
objetos enteros de Python:
>>> from array import array >>> a = array('H', [4000, 10, 700, 22222]) >>> sum(a) 26932 >>> a[1:3] array('H', [10, 700])
El módulo collections proporciona un objeto deque() que es como una lista con operaciones append y pop por el lado izquierdo más rápidas pero búsquedas más lentas enmedio. Estos objetos son adecuados para implementar colas y búsquedas de árbol a lo ancho (breadth first).
>>> from collections import deque >>> d = deque(["tarea1", "tarea2", "tarea3"]) >>> d.append("tarea4") >>> print "Handling", d.popleft() Gestionando tarea1 unsearched = deque([starting_node]) def breadth_first_search(unsearched): node = unsearched.popleft() for m in gen_moves(node): if is_goal(m): return m unsearched.append(m)
Además de implementaciones alternativas de las listas, la biblioteca también ofrede herramientas como el módulo bisect, que contiene funciones para manipular listas ordenadas:
>>> import bisect >>> puntuaciones = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')] >>> bisect.insort(puntuaciones, (300, 'ruby')) >>> puntuaciones [(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]
El módulo heapq proporciona funciones para implementar montículos (heaps) basados en listas normales. El elemento de menos valor siempre se mantiene en la posición cero. Esto es útil para las aplicaciones que acceden repetidamente al menor elemento pero no necesitan la ordenación de la lista entera:
>>> from heapq import heapify, heappop, heappush >>> datos = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0] >>> heapify(datos) # reordena la lista en orden de montículo >>> heappush(datos, -5) # añade un nuevo elemento >>> [heappop(datos) for i in range(3)] # obtiene los tres elementos más bajos [-5, 0, 1]
El módulo decimal proporciona un tipo de dato Decimal para la aritmética de coma flotante decimal. Comparada con la implementación interna de float de la aritmética de coma flotante binaria, la nueva clase es especialmente útil para aplicaciones financieras y otros usos que exigen una representación decimal exacta, control de la precisión, control sobre el redondeo para amoldarse a requisitos legales, seguimiento del número de cifras significativas o aplicaciones en las que el usuario espera que los resultados coincidan con los cálculos hechos a mano.
Por ejemplo, calcular un impuesto del 5% sobre una tarifa telefónica de 70 céntimos da diferentes resultados en coma flotante decimal y coma flotante binaria. La diferencia resulta significativa si se redondean los resultados al céntimo más cercano:
>>> from decimal import * >>> Decimal('0.70') * Decimal('1.05') Decimal("0.7350") >>> .70 * 1.05 0.73499999999999999
El resultado Decimal mantiene un cero por detrás, obteniendo automáticamente cuatro cifras significativas a partir de los operandos de dos cifras. Decimal reproduce las cuentas como si se hicieran a mano y evita problemas que pudieran surgir cuando la coma flotante no puede representar exactamente cantidades decimales.
La representación exacta permite que la clase Decimal realice cálculos de restos y pruebas de igualdad que son impropias en la coma flotante binaria:
>>> Decimal('1.00') % Decimal('.10') Decimal("0.00") >>> 1.00 % 0.10 0.09999999999999995 >>> sum([Decimal('0.1')]*10) == Decimal('1.0') True >>> sum([0.1]*10) == 1.0 False
El módulo decimal proporciona tanta precisión como sea necesaria:
>>> getcontext().prec = 36 >>> Decimal(1) / Decimal(7) Decimal("0.142857142857142857142857142857142857")
Consultar en Acerca de este documento... información para sugerir cambios.