Página inmutable History Adjuntos

Cursos/Principiantes/Parte8

Nombre de la página: Cursos / Principiantes / Parte8



Archivos, Iteradores, Biblioteca estándar y más

En esta sección cubriremos temas básicos para entender conceptos más complejos a futuro, patrones de desarrollo, estilos de programación, interfaces comunes de los sistemas operativos, cubriremos a detalle algunas construcciones idiomaticas de python los cuales serán de gran ayuda para la presentación y mantenimiento de tu código.

Finalizamos con módulos comunes de la librería estándar, formas de obtener más módulos y referencias para seguir aprendiendo.

Archivos

La importancia de los archivos

Puede parecer que a veces se hace demasiado énfasis en un tema relativamente sencillo, el de leer y escribir archivos, la experiencia les mostrará que muchas de las cosas con las que vayan a lidiar en el futuro cercano iniciara y/o finalizara con archivos, es el método más común para tener una interfaz con el sistema operativo (Linux, Mac, Windows, etc) esto debido en gran medida a que es la única interfaz que se tiene acceso al almacenamiento permanente de tu computadora (e.g. disco duro), además de que se usa un modelo muy similar para enviar y recibir archivos con Sockets, estos son una interfaz para enviar información por varios medios y protocolos como TCP/IP.

Un archivo puede ser desde una imagen, una canción, un documento de word, un archivo de texto plano, el contenido del archivo se vuelve irrelevante hasta el momento que se desea interpretar la información contenida, en principio es solo una llave en el mismo sentido que un diccionario de python que contiene como valor un conjunto de bytes.

Muchas veces se malentiende que las bases de datos usan otros medios para almacenar los datos, pero siempre a fin de cuentas se reduce a archivos, la capa de la base datos ayuda a mantener los datos ordenados y en memoria lo mas reciente (LRU cache) además de muchas otras cosas sofisticadas pero tengan por seguro que se usan muchos archivos de por medio.

Es por todo eso que es importante tener claro el concepto de archivo, en python existe el concepto de "file object" o "file like object"(archivo que parece objeto) como abstracción que ayuda a integrar de forma natural los archivos que sean necesarios manipular.

Manejando archivos

La forma más común de usar los archivos del mismo sistema operativo es a través de la función open, anteriormente se usaba file, pero hace ya varías versiones que se considera obsoleto y en python3 dejó de existir.

Leer un archivo (tiene que existir):

   1 archivo = open("sample_file.txt", "r")
   2 print(archivo.read())
   3 archivo.close()

Para escribir y truncar un archivo (sobre escribe todo el archivo si ya existe) o crea y permite escribir:

   1 archivo = open("sample_file.txt", "w")
   2 archivo.write("Hola mundo de los archivos!\n")
   3 archivo.close()

Leer y escribir un archivo sin truncar su contenido (tiene que existir el archivo):

   1 archivo = open("sample_file.txt", "r+")
   2 archivo.write("Hola mundo de los archivos!\n")
   3 print(archivo.read())
   4 archivo.close()

Lee y escribe un archivo truncando su contenido (crea el archivo si no existe):

   1 archivo = open("sample_file.txt", "w+")
   2 archivo.write("Hola mundo de los archivos!\n")
   3 archivo.seek(0) # movemos el archivo el inicio de su contenido
   4 print(archivo.read())
   5 archivo.close()

Agrega contenido a un archivo y posiciona la lectura al final del archivo (tiene que existir el archivo):

   1 archivo = open("sample_file.txt", "a")
   2 archivo.write("Nuevo contenido al final del archivo!\n")
   3 archivo.close()

Agrega contenido a un archivo y posiciona la lectura al final del archivo ademas de que permite leer su contenido (tiene que existir el archivo):

   1 archivo = open("sample_file.txt", "a+")
   2 archivo.write("Nuevo contenido al final del archivo!\n")
   3 archivo.seek(0) # movemos el archivo el inicio de su contenido
   4 print(archivo.read())
   5 archivo.close()

Interfaz de "file objects"

Se dice que un objeto "file like" (parecido a un archivo), cuando implementa los siguientes métodos:

los argumentos que vienen en entre corchetes "[]" son opcionales, es una convención muy usada en toda la documentación

close()
Cierra el archivo
flush()
Garantiza que se liberan los buffers, e.g. que realmente se escriba en el disco.
fileno()
No es necesarios si el archivo no tiene in file descriptor
isatty()
No es necesario si el archivo no es un archivo real.
next()
Método que se llama en cada ciclo en la iteración dentro de for. (detalles más adelante)
read([size])

