Rootstack

Guía General: Versiones de Python

December 05, 2023

Tags: Tecnologías

python

 

Evitar trabajar y sugerir migraciones a cualquier proyecto desarrollado en Python 2 debido a encontrar en EOL. A partir de Python 3 los cambios introducidos no suelen romper implementaciones, aunque debe tenerse cierta precaución si la diferencia de versiones es muy alta.

 

También es importante mantenerse informado del status y el timeline que lleva cada versión visitando https://devguide.python.org/versions/. Al momento de este guideline Python 3.11 es buena opción para proyectos nuevos.

 

Guía de estilo de Python

 

PSF (Python Foundation) sugiere seguir una guía de estilos conocida como PEP 8 (https://peps.python.org/pep-0008/). Es una lectura extensa pero recomendable; sin embargo también existe el PEP 20 o "Zen of Python" (https://peps.python.org/pep-0020/#the-zen-of-python) que ayuda a comprender a más alto nivel las reglas del lenguaje. 

 

Estos dos PEP influyen mucho en lo cómo está escrito y estructurado el código en Python. No es necesario aprender estos estándares por completo, existe un paquete llamado pycodestyle que ayuda a seguir el PEP 8. También existen otros que extienden sobre la funcionalidad de pycodestyle , aunque con diversos "sabores" o reglas ( flake8 , black ) y otros para limpieza de código y mantener orden ( isort ). Una combinación de estos paquetes, sirve como buen base para el desarrollo de pipelines de CI.

 

En el caso de Rootstack, el paquete a utilizar por defecto para formato del código será black que valida con el PEP8 y ofrece visualizar los cambios recomendados (No debe utilizarse para cambios automáticos, si no como guía). Es necesario pasar la opción -l 79 a black para que siga el estándar de PEP 8 en cuanto a longitud de líneas.

 

Ejemplo:

 

black -l 79 --check --diff

 

python

 

Uso de tipos estáticos

 

Utilizar tipos estáticos en la definición de funciones o métodos si la tecnología/framework lo permite. El caso de variables, no es necesario anotar el tipo si la misma es utilizada solo de manera local. Atributos de clases y objetos deben quedar con sus tipos anotados.

 

Para validar los tipos estáticos anotados en el código utilizamos mypy.

 

Ejemplo de configuración ( mypy.ini ):

 

[mypy]
python_version = "3.10"
# No retornar Any
warn_return_any = True
warn_unused_configs = True
# Las funciones deben tener tipo de retorno
disallow_untyped_refs = True
disallow_untyped_calls = True
# Necesario en algunos casos para librerías que no incluyen tipos
ignore_missing_imports = True

 

El código debe ser lo suficientemente expresivo para no necesitar comentarios; sin embargo es aceptable en caso de aclaraciones de implementaciones "no estándar", ligadas a una necesidad específica (ejemplo, una tarea lo exige). Los métodos y funciones deben estar comentados con una descripción breve de la función, sus parámetros y valor de retorno.

 

Utilizar un ambiente virtual (virtualenv)

 

Un ambiente virtual provee un ambiente aislado, donde los paquetes y programas de Python son instalados aparte de las instalaciones realizadas globalmente a nivel del sistema; esto facilita controlar mejor los requisitos por proyecto.

 

Uno de los paquetes más utilizados para administrar los ambientes virtuales es virtualenv y puede ser adquirido a través de pip . Asumiendo que pip está ya instalado en el sistema ( python3-pip en el caso de Ubuntu) , las siguientes son instrucciones del uso básico del paquete virtualenv.

 

# Instala el paquete globalmente
pip install virtualenv
# Crea y "activa" el ambiente virtual, agregando los binarios (`.venv/bin`)
al PATH y configurando otras variables de Python.
# La opción "-p" selecciona el ejecutable de Python3 como ejecutable de
Python para este ambiente virtual.
# El ambiente virtual guardará sus archivos en `.venv`
virtualenv -p /usr/bin/python3 .venv
source .venv/bin/activate

 

Luego de esto, cualquier llamado a python o pip instalará y utilizará los paquetes en el ambiente virtual que esté activo. Es decir, realizar un pip install -r requirements.txt , únicamente descargará las dependencias en este ambiente; facilitando administrar múltiples proyectos con dependencias distintas.

 

# Dejar de utilizar el ambiente aislado.
.venv/bin/deactivate

 

Normalmente cada proyecto utilizaría su propio ambiente virtual, pero también puede darse el caso en que se trabajen proyectos con dependencias similares o que dependan el uno del otro. En estos casos no hay problema en mantener el mismo ambiente activo mientras se trabaja en ambos proyectos.

 

python

 

Definir un archivo de dependencias

 

La regla principal es mantener un requirements.txt y un requirements-dev.txt para las dependencias funcionales como de desarrollo del proyecto.

 

Este archivo debe listar las dependencias y versiones utilizadas en el proyecto. Para proyectos nuevos, estos pueden obtenerse ejecutando pip freeze > requirements.txt dentro del ambiente virtual del proyecto. A partir de aquí puede separarse en dependencias de desarrollo y actualizar versiones.

 

Nota: Ejecutar este comando fuera de un ambiente virtual puede listar dependencias de otros proyectos, inclusive instalados por el gestor de paquetes del sistema y no es la manera óptima de obtener las dependencias del proyecto.

 

Para las dependencias de desarrollo como pytest o black, se debe crear un archivo aparte requirements-dev.txt . De esta manera podemos instalar los paquetes necesarios para ejecución, sin necesidad de incluir otros paquetes de pruebas.

 

Es práctica común incluir también el archivo requirements.txt dentro de las dependencias de desarrollo, especialmente si se están incluyendo pruebas que dependan de la funcionalidad de la aplicación.

 

-r requirements.txt
pytest>=7.4.0

 

python

 

Estructura de Código

 

Para esta sección es importante tener claro el concepto de módulo vs paquete. Un módulo es cada uno de los archivos .py del proyecto, los mismos pueden contener múltiples definiciones de clases o funciones. Los paquetes son utilizados para estructurar/agrupar módulos dentro de una aplicación.

 

Como regla general, los paquetes son utilizados para separar los módulos según el propósito (o dominio) que tengan dentro de la aplicación (controladores, config, servicios...), mientras que los módulos de cada uno indican la especificidad (servicio de usuarios, servicio de publicaciones). El cómo se agrupan a nivel de directorios y la nomenclatura puede variar entre frameworks, pero suele ir bajo el mismo principio.

 

Con estos conceptos claros, los namespaces definirán la interfaz de los paquetes, qué clases y funciones están disponibles y cómo los demás módulos accederán a ellas.

 

Ejemplo:



├── README.md
├── package
│ ├── __init__.py
│ ├── app.py
│ ├── subpackage1
│ │ ├── __init__.py
│ │ └── config.py
│ └── subpackage2
│ ├── __init__.py
│ └── user_service.py
├── requirements-dev.txt
├── requirements.txt
└── tests
└── __init__.py
5 directories, 10 files

 

python

 

Malas Prácticas Comunes

 

Argumentos mutables en definiciones de métodos

 

Los argumentos de los métodos obtienen su valor en tiempo de ejecución, haciendo que múltiples llamados al mismo método o función compartan el valor de la variable. Ejemplo:

 

def prueba(argumento_mutable: dict = {}) -> None:
pass

 

En este caso, el diccionario argumento_mutable de ser modificado dentro de la función, el valor cambiará en distintas ejecuciones ya que argumento_mutable se define en un scope más alto. La versión correcta, sería definirlo como None o para mejor comprensión de los IDEs, el uso de typing.Optional para indicar el tipo correcto sería lo indicado.

 

Otra opción a partir de Python 3.10+ es utilizar el tipo Union, ahora incluido en estándar del lenguaje.

 

Importar paquetes completos

 

Tomando como ejemplo el error anterior, también es importante destacar la importación realizada del paquete typing . El "Zen of Python" lo dice ( import this ) "Explicit is better than implicit.".

 

Se importa específicamente el módulo Optional , en lugar de solo utilizar import typing o from typing import * . Esto es buen práctica para evitar conflictos y tener claras las dependencias de nuestro proyecto. Así, si otro paquete tiene el módulo Optional , está explícito que se está importando el de typing y en caso de requerir ambos, se puede actuar acorde.

 

En el último caso, hay que asegurar de utilizar siempre el namespace donde se encuentra Optional . Teniendo que ser llamado typing.Optional dentro del código. 

 

python

 

Tareas / Paquetes Comunes

 

Logging

 

El logging en Python está integrado como parte del estándar del lenguaje en el módulo logging. En general, todos los demás módulos son desarrollados con este paquete en mente y esperan que la aplicación principal lo utilice también.

 

Ejemplo de configuración de formato en un logger específico:

 

import logging
def main():
logger = logging.getLogger()
handler = logging.StreamHandler() # Stream para streams de Unix, también
puede configurarse para escribir archivos.
formatter = logging.Formatter("%(asctime)s:%(levelname)s:" "%(name)-0s:
%(message)s")
logger.setLevel(logging.INFO)
handler.setFormatter(formatter) # El formatter va atado al handler.
logger.addHandler(handler)
logging.info("TEST")
logging.info(['this', 'is', 'a list'])

 

Testing

 

Las pruebas son creadas en el directorio tests/ del proyecto y ejecutadas con pytest . Esto permite integración directa con pruebas que utilizan la librería unittest , teniendo en cuenta que las evaluaciones finales se estarán haciendo más que todo con assert.

 

La excepción a esta regla es el caso de los objetos de prueba, creados utilizando unittest.mock , donde aplican las evaluaciones con mock_func.assert_* ( assert_called , assert_called_with , etc...).

 

Coverage

 

Para la revisión de cobertura del código en relación a las pruebas, utilizamos un plugin de Pytest que se instala bajo el paquete pytest-cov . La generación del reporte se habilita pasando la opción --cov=[ruta al código] en el comando de Pytest como se muestra a continuación.

 

Las demás opciones para el formato del reporte o porcentaje aceptable de cobertura, que dependerá del proyecto, pueden encontrarse en la referencia de esta sección.

 

Te recomendamos en video