¡Bienvenid@s a la quinta entrega de Aprender Python Programando!
En el capítulo de hoy, nos vamos a poner más técnicos como verdaderos programadores. Mejoraremos no solo la funcionalidad externa de nuestro programa, sino que además, emplearemos técnicas de refactoring (refactorización) que nos ayuden a mejorar el rendimiento interno del programa.

¿Qué haremos hoy?

  1. Agregaremos nuevas funcionalidades al programa y mejoraremos otras existentes:
    • Haremos que tanto la fecha actual como la de caducidad de nuestro presupuesto, se calculen de forma automática.
    • Al finalizar la carga de un presupuesto, tendremos la opción de abrirlo directamente en el navegador para visualizarlo.
    • Mejoraremos el funcionamiento de la elección del plan a presupuestar, validando que el dato ingresado sea correcto. De no serlo, se nos volverá a pedir que ingresemos una opción.
  2. Haremos un refactoring del código, a fin de:
    • Hacer nuestro código más legible.
    • Lograr que el programa tenga un mejor rendimiento.
    • Facilitar el mantenimiento y evolución del sistema.

Ingredientes

Nuestra “receta” del capítulo hoy, necesitará nuevos archivos e incorporará modificaciones. Lo primero que haremos es:

Descargar archivos modificados:
presupuesto.py

Descargar los archivos nuevos:
Si lo deseas, puedes modificar el nombre de la carpeta “capitulo3” por el nombre que desees otorgarle al programa, o dejarlo sin cambios y guardar los siguientes archivos, dentro de ella.
constantes.py
helpers.py

Helpers: una forma de agrupar funciones genéricas

Hemos creado un nuevo archivo: helpers.py. En este archivo, hemos agrupado funciones de uso común, que si bien hoy, son utilizadas por nuestro módulo Presupuesto, al ser funciones genéricas (no son “acciones” propias del objeto presupuesto, sino que aplicarían a cualquier otro objeto), el día de mañana, podríamos utilizarlas en otros módulos de nuestro sistema.

Helper

En la programación, un helper es una forma de agrupar funciones de uso común, destinadas a servir de ayuda a otros procesos.

Un Helper se compone de funciones genéricas que se encargan de realizar acciones complementarias, aplicables a cualquier elemento de un sistema.

Es importante mencionar, que un Helper, pueden ser, tanto funciones “sueltas” como la abstracción de un “objeto helper” (es decir, una clase).

class Helper:
    def helper_1(self):
        # algoritmo

    def helper_2(self):
        # algoritmo

Es tan (o más) válido que:

def helper_1(self):
    # algoritmo

def helper_2(self):
    # algoritmo

Por lo tanto, nuestro archivo helpers.py, es un módulo que contiene “ayudantes”, comunes a cualquier otro módulo de nuestro sistema.

Docstrings y la función help() en Python

¿Quieres saber de qué trata el archivo helpers.py?

  1. Abre una terminal
  2. Navega hasta el directorio donde tienes almacenados los módulos del sistema
  3. Ejecuta el intérprete interactivo de python (escribe: python)
  4. Una vez en el intérprete, escribe:
    import helpers
    help(helpers)

El "Easter Egg" de la Guía Python

¿Haz visto el resultado? ¡Sorprendente! ¿no es cierto?

Los docstrings, no son algo nuevo. Ya los habíamos visto al comienzo de la guía. Los Docstrings no son más que el comentario de nuestro código. Y si haz probado hacer un help(helpers), habrás podido notar la importancia de comentar nuestro código.

La función help(), al igual int(), raw_input() y tantas otras que hemos visto, es una función nativa de Python, es decir una función interna o función built-in. Si lo deseas, es muy recomendable ver la lista completa de las funciones built-in de Python (en inglés).

help() ha sido diseñada para ejecutarse en modo interactivo. Cuando le es pasado como parámetro, el nombre de un módulo, objeto, clase, método, función, etc., help() devolverá la documentación que hayamos especificado, así como toda información adicional sobre el elemento pasado como parámetro.