Regresa hasta size bytes a la vez, si el argumento size es negativo o no esta definido entonces lee todo el archivo hasta termina.

readline([size])

Regresa una linea del archivo o hasta size bytes

readlines([sizehint])
seek(offset, [whence])
truncate([size])
write(str)
Escribe en el archivo
writelines(sequence)
Escribe linea por linea iterando la secuencia que recibe como argumento

Para más información sobre los detalles de cada método consulta la documentación oficial

Para más información sobre el manejo de archivo (en español) pueden consultar la página de la Universidad de Antioquia

Iteradores

Los iteradores son el conjuto de objetos que son "iterables" (valga la redundancia), usualmente en forma de colecciones (listas, tuplas, diccionarios, sets) o también en objetos que parecen archivos iterando linea por linea.

¿Para que sirven?

Lo iteradores no aportan algo completamente nuevo y en principio todo for que itera se puede substituir por un while, pero permiten tener una capa de abstracción sobre como interactuan los objetos, permite tener un for muy idiomático que abstrae de donde se obtienen los datos que se iteran, ¿de la red?, ¿del disco?, ¿de un calculo en tiempo real?, ¿de una cola infinita de elementos?, existen muchas posibilidades.

Interfaz de iteración

La interfaz de iteración es muy sencilla, se basa en tres puntos:

  1. La excepción StopIteration

  2. El método especial next o en python3 __next__.

  3. El método especial __iter__

El flujo es el siguiente:

  1. Si llama al método iter y regresa un objeto que debe de tener las tres características antes mencionadas, con la particularidad que el metodo __iter__ regresa al mismo objeto.

  2. Del objeto que se regreso en el paso 2, llamar a su método next hasta que ocurra la excepción, StopIteration, en ese punto termina la iteración.

Ejemplo de una clase que implementa la interfaz de iteración:

   1 class Server(object):
   2 
   3     def __init__(self, *clients):
   4         self.position = 0
   5         self.clients = []
   6         for c in clients:
   7            self.add_client(c)
   8 
   9     def __iter__(self):
  10         return self
  11 
  12     def __next__(self):
  13         try:
  14             cclient = self.clients[self.position]
  15         except IndexError:
  16             raise StopIteration()
  17         else:
  18             self.position += 1
  19             return cclient
  20     next = __next__ # for python2 compatibility
  21 
  22     def add_client(self, client):
  23         self.clients.append("Clients served %s"  % client)
  24 
  25 for cliente in Servidor(1, 2, 3, 4):
  26     print cliente

O mejor aún con el objeto que itera separado en otra clase

   1 class ServerIterator(object):
   2 
   3     def __init__(self, clients):
   4         self.clients = clients
   5 
   6     def __iter__(self):
   7         return self
   8 
   9     def __next__(self):
  10         try:
  11             return "Clients served %s" % self.clients.pop(0)
  12         except IndexError:
  13             raise StopIteration()
  14     next = __next__ # for python2 compatibility
  15 
  16 
  17 class Server(object):
  18 
  19     def __init__(self, *clients):
  20         self.position = 0
  21         self.clients = []
  22         for c in clients:
  23            self.add_client(c)
  24 
  25     def __iter__(self):
  26         iterator = ServerIterator(self.clients)
  27         self.clients = []
  28         return iterator
  29 
  30     def add_client(self, client):
  31         self.clients.append(client)
  32 
  33 for cliente in Server(1, 2, 3, 4):
  34     print(cliente)

Generadores

Los generadores son un tipo particular de iterador donde en vez de estar llamando muchas veces al método next, usamos la palabra reservada "yield" para regresar el valor de la iteración hasta que se llegue al final de la ejecución de la función o que se se levante la excepción StopIteration.

Implementando el mismo ejemplo de iteradores ahora con un generador::

   1 def server_generator(clients):
   2     print("Generator initialization")
   3     try:
   4         while True:
   5             yield  "Clients served %s"  %  clients.pop(0)
   6     except IndexError:
   7         raise StopIteration()
   8 
   9 
  10 class Server(object):
  11 
  12     def __init__(self, *clients):
  13         self.position = 0
  14         self.clients = []
  15         for c in clients:
  16            self.add_client(c)
  17 
  18     def __iter__(self):
  19         generator = server_generator(self.clients)
  20         self.clients = []
  21         return generator
  22 
  23     def add_client(self, client):
  24         self.clients.append(client)
  25 
  26 for cliente in Server(1, 2, 3, 4):
  27     print(cliente)

