Buenas Prácticas - Python

Consejos que te ayudarán a mejorar tus skills en el desarrollo de software (con Python).

python
software-development
Autor/a

Francisco Alfaro

Fecha de publicación

15 de agosto de 2021

Buenas Prácticas


Introducción

Una pregunta que surgue a menudo cuando uno se encuentra programando es saber cuál es la forma correcta de programar. La respuesta es que no existe la forma correcta de programar (ya sea en Python o cualquier otro lenguaje), sin embargo, existen estandares dentro del mundo de la programación, con el fin de hacer el código más legible, sencillo de entender y ayudar a encontrar posibles errores.

En esta sección se mostrará algunos conceptos sencillos que te ayudarán a mejorar tus skills en el desarrollo de software (con Python).

Estilo de codificación: PEP8

El PEP8 es un estilo de codificación que proporciona convenciones de codificación para el código Python que comprende la biblioteca estándar en la distribución principal de Python.

Algunos aspectos importantes:

  • El PEP8 y el PEP 257 (Docstring Conventions) fueron adaptados del ensayo original de la Guía de estilo Python de Guido, con algunas adiciones de la guía de estilo de Barry.

  • Esta guía de estilo evoluciona con el tiempo a medida que se identifican convenciones adicionales y las convenciones pasadas se vuelven obsoletas debido a cambios en el propio lenguaje.

  • Muchos proyectos tienen sus propias pautas de estilo de codificación. En caso de conflicto, dichas guías específicas del proyecto tienen prioridad para ese proyecto.

Basados en el PEP8 y algunas buenas prácticas del diseño de software, veamos ejemplo para poder escribir de mejor forma nuestros códigos.

Variables

Cuando sea posible, define variables con nombres que tengan algún sentido o que puedas identificar fácilmente, no importa que sean más largas. Por ejemplo, en un programa podríamos escribir:

a = 10.  
b = 3.5 
print(f"El area es {a*b}" )
El area es 35.0

pero, ¿qué significan a y b? lo sabemos por el comentario (bien hecho), pero si más adelante nos encontramos con esas variables, tendremos que recordar cual es cual. Es mejor usar nombres con significado:

altura = 10.  
base = 3.5 
print(f"El area es {a*b}" )
El area es 35.0

Lineas de códigos

Las líneas de codigo no deben ser muy largas, como mucho 72 caracteres. Si se tiene una línea larga, se puede cortar con una barra invertida (\) y continuar en la siguiente línea:

print("Esta es una frase muy larga, se puede cortar con un \
       y seguir en la línea inferior.")
Esta es una frase muy larga, se puede cortar con un        y seguir en la línea inferior.

Comentarios

Los comentarios son muy importantes al escribir un programa. Describen lo que está sucediendo dentro de un programa, para que una persona que mira el código fuente no tenga dificultades para descifrarlo.

#
# esto es un comentario
print('Hola')
Hola

También podemos tener comentarios multilíneas:

#
# Este es un comentario largo
# y se extiende
# a varias líneas

Importaciones

Las importaciones generalmente deben estar en líneas separadas:

#
# no:
import sys, os
#
# si:
import os
import sys

Comparaciones

Existen varias formas de hacer comparaciones de objetos (principalmente en el uso del bucle if), acá se dejan alguna recomendaciones:

# no
if greeting == True:

# no
if greeting is True:
# si
if greeting:

Identación

Dentro de paréntesis, corchetes o llaves, no dejar espacios inmediatamente dentro de ellos:

#
# no
lista_01 = [1, 2, 3,4, 5, 6,7, 8, 9,]
#
# si 
lista_01 = [
    1, 2, 3,
    4, 5, 6,
    7, 8, 9, 
]

Aunque en Python se pueden hacer varias declaraciones en una línea, se recomienda hacer sólo una en cada línea:

#
# no
a = 10; b = 20
#
# si
a = 10
b = 20  

Cuando se trabaja con lista, conjuntos y/o tuplas se recomienda poner en cada línea sus argumentos.

#
# no
lista = [(1, 'hola'),(2, 'mundo'),]  
#
# si
lista = [
    (1, 'hola'),
    (2, 'mundo'),
]

Lo anterior se puede extender para funciones con muchos argumentos

#
# no
def funcion_01(x1,x2,x3,x4):
    print(x1,x2,x3,x4)
    
def funcion_02(
    x1,x2,x3,x4):
    print(x1,x2,x3,x4)
#
# si
def funcion_01(x1,x2,
               x3,x4):
    
    print(x1,x2,x3,x4)
    
def funcion_02(
        x1,x2,
        x3,x4):
    
    print(x1,x2,x3,x4)

Operadores binarios

Un tema interesante es corresponde a la identación respecto a los operadores binarios, acá se muestra la forma correcta de hacerlo:

# no
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)
# si
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

Manipulación de listas

Aunque combinar iterables con elementos de control de flujo para manipular listas es muy sencillo con Python, hay métodos específicos más eficientes para hacer lo mismo. Pensemos el fitrado de datos de una lista:

#
# Seleccionar los números positivos
numeros = [-3, 2, 1, -8, -2, 7]
positivos = []
for i in numeros:
    if i > 0:
        positivos.append(i)
        
print(f"positivos: {positivos}")
positivos: [2, 1, 7]

Aunque técnicamente es correcto, es más eficiente hacer List Comprehension:

#
# comprension de lista
numeros = [-3, 2, 1, -8, -2, 7]
positivos = [i for i in numeros if i > 0] # List Comprehension
print(f"positivos: {positivos}")
positivos: [2, 1, 7]

Especificar tipo de error

Cuando se ocupa try/except, es necesario especificar el tipo de error que se está cometiendo.

#
# importar librerias
import sys
#
# no
try:
    r = 1/0
except:
    print("Oops! ocurrio un",sys.exc_info()[0])
Oops! ocurrio un <class 'ZeroDivisionError'>
#
# si
try:
    r = 1/0
except ZeroDivisionError:
    print("Oops! ocurrio un",sys.exc_info()[0])
Oops! ocurrio un <class 'ZeroDivisionError'>

Explicitar dependencias de una función

Siempre es mejor definir las variables dentro de una función y no dejar variables globales.

#
# no
valor = 5

def funcion_01(variable):
    return 2*variable + valor
funcion_01(2)
9
#
# si
def funcion_01(variable,valor):
    return 2*variable + valor
funcion_01(2,5)
9

Dynamic/Static typing

Con Python 3 se puede especificar el tipo de parámetro y el tipo de retorno de una función (usando la notación PEP484 y PEP526. Se definen dos conceptos claves:

  • Escritura dinámica: no se especifican los atributos de los inputs ni de los ouputs
  • Escritura estática: se especifican los atributos de los inputs y los ouputs
#
# escritura dinámica
def suma(x,y):
    return x+y
print(suma(1,2))
3
#
# escritura estatica
def suma(x:float,
         y:float)->float:
    return x+y
print(suma(1,2))
3

Para la escritura estática, si bien se especifica el tipo de atributo (tanto de los inputs o outputs), la función puede recibir otros tipos de atributos.

print(suma("hola"," mundo"))
hola mundo

Para validar los tipos de datos son los correctos, se deben ocupar librerías especializadas en la validación de datos (por ejemplo: pydantic).

Librerías

Existen librerías que pueden ayudar a corregir errores de escrituras en tú código (también conocido como Análisis Estático), por ejemplo:

  • black: El formateador de código inflexible.
  • flake8: La herramienta para aplicar la guía de estilo PEP8.
  • mypy: Mypy es un verificador de tipo estático para Python 3.

Documentación

Casi tan importante como la escritura de código, es su correcta documentación, una parte fundamental de cualquier programa que a menudo se infravalora o simplemente se ignora. Aparte de los comentarios entre el código explicando cómo funciona, el elemento básico de documentación de Python es el Docstring o cadena de documentación, que ya hemos visto. Simplemente es una cadena de texto con triple comillas que se coloca justo después de la definición de función o clase que sirve de documentación a ese elemento.

def potencia(x, y):
    """
    Calcula la potencia arbitraria de un numero
    """
    return x**y
# Acceso a la documentación
potencia.__doc__
'\n    Calcula la potencia arbitraria de un numero\n    '
# Acceso a la documentación
help(potencia)
Help on function potencia in module __main__:

potencia(x, y)
    Calcula la potencia arbitraria de un numero

Lo correcto es detallar lo mejor posible en el Docstring qué hace y cómo se usa la función o clase y los parámetros que necesita. Se recomienda usar el estilo de documentación del software de documentación sphinx, que emplea reStructuredText como lenguaje de marcado.

Veamos un ejemplo de una función bien documentada:

def potencia(x, y):
    """
    Calcula la potencia arbitraria de un numero

    :param x: base
    :param y: exponente
    :return:  potencia de un numero
    :ejemplos:
    
    >>> potencia(2, 1)
    2
    >>> potencia(3, 2)
    9
    """

    return x**y
# Acceso a la documentación
potencia.__doc__
'\n    Calcula la potencia arbitraria de un numero\n\n    :param x: base\n    :param y: exponente\n    :return:  potencia de un numero\n    :ejemplos:\n    \n    >>> potencia(2, 1)\n    2\n    >>> potencia(3, 2)\n    9\n    '
# Acceso a la documentación
help(potencia)
Help on function potencia in module __main__:

potencia(x, y)
    Calcula la potencia arbitraria de un numero
    
    :param x: base
    :param y: exponente
    :return:  potencia de un numero
    :ejemplos:
    
    >>> potencia(2, 1)
    2
    >>> potencia(3, 2)
    9

Existen varias formas de documentar tus funciones, las principales encontradas en la literatura son: * Google docstrings: forma de documentación recomendada por Google.. * reStructured Text: estándar oficial de documentación de Python; No es apto para principiantes, pero tiene muchas funciones. * NumPy/SciPy docstrings: combinación de NumPy de reStructured y Google Docstrings.

Zen de python

El Zen de Python te dará la guía para decidir sobre que hacer con tu código, no te dice como lo debes escribir, sino como debes pensar si estas programando en Python.

Principios importantes:

  • Explícito es mejor que implícito: Que no se asuma nada, asegúrate que las cosas sean.
  • Simple es mejor que complejo: Evita código complejo, código espagueti o que hace mas cosas para poder hacer una simple tarea.
  • Plano es mejor que anidado: Si tu código tiene mas de 3 niveles de identación, deberías mover parte de ese código a una función.
  • Los errores nunca deberían pasar silenciosamente: No uses un Try/Except sin definir que tipo de error vas a cachar, viene de la mano con Explicito es mejor que implícito.
  • Si la implementación es difícil de explicar, es mala idea.

También, podemos ver el mensaje original del zen de python, ejecutando la siguiente linea de comando.

import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Más consejos

Los consejos que se presentan son de mucha utilidad si usted quiere llevar sus conociminetos de programación al siguiente nivel, sin embargo, el contenido de cada uno amerita un curso por si solo. Se deja recomienda al lector seguir profundizando en estos temas.

Entender programación multiparadigma

Python al ser multiparadigma, nos da una amplia gama de posibilidades de diseñar nuestros códigos. Dentro de estos se destacan:

Cuándo ocupar uno o la otra, va a depender de cómo queremos abordar una determinada problemática, puesto que en la mayoría de los casos, se puede pasar de un paradigma a o otro (incluso mezclarlos de ser necesario).

Principio S.O.L.I.D

En ingeniería de software, SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion) es un acrónimo mnemónico introducido por Robert C. Martin a comienzos de la década del 2000 que representa cinco principios básicos de la programación orientada a objetos y el diseño. Cuando estos principios se aplican en conjunto es más probable que un desarrollador cree un sistema que sea fácil de mantener y ampliar con el tiempo.

En el siguiente link se deja una guía para poder entender estos conceptos en python.

Patrones de diseño

Los patrones de diseño son la base para la búsqueda de soluciones a problemas comunes en el desarrollo de software y otros ámbitos referentes al diseño de interacción o interfaces.

Un patrón de diseño es una solución a un problema de diseño.

Se destacan tres tipos de patrones de diseños:

  • Comportamiento
  • Creacionales
  • Estructurales

En el siguiente link se deja una guía para poder entender estos conceptos en python.

Libros

Esta sección ofrece una selección de libros fundamentales para programadores, abarcando temas como codificación limpia, manejo de código heredado, refactorización y desarrollo profesional. Escritos por expertos como Robert C. Martin, Michael C. Feathers, Martin Fowler y Thomas Hunt, estos libros proporcionan consejos prácticos y estrategias probadas para mejorar la calidad del código y avanzar en la carrera de programación.

  • The Clean Coder: A Code Of Conduct For Professional Programmers Robert C. Martin (2011)
  • Clean Code: A Handbook of Agile Software - Robert C. Martin (2009).
  • Working effectively with legacy code Michael C. Feathers (2004)
  • Refactoring Martin Fowler (1999)
  • The Pragmatic Programmer Thomas Hunt (1999)

Referencias