Consideraciones para escribir docstrings

Cuando documentes un método o función en Python, deberás escribir los docstrings teniendo en cuenta lo siguiente:

La documentación se escribe entre triple comillas dobles en la misma línea, justo después de la definición def
Correcto:

def mi_funcion():
    """Aquí la documentación"""

Incorrecto:

def mi_funcion():
    """ Aquí la documentación """

 

def mi_funcion():
    """
Aquí la documentación
"""

Definitivamente un homicidio que amerita prisión perpetua:

def mi_funcion():
    """

    Aquí la documentación

"""

Una buena documentación, debe incluir la acción que realiza, lo que retorna y los parámetros que acepta.
En estos casos, se utilizan docstrings multilínea, siguiendo el próximo esquema:

"""Hace X retorna Y

Argumentos:
arg1 -- descripción de arg1
arg2 -- descripción de arg2

"""

Las especificaciones para los docstrings se encuentran oficialmente documentadas por Python en las PEP 257.

Trabajando con fechas en Python

Una de las modificaciones que incorporamos hoy, consiste en gestionar las fechas de confección y caducidad del presupuesto, en forma automática. Para ello, incorporamos dos helpers:

def get_fecha_actual():
    hoy = datetime.datetime.now()
    fecha_actual = hoy.strftime("%d/%m/%Y")
    return fecha_actual

y

def sumar_dias(dias=0):
    fecha = datetime.datetime.now() + datetime.timedelta(days=dias)
    nueva_fecha = fecha.strftime("%d/%m/%Y")
    return nueva_fecha

Las fechas, podemos generarlas automáticamente, gracias al módulo datetime de Python:

import datetime

El método now() de datetime, retorna la fecha y hora actual:

hoy = datetime.datetime.now()

La función strftime() convierte la fecha a cadena de texto, con el formato pasado como parámetro:

hoy.strftime("%d/%m/%Y")

Con la función timedelta() podemos calcular fechas, ya sea restando, como sumando, dividiendo o multiplicando N cantidad de días:

fecha = datetime.datetime.now() + datetime.timedelta(days=dias)

El módulo datetime

Este módulo, nativo de Python, nos permite realizar diferentes manejos con fechas.

Para utilizar el módulo debe importarse:

import datetime

Obtener la fecha y hora actual:

datetime.datetime.now()

Sumar, restar, multiplicar o dividir N días a una fecha:

fecha operador_aritmético datetime.datetime.timedelta(days=N)