Los generadores tienen ademas otros atributos para enviar información para modificar su comportamiento, es una forma de comunicación bidireccional en la ejecución pero eso queda para un tema mas a profundidad de lo que se puede llegar a hacer con eso a futuro.

Como referencia para el que quiere saber mas al respecto la documentación oficial es un buen punto de partida (si no tienes problemas leyendo inglés).

Ciclos y contexto, for, while, with

El ciclo "for"

En el curso ya se han visto un conjunto de usos de la construcción for que siempre va de la mano de los iteradores, como forma de hacer un repaso de lo que se puede llegar ademas de el de desempaquetar un solo elemento a la vez:

Ejemplo basico de for:

   1 for i in range(1000):
   2     print(i)

   1 for i, j in zip(range(0, 1000, 2), range(1, 1000, 2)):
   2     print(i, j)

   1 alumnos = [(80, "Jan"),
   2            (23, "Pedro"),
   3            (24, "Alejandra")]
   4 for pos, (num_lista, nombre) in enumerate(alumnos, 1):
   5     print("Posicion: %s\n\t%s: %s" % (pos, num_lista, nombre))

El ciclo for puede ser usado directamente con los archivos:

datos = open("data.txt")
for linea in datos:
    print(linea)
datos.close()

Este es un caso particular del else en el for de python, solamente se llega ese esa sección cuando la iteración termina sin ser interrumpida por un break:

   1 count = 0
   2 word = "python monterrey"
   3 for i, letra in enumerate(word):
   4     if i > 2:
   5         break
   6     else:
   7         print("%s: %s" % (i, letra))
   8 else:                          
   9     print("Todo sin problemas")

El ciclo "while"

El ciclo while es una de los ciclos más viejos de toda la historia de la programación de igual manera que el for ya se a utilizado en repetidas ocasiones durante el curso, como forma de repaso estos son algunos ejemplos de while:

In for improvisado:

   1 # un while que funciona como for
   2 i = iter(range(1000))
   3 try:
   4     while True:
   5         print(i.next()) # o en python 3, next(i)                      
   6 except StopIteration:
   7     pass

Usando la sección else de while muy similar al else del for:

   1 queue = [1, 2, 3, 4, 5, 6, 6]
   2 while queue:
   3     elem = queue.pop(0)
   4     print(elem)
   5 else:
   6     print("Todo normal, nada llamo break")

   1 queue = [1, 2, 3, 4, 5, 6, 6]
   2 while queue:
   3     elem = queue.pop(0)
   4     if len(queue) < 2:
   5         break # Que no se termine!
   6     print(elem)
   7 else:
   8     print("Todo normal, nada llamo break")

El administrador de contexto "with"

El administrador de contexto with es una de las cosas que te permite obtener código idiomático y robusto en general es sencillo es un bloque que promete hacer una llamada a dos metodos especiales al iniciar y finalizar de ejecutarse el bloque que contiene la construcción:

Donde de las formas más usadas de usarlo es con los archivos:

   1 with open("some_file.txt") as lefile:
   2    for line in lefile:
   3         print(line)

Nos permite usarlos sin preocuparnos de las excepciones que puedan ocurrir mientras intentamos usar el archivo, preocupaciones como dejar muchos archivos abiertos, lo cual puede suceder si tu programa es uno que dura corriendo mucho tiempo, los sistemas operativos tienen limites de "file descriptors" para cada proceso y fácilmente se puede llegar a esos limites.

Otro uso muy comun es cuando se trabaja con recursos compartidos por diferentes hilos de ejecución, y usamos locks para limitar el acceso simultaneo:

   1 from threading import Lock
   2 lock = Lock() # lock compartido por todos los thread
   3 with lock:
   4     print("Haciendo cosas que podrian afectar a otros hilos de ejecucion")

Como verán no siempre se tiene que usar el:

   1 with ALGO as ALGO_MAS

with puede o no regresar un objeto a ser usado dentro del contexto.

Creando administradores de contexto

