Flujo de trabajo: estilo

Un buen estilo de código es como una puntuación correcta: puedes arreglártelas sin él, perosinduda hacequelascosasseanmásfácilesdeleer. Incluso si eres un programador muy novato, es buena idea trabajar en el estilo de tu código. Usar un estilo coherente facilita que otras personas (¡incluido tu yo del futuro!) lean tu trabajo, y es especialmente importante si necesitas pedirle ayuda a alguien.

Este capítulo te presentará algunos puntos de estilo importantes extraídos de Clean Code in Python, Tips for Better Coding de Coding for Economists, la guía Quality Assurance of Code for Analysis and Research del Government Statistical Service del Reino Unido, y la biblia de las guías de estilo de Python, PEP 8 — Style Guide for Python Code.

Dar estilo a tu código te parecerá un poco tedioso al principio, pero si lo practicas, pronto se volverá algo natural. Además, existen herramientas excelentes para cambiar rápidamente el estilo de código existente, como el paquete de Python Black (“puedes tener el color que quieras, siempre que sea negro”).

Una vez que hayas instalado Black ejecutando uv tool install black, puedes usarlo en la línea de comandos (también llamada terminal) dentro de Visual Studio Code. Abre una terminal haciendo clic en ‘Terminal -> New Terminal’ y luego ejecuta black *.py para aplicar un estilo de código estándar a todos los scripts de Python del directorio actual.

Nombres

Primero, los nombres importan. Usa nombres significativos para variables, funciones o lo que sea que estés nombrando. Evita abreviaturas que entiendes ahora pero que resultarán confusas para otros o para tu yo del futuro. Por ejemplo, usa real_wage_hourly en lugar de re_wg_ph. Sé que es tentador usar temp, pero te sentirás tonto más tarde cuando no logres recordar de ninguna manera qué hace o qué es temp. Un buen truco al nombrar booleans (variables que son verdaderas o falsas) es usar is seguido de aquello a lo que se refiere la variable boolean, por ejemplo is_married.

Además de este consejo general, Python tiene convenciones para nombrar distintos tipos de variables. La convención de nombres para casi todos los objetos es minúsculas separadas por guiones bajos, p. ej., a_variable=10 o ‘this_is_a_script.py’. Este estilo de nombres también se conoce como snake case. Sin embargo, existen distintas convenciones de nombres: Allison Horst hizo esta fantástica ilustración de las diferentes convenciones que se usan.

Diferentes convenciones de nombres. Ilustración de @allison_horst. Diferentes convenciones de nombres. Ilustración de @allison_horst.

Hay tres excepciones a la convención snake case: las clases, que deben ir en camel case, p. ej., ThisIsAClass; las constantes, que van en snake case en mayúsculas, p. ej., THIS_IS_A_CONSTANT; y los paquetes, que normalmente no llevan espacios ni guiones bajos y van en minúsculas, thisisapackage.

Para algunos atajos rápidos para renombrar columnas en dataframes de pandas u otras variables string, prueba la librería compatible con unicode slugify o la función clean_headers() de la librería dataprep.

Cuanto mejor nombradas estén tus variables, más claro será tu código, ¡y menos comentarios tendrás que escribir!

En resumen: - usa nombres de variables descriptivos que revelen tu intención, p. ej., days_since_treatment - evita usar abreviaturas ambiguas en los nombres, p. ej., usa real_wage_hourly en lugar de rw_ph - usa siempre el mismo vocabulario, p. ej., no cambies de worker_type a employee_type - evita los ‘números mágicos’, es decir, números en tu código que fijan un parámetro clave. En su lugar, defínelos como constantes con nombre. Aquí tienes un ejemplo: ```python import random

# This is bad
def roll():
    return random.randint(0, 36)  # magic number!

# This is good
MAX_INT_VALUE = 36

def roll():
    return random.randint(0, MAX_INT_VALUE)
- usa verbos para los nombres de funciones, p. ej., `get_regression()`
- usa verbos coherentes en los nombres de funciones; no uses `get_score()` y `grab_results()` (en su lugar, usa `get` en ambos)
- los nombres de variables deben ir en snake_case y todo en minúsculas, p. ej., `first_name`
- los nombres de clases deben ir en CamelCase, p. ej., `MyClass`
- los nombres de funciones deben ir en snake_case y todo en minúsculas, p. ej., `quick_sort()`
- las constantes deben ir en snake_case y todo en mayúsculas, p. ej., `PI = 3.14159`
- los módulos deben tener nombres cortos, en snake_case y todo en minúsculas, p. ej., `pandas`
- las comillas simples y dobles son equivalentes, así que elige una y sé coherente; la mayoría de los formateadores automáticos prefieren `"`

## Espacios en blanco

Rodear fragmentos de código con espacios en blanco puede mejorar notablemente la legibilidad. Una de estas convenciones es que las funciones deben ir seguidas de dos líneas en blanco tras su última línea. Otra es que las asignaciones se separan con espacios