Donde N debe ser un número entero (representa la cantidad de días a sumar, restar, multiplicar o dividir). Sumar (+), Restar (-), dividir (//), multiplicar (*).

Dar formato a una fecha:

fecha.strftime(string_formato)

Donde string_formato será la cadena de texto que defina el formato deseado, con las directrices indicadas.

Para dar diversos formatos de fechas, las directrices disponibles, son las siguientes:

Chuleta para formato de fechas

Chuleta para formato de fechas

Refactoring

Hemos realizado algunos cambios a nuestro código, pero que sin embargo, no se reflejan al momento de utilizar el programa.

Refactoring

Refactoring (o refactorización / refactorizar) es una técnica de programación, que consiste en efectuar cambios al código fuente, sin alterar el funcionamiento externo del programa.

¿Qué cambios hemos hecho?

Eliminar de la clase todo aquello que no esté relacionado de forma directa con la lógica propia del objeto. Para ello, recurrimos a:

Refactoring #1: Eliminar la directiva print del módulo presupuesto, moviéndola a un helper.
Refactoring #2: Todos los textos a mostrar en los raw_input(), así como otros mensajes del sistema, fueron movidos a variables de acceso global, definidas en el archivo constantes.py. Es muy importante hacer la salvedad, de que Python, no posee el concepto de constantes como otros lenguajes.
Hablar de constantes en Python, es una cuestión lingüística pero no técnica. A los fines del lenguaje, decir “constante” en Python, simplemente hace referencia a variables cuyo valor, se encuentra predefinido sin necesidad de requerir modificar dinámicamente dichos datos.

Limpiamos el módulo, moviendo un método genérico que podría utilizarse en cualquier otro módulo (no necesariamente Presupuesto). Para ello, recurrimos a:

Refactoring #3: mover el método leer_plantilla() del módulo Presupuesto, a un helper llamado leer_archivo() logrando que éste, pueda ser reutilizado desde cualquier otro módulo.

Tratamiento de excepciones: validando los datos ingresados

Cuando se nos pedía ingresar el código de plan, se nos daban tres opciones: 0, 1 y 2. Pero ¿qué sucedía si por error, en vez de 0, 1 ó 2 ingresábamos otro dato?

Si ingresábamos un dato no numérico, nos generaba un error, al intentar convertirlo de literal a entero con int(). Pero, también podía suceder que se ingresara un número mayor que 2. En ese caso, la conversión a entero no fallaría, pero al momento de intentar acceder al plan elegido mediante self.planes[plan_elegido] nos daría un error, ya que el índice, estaría fuera del rango de la lista.

A fin de evitar estos errores, incorporamos un algoritmo de validación, mediante el tratamiento de excepciones.

Excepciones

Una excepción es un error inesperado que se produce durante la ejecución de un programa.

Las excepciones en Python, cuentan con dos instancias obligatorias: try y except, donde el código contenido en try intentará ejecutarse, y se falla, el error será capturado por except, lanzando otra acción.

try:
    # intentar esto
except:
    # Si lo anterior falla, capturar el error
    # y hacer esto otro

Opcionalmente, en Python, pueden agregarse otras dos instancias, pero opcionales: else y finally. Mientras que else, se encarga de ejecutar el código indicado solo si no ha fallado nada, finally, se llamará siempre (falle o no), siendo su finalidad, la de ejecutar “acciones de limpieza”.

def probar_excepciones():
    dato = raw_input("Ingresar numero para pasar, letra para fallar: ")
    try:
        int(dato)
    except:
        print "ejecutando execpt, try ha fallado"
    else:
        print "ejecutando else, try se ha logrado"
    finally:
        print "finally se ejecuta siempre"

Hecha esta introducción, vayamos a nuestro módulo Presupuesto (líneas 53 a 63):

try:
    elegir_plan = int(elegir_plan)
    self.plan = self.planes[elegir_plan]

Intenta convertir a entero el plan elegido en el raw_input() y después, intentará obtener self.planes[elegir_plan] (elegir_plan actúa como número de índice). Pero esta última, solo se llevará a cabo, si la primera, no genera una excepción.

except (ValueError, IndexError):
    mostrar_texto(DATO_INCORRECTO)
    self.seleccionar_plan()

Si se genera una excepción, se muestra un mensaje de error y se realiza una llamada recursiva.
Pero ¿Qué hay allí entre paréntesis?
ValueError e IndexError son dos tipos de excepciones. ValueError se produce cuando el valor tratado, no corresponde al tipo de dato esperado (por ejemplo, se ingresa un caracter no numérico). E IndexError es el error lanzado cuando se intenta acceder, por ejemplo, a un ítem inexistente de una lista o tupla, es decir, cuando como número de índice se pasa un valor fuera del rango (es decir, mayor a la cantidad de ítems en la tupla o lista).

Si se desea capturar cualquier excepción, se puede utilizar:

except:

Si se desea capturar únicamente un tipo de excepción, se puede utilizar:

except TipoDeExcepcion:

Si se desean capturar varias excepciones en un mismo paso, se utiliza:

except (TipoExcepcion1, TipoExcepcion2, TipoExcepcion5):

Es posible también, capturar distintas excepciones en varios pa<strongsos:

except TipoExcepcion1:
    #...
except (TipoExcepcion2, TipoExcepcion3):
    #...
except TipoExcepcion4:
    #...

Una alternativa muy útil, es recoger la descripción del error para poder mostrarla:

except TipoExcepcion, error_capturado:
    print error_capturado
except (TipoExcepcion1, TipoExcepcion2), error_capturado:
    print error_capturado

Finalmente, ejecutaremos el resto del script, si ninguna excepción ha sido lanzada:

else:
    datos_servicio = self.lista_precios[self.planes[elegir_plan]]
    self.servicio = datos_servicio[0]
    importe = datos_servicio[1]
    self.importe = float(importe)
Chuleta de tipos de excepciones

Chuleta de tipos de excepciones

Webbrowser: abriendo el navegador desde Python

    # Mostrar presupuesto en navegador
    def mostrar_presupuesto(self, archivo):
        respuesta = raw_input(MOSTRAR_PRESUPUESTO)
        if respuesta.lower() == 's':
            webbrowser.open(BASE_DIR + "/" + archivo)

(líneas 103 a 107)

Con el módulo webbrowser nativo de Python, es posible abrir el navegador y cargar en él, cualquier archivo HTML o dirección Web. Solo basta importar el módulo webbrowser import webbrowser y cargar la URL mediante webbrowser.open(URL)

Un nuevo desafío

En este capítulo, hemos aprendido dos conceptos fundamentales, indispensables en la vida de cualquier programador experto: la técnica de refactoring y el manejo de excepciones. Y ninguna de las dos, es poca cosa, ni mucho menos, pueden etiquetarse como “básicas”. Los programadores que recién se inician (e incluso, puedo asegurarles que muchos programadores con años de experiencia), no suelen refactorizar el código fuente de sus programas, y son muy pocas las veces que manejan excepciones dentro del código.

La tarea que nos toca hoy, es animarnos a programar como verdaderos profesionales. ¿Cuál es el desafío entonces?

Hacer un refactoring de TODOS los archivos de nuestro sistema
¿Cómo? Muy simple. Python, tiene una serie de recomendaciones para la escritura de código, llamada “Style Guide for Python Code”, definida en la PEP 8 – de la cual hemos hablado sutilmente en capítulos anteriores -. Los verdaderos programadores Python, debemos respetar esta guía de estilos al pie de la letra, ya que hacerlo, nos garantiza que nuestro código pueda ser fácilmente mantenible, legible, evolutivo y por sobre todo, es una forma de respetarnos entre colegas. Escribir código desprolijo o con “estilo propio”, dificulta la lectura. Ergo, hace que entender el código escrito por otro programador, nos lleve más trabajo.

Entonces:

  1. Lee las PEP 8 (en inglés) o las PEP 7 (disponibles en español)
  2. Revisa cuidadosamente los archivos constantes.py, helpers.py y sobre todo, presupuesto.py (tiene violaciones a las PEP 8 de forma ex-profesa)
  3. Haz todas las modificaciones que sean necesarias, para estandarizar el código fuente según las PEP

 
Si no entiendes algo de las PEP ¡pregúntalo! No te quedes con la duda. Si te aburre, o no te apasiona hacerlo o lo consideras sin sentido, ten en cuenta que hacerlo será fundamental para tu crecimiento profesional. Te lo garantizo!.

Es muy importante que aproveches este reto para trabajar en equipo. Puedes utilizar los comentarios para compartir con otros programadores, las líneas de código que hayas refactorizado, así como también, intercambiar opiniones sobre cómo sería mejor refactorizar tal o cual otra línea. ¡Aprovecha esto para entrenarte! La capacidad para trabajar en equipo, no solo la da la buena predisposición, sino también la práctica y el intercambio con otros programadores. Y ten en cuenta, que a la hora de buscar trabajo, sumará puntos a tus fortalezas.