Ahora vamos a implementar un manejador de contexto es bastante sencillo:

  1. Definimos un método con nombre enter

  2. Definimos un método con nombre exit

   1 class Contexto(object):
   2      def __init__(self, datos_protegidos):
   3           self.dp = datos_protegidos
   4     
   5      def __enter__(self): 
   6           print("Preparando el contexto")
   7           def guardian():
   8              return self.dp
   9           return guardian
  10                 
  11      def __exit__(self, exp_type, exp_value, traceback):
  12           print("La ejecucion del contexto a terminado")
  13           if exp_type is None:
  14               print("Y sin ningun problema") 
  15 
  16 with Contexto("Python") as datos:
  17     print("Manipulando los datos %s" % datos())

Como forma alternativa podemos usar el modulo de la libreria estandar contextlib

   1 from contextlib import contextmanager
   2 
   3 @contextmanager
   4 def Contexto(datos_protegidos):
   5     print("Preparando el contexto")
   6     def guardian():
   7        return datos_protegidos
   8     try:
   9         yield guardian
  10     finally:
  11         print("La ejecucion del contexto a terminado")
  12 
  13 with Contexto("Python") as datos:
  14     print("Manipulando los datos %s" % datos())

Decoradores

¿Que son?

Son funciones que toman como argumento a una función y regresan a otra función, se evaluan al momento que se evalua el script, no al momento que se ejectua la función.

¿Para que sirven?

Son un bloque básico para la programación declarativa en la que el flujo de ejecución no es lineal mas la forma en como se expresa es mucho mas rica, favoreciendo a un estilo declarativo y no imperativo.

Algunos ejemplos de sus usos

   1 def capitalize(func)
   2    def wrap():
   3        func().capitalize()
   4    return wrap
   5 
   6 @capitalize
   7 def name():
   8    return "joel rivera"

   1 class Log(object):
   2     def __init__(self, filename):
   3         self.fname = filename
   4 
   5     def log(self, message):
   6         print("LOG: %s" % message)
   7 
   8 def logexec(logfilename):
   9     logfile = Log(logfilename)
  10     def wrap(func):
  11         def inner_wrapp(*args, **kwargs):
  12             try:
  13                 return func(*args, **kwargs)
  14             except Exception as e:
  15                  logfile.log("Exception executing %s %s" % (e, func))
  16         return inner_wrapp
  17     return wrap
  18 
  19 @logexec("logfile")
  20 def hard_computing(a, b):
  21     raise Exception("OMG!")
  22     return a + b
  23 
  24 hard_computing(1, 2)

Biblioteca estándar

Módulos para tareas comunes

El indice de paquetes de Python

No hay mejor programa que el que no necesita se programado, como programador siempre de debe de tratar de maximizar la eficiencia es por eso que muchas veces es mucho mejor no re-inventar la rueda, para esa misma razón la comunidad de python cuenta con el Python Package Index el cual se puede navegar desde su sitio oficial https://pypi.python.org/pypi, dentro de la biblioteca estandar de python cuenta con modulos que te ayudan a que publiques tu codigo en el indice y pueda ser usado por toda la comunidad de python, el como hacer eso queda de tema para el futuro, ahora nos vamos a enfocar a como usarlo.

Existen dos programas que sirven para acceder al indice, pip e easy_install.

PIP

Este es el programa más recomendado para usar los modulos que se encuentran en el indice que pasra fines practicos se encuentran casi todos los que pienses usar, los comandos mas comunes para usarlo es:

Para instalar:

pip install MODULO

por ejemplo

pip install lxml

o

pip install CherryPy

ademas de poder hacer busquedas al indice directamente desde la linea de comandos:

pip search Cherr

Regresa todos los paquetes parecidos a la palabra clave que se le indique.

Ademas de las opciones de instalar y buscar con pip podemos desintalar paquetes de una formas bastante intuitiva:

pip uninstall NOMBRE_DE_PAQUETE

por ejemplo para quitar CherryPy

pip uninstall CherryPy

luego de unos segundos nos pregunta que si estamos seguros y realiza la operación

Para mas información sobre pip pueden consultar su página oficial

easy_install

easy_install esta siendo substituido por pip y se no se recomienda su uso, pero solo como datos cultural la forma de instalar es igual que en pip, solo que no podemos des instalar, para más información consulta la pagina oficial de easy_install.py

El camino apenas comienza

Hasta aquí hasta el momento, considerate todo un aprendiz de python con conocimientos muchos mas amplios que la media, lo que aún separa es la experiencia que solo llega con la practica, asi que... ¿que estas esperando?, ¡a programar!.