```python
this_is_a_var = 5

Otra convención es que aparece un espacio después de una ,; por ejemplo, en la definición de una lista tendríamos

list_var = [1, 2, 3, 4]

en lugar de

list_var = [1,2,3,4]
# or
list_var = [1 , 2 , 3 , 4]

Comentarios en el código

Como se mencionó antes, Python ignora cualquier texto después de #. Esto te permite escribir comentarios, texto que Python ignora pero que otras personas pueden leer. Los comentarios pueden ser útiles para describir brevemente lo que hace el código siguiente: úsalos para aportar información de contexto adicional que no transmiten los nombres de funciones y variables.

En realidad, el código bien escrito necesita menos comentarios porque resulta más evidente lo que ocurre. Y es tentador no actualizar los comentarios aunque cambie el código. Así que comenta, pero intenta primero que el código cuente su propia historia.

Además, evita los comentarios de “ruido” que te dicen lo que ya sabes con solo mirar el código.

Imagen de un gato con una etiqueta que dice gato

Por último, las funciones tienen su propio tipo especial de comentarios, llamado docstring. Aquí tienes un ejemplo que nos explica todo sobre las entradas y salidas de la función, incluido el tipo de entrada y de salida (aquí un dataframe, también conocido como pd.DataFrame).

def round_dataframe(df: pd.DataFrame) -> pd.DataFrame:
    """Rounds numeric columns in dataframe to 2 s.f.
    Args:
        df (pd.DataFrame): Input dataframe
    Returns:
        pd.DataFrame: Dataframe with numbers rounded to 2 s.f.
    """
    for col in df.select_dtypes("number"):
        df[col] = df[col].apply(lambda x: float(f'{float(f"{x:.2g}"):g}'))
    return df

Ancho de línea y continuación de línea

Por razones históricas bastante arbitrarias, PEP8 también sugiere 79 caracteres por cada línea de código. A algunas personas esto les parece demasiado restrictivo, sobre todo con la llegada de monitores más anchos. Pero es bueno dividir las líneas muy largas. Todo lo que esté entre paréntesis se puede dividir en varias líneas así:

def function(input_one, input_two,
             input_three, input_four):
    result = (input_one,
              + input_two,
              + input_three,
              + input_four)
    return result

Al usar encadenamiento de métodos (algo que puedes ver en acción en ) es necesario poner la cadena entre paréntesis, y es buena práctica usar una línea nueva para cada método. El siguiente fragmento de código muestra un ejemplo de cómo hacerlo bien:

import pandas as pd

df = pd.DataFrame(
    data={
        "col0": [0, 0, 0, 0],
        "col1": [0, 0, 0, 0],
        "col2": [0, 0, 0, 0],
        "col3": ["a", "b", "b", "a"],
        "col4": ["alpha", "gamma", "gamma", "gamma"],
    },
    index=["row" + str(i) for i in range(4)],
)


# Chaining inside parentheses works

results = df.groupby(["col3", "col4"]).agg({"col1": "count", "col2": "mean"})

results
col1 col2
col3 col4
a alpha 1 0.0
gamma 1 0.0
b gamma 2 0.0

Y esto es lo que no debes hacer:

results = df
    .groupby(["col3", "col4"]).agg({"col1": "count", "col2": "mean"})

Principios del código limpio

Aunque la automatización puede ayudar a aplicar el estilo, no puede ayudarte a escribir código limpio. El código limpio es un conjunto de reglas y principios que ayudan a mantener tu código legible, mantenible y extensible. Escribir código es fácil; ¡escribir código limpio es difícil! Sin embargo, si sigues estos principios, no te equivocarás mucho.

No te repitas (DRY)

El principio DRY dice: ‘Cada pieza de conocimiento o lógica debe tener una representación única e inequívoca dentro de un sistema’. Divide tu código en piezas reutilizables que puedas llamar cuando y donde quieras. No escribas métodos extensos; divide la lógica en bloques claramente diferenciados.

Esto te ahorra tener que repetir código y no saber si es esta o aquella versión de la misma función la que hace el trabajo, y te ayudará muchísimo en la depuración.

Algunas formas prácticas de aplicar DRY son usar funciones, poner las funciones o el código que deben ejecutar varias veces distintos scripts en otro script (p. ej., llamado utilities.py) e importarlo, y pensar detenidamente si otra forma de escribir tu código sería más concisa (sin dejar de ser legible).

TipConsejo

Si usas Visual Studio Code, puedes mover código automáticamente a una función haciendo clic derecho sobre el código y usando la opción ‘Extract to method’.

KISS (Keep It Simple, Stupid; mantenlo simple)

La mayoría de los sistemas funcionan mejor si se mantienen simples en lugar de complicarlos. Esta regla dice que debes evitar la complejidad innecesaria. Si tu código es complejo, solo te costará más entender lo que hiciste cuando vuelvas a él más adelante.

SoC (separación de responsabilidades) / Hazlo modular

No tengas un único archivo que lo haga todo. Si divides tu código en módulos separados e independientes, será más fácil de leer, depurar, probar y usar. Puedes consultar el capítulo de fundamentos de programación para ver cómo crear e importar funciones desde otros scripts. Pero incluso dentro de un script, puedes hacer tu código modular definiendo funciones con entradas y salidas claras.

Una buena regla general es que si un código que cumple un objetivo supera unas 30 líneas, probablemente debería ir en una función. Los scripts de más de unas 500 líneas también están listos para dividirse.

En relación con esto, no tengas una sola función que intente hacerlo todo. Las funciones también deben tener límites; deberían hacer aproximadamente una sola cosa. Si al ponerle nombre a una función tienes que usar ‘y’ en el nombre, probablemente valga la pena dividirla en dos funciones.

Las funciones tampoco deberían tener ‘efectos secundarios’; es decir, solo deberían recibir uno o varios valores y devolver uno o varios valores mediante una sentencia return. No deberían modificar variables globales ni hacer otros cambios.

Otra buena regla general es que cada función no debería tener muchos argumentos distintos.

Un último consejo sobre la modularidad y la creación de funciones es que no deberías usar ‘flags’ (indicadores) en las funciones (también conocidos como condiciones boolean). Aquí tienes un ejemplo:

# This is bad
def transform(text, uppercase):
    if uppercase:
        return text.upper()
    else:
        return text.lower()

# This is good
def uppercase(text):
    return text.upper()

def lowercase(text):
    return text.lower()