La librería estándar de Python#

El lenguaje Python proporciona una extensísima librería de módulos que nos sirven para construir nuestro software sin tener que reimplementar una y otra vez algunas funcionalidades que suelen ser comunes a todo tipo de programas.

Entre esas librerías podemos mencionar algunas de las más comunes:

  • os incluye muchas funciones y variables útiles para interactuar con aspectos específicos del sistema operativo y el entorno de ejecución: variables de entorno, rutas de ficheros, descriptores de ficheros de entrada, salida y error estándar…

  • sys nos permite acceder a la línea de comandos de nuestro programa (sys.argv) y también a la función sys.exit que nos permite terminar la ejecución e indicar el código de salida.

  • re incluye todas las funcionalidades relativas a expresiones regulares.

  • socket contiene toda la librería que da acceso de bajo nivel a sockets de comunicación.

  • math incluye una buena colección de funciones matemáticas y valores para constantes.

Esto es sólo una pequeña muestra de toda la librería estándar de Python.

En este apartado vamos a ver un par de librerías que deberéis utilizar en vuestra práctica y que os facilitarán la vida: argparse y logging

argparse#

Casi todos los programas que se ejecutan desde la línea de comandos suelen necesitar leer una serie de parámetros y argumentos de la misma para decidir qué acciones deben realizarse. A través de la librería sys podemos acceder fácilmente a la línea que ha sido ejecutada, usando la variable sys.argv.

Aunque en algunas ocasiones es posible que con lo anterior sea suficiente, en otras muchas no lo será. Además, también es recomendable proporcionar una ayuda cuando se solicite o se comenta algún error en la entrada, e implementar todo eso a mano suele ser bastante tedioso.

Para ello, Python proporciona la librería argparse. Básicamente esta librería funciona definiendo un objeto ArgumentParser al cuál le vamos añadiendo los diferentes argumentos que queremos que soporte nuestro programa. Cada uno de estos argumentos están ligados una opción en la línea de comandos, unos argumentos y puede definir un texto de ayuda que permita generar la salida del comando de ayuda de forma automática.

#!/usr/bin/env python3
import argparse

def parse_commandline():
    parser = argparse.ArgumentParser(description="Commandline calculator")
    parser.add_argument('value1', type=float, help='First number')
    parser.add_argument('value2', type=float, help='Second number')
    return parser.parse_args()

def main():
    user_options = parse_commandline()
    print(user_options.value1 + user_options.value2)

if __name__ == '__main__':
    main()

En el ejemplo anterior se puede observar que nuestro entrypoint va a ejecutar primero la función parse_commandline. En dicha función se crea el objeto argparse.ArgumentParser y se añaden dos argumentos a la línea de órdenes. El valor devuelto es el resultado de la llamada a parse_args, que analizará la línea de argumentos que ha recibido nuestro programa y devolverá, si es correcta, un Namespace que contendrá los valores recibidos. En este caso, el programa no debe recibir ninguna opción y aceptará obligatoriamente 2 valores de tipo float. Si se pasaran menos o más de 2, automáticamente el programa fallaría.

#!/usr/bin/env python3

import argparse
import sys

_ALLOWED_OPERATIONS_ = ['sum', 'sub', 'mul', 'div']

def parse_commandline():
    parser = argparse.ArgumentParser(description="Commandline calculator")
    parser.add_argument('value1', type=float, help='First number')
    parser.add_argument('value2', type=float, help='Second number')
    parser.add_argument(
        '-o',
        '--operation',
        choices=_ALLOWED_OPERATIONS_,
        default='sum',
        dest='op',
        help='Operation to execute',
    )

    return parser.parse_args()


def main():
    user_options = parse_commandline()
    if user_options.op == 'sum':
        print(user_options.value1 + user_options.value2)
    elif user_options.op == 'sub':
        print(user_options.value1 - user_options.value2)
    elif user_options.op == 'mul':
        print(user_options.value1 * user_options.value2)
    elif user_options.op == 'div':
        print(user_options.value1 / user_options.value2)

if __name__ == '__main__':
    main()
    sys.exit(0)

En éste otro ejemplo se ha añadido un argumento de opción (-o o --operation) que permite pasar un valor dentro de una lista de valores permitidos y que hace que nuestro método main se comporte de manera diferente según el valor especificado.

logging#

En todo proyecto suele ser necesario añadir diferentes mensajes que indiquen qué está haciendo nuestro programa, tanto a nivel de tener informes de lo que está sucediendo como, en tiempo de desarrollo, para depurar el funcionamiento del programa.

Todos los lenguajes permiten escribir mensajes en el terminal de modo que podamos ver dichos mensajes y saber qué está ocurriendo, pero esa es una solución muy poco práctica, ya que en ocasiones podemos olvidarnos de quitar mensajes de depuración y terminan llegando al producto final, o que muestran información que no debería ser vista por los usuarios de nuestra aplicación.

En ocasiones también es muy práctico poder volcar, según el tipo o la gravedad, los mensajes a la salida estándar, de error o a un fichero, o incluso yendo más allá, utilizar algún sistema de monitorización de logs en la nube. Evidentemente, para este tipo de situaciones un simple print se nos queda muy corto.

Python proporciona la librería logging que permite toda esta serie de operaciones:

  • Definir diferentes “loggers”, que tengan diferentes configuraciones cada uno de ellos.

  • Cada mensaje puede ser enviado con un nivel de “severidad” diferente, yendo desde mensajes de depuración (debug) a mensajes criticos (critical).

  • Desacoplar la escritura de cada mensaje de su configuración, no teniendo que preocuparnos en nuestro código de si el mensaje va a ser enviado a la salida estándar, fichero o un servicio en la nube remoto.

  • Desacoplar el formato con el que se mostrará o almacenará el mensaje de la escritura del mismo.

Veamos algunos ejemplos:

import logging

logging.debug("This is a debug message")
logging.info("This is an info message")
logging.warning("This is a warning message")
logging.error("This is an error message")
logging.critical("This is a critical message")

El ejemplo anterior muestra el uso más básico de la librería, ya que todos los mensajes se enviarán al logger denominado “root”, al no haber especificado otro. Cada mensaje se envía con un nivel diferente. Si lo ejecutamos, veremos que sólo se imprimirán por la salida de error estándar los mensajes de nivel “warning”, “error” y “critical”, ya que el logger por defecto está configurado para emitir a su salida únicamente los mensajes de nivel superior a “info”.

Además, se imprimen con un formato muy concreto:

$ python3 log_example.py
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical message

Ese formato por defecto utilizado incluye en primer lugar el nivel, luego el nombre del logger y por último el mensaje.

Para configurar la salida podemos utilizar la siguiente función:

import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s]:%(message)s', force=True)
logging.debug("This is a debug message")
logging.info("This is an info message")
logging.warning("This is a warning message")
logging.error("This is an error message")
logging.critical("This is a critical message")

En este caso, estamos proporcionando una configuración por defecto diferente, especificando que queremos imprimir aquellos mensajes de nivel “info” o superior y modificando el formato del mensaje. Si volvemos a ejecutarlo ahora, obtendremos:

$ python3 log_example.py
2022-09-25 14:09:42,317 [INFO]:This is an info message
2022-09-25 14:09:42,317 [WARNING]:This is a warning message
2022-09-25 14:09:42,317 [ERROR]:This is an error message
2022-09-25 14:09:42,317 [CRITICAL]:This is a critical message

Como se puede observar, con sólo una línea se ha modificado por completo la salida del programa sin tener que modificar el código como tal de la emisión de mensajes, mostrando el enorme desacoplamiento entre configuración y mensajes que permite la librería logging.

Para más información sobre el uso de esta librería, Python proporciona unos tutoriales básico y avanzado en su documentación.