Inicio / Python / Python: Desde Cero hasta Profesional / Buenas Prácticas y Pythonic Code

Buenas Prácticas y Pythonic Code

PEP 8, EAFP vs LBYL, walrus operator, linters y estructura de proyecto.

Avanzado
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes


title: "Buenas Prácticas y Pythonic Code" slug: "python-buenas-practicas" description: "Aprende las convenciones, patrones y herramientas que hacen tu código Python profesional, legible y mantenible."

Buenas Prácticas y Pythonic Code

Escribir código que funcione no es suficiente. El código Pythonic es legible, elegante y sigue las convenciones de la comunidad. En esta lección exploraremos las guías de estilo, los patrones idiomáticos de Python y las herramientas que te ayudarán a mantener un código profesional.

PEP 8 — Guía de Estilo

PEP 8 es la guía de estilo oficial de Python. Algunas reglas clave:

# ✅ Nombres correctos según PEP 8
mi_variable = 42                    # snake_case para variables y funciones
CONSTANTE_GLOBAL = 3.14159          # MAYÚSCULAS para constantes
class MiClase:                      # PascalCase para clases
    pass
_variable_privada = "interna"       # Prefijo _ para privado por convención

# ✅ Indentación: 4 espacios (nunca tabs)
if True:
    print("Indentado con 4 espacios")

# ✅ Longitud de línea: máximo 79-88 caracteres
# Se puede partir con paréntesis implícitos
resultado = (primera_variable
             + segunda_variable
             - tercera_variable)

# ✅ Espacios alrededor de operadores
x = 1 + 2
y = x * 3

# ❌ Evitar espacios innecesarios
# spam( jamón[ 1 ], { huevos: 2 } )  ← incorrecto
spam(jamón[1], {huevos: 2})           # ← correcto

# ✅ Imports en orden: stdlib, terceros, locales
import os                     # 1. Biblioteca estándar
import sys
from pathlib import Path

import requests               # 2. Paquetes de terceros
import pandas as pd

from mi_proyecto import utils  # 3. Imports locales

PEP 20 — El Zen de Python

Los principios filosóficos de Python, accesibles con import this:

import this

# Los más importantes en la práctica:
# 1. "Explícito es mejor que implícito"
# 2. "Simple es mejor que complejo"
# 3. "La legibilidad cuenta"
# 4. "Debería haber una, y preferiblemente solo una, manera obvia de hacerlo"
# 5. "Los errores nunca deben pasar en silencio"

# ✅ Explícito
def calcular_precio(base, iva=0.21):
    return base * (1 + iva)

# ❌ Implícito — ¿qué hace 1.21?
def calcular_precio(b):
    return b * 1.21

EAFP vs LBYL

Python favorece EAFP (Es más fácil pedir perdón que permiso) sobre LBYL (Mira antes de saltar):

# ❌ LBYL — Verificar antes de actuar (estilo C/Java)
def obtener_valor_lbyl(diccionario, clave):
    if clave in diccionario:
        valor = diccionario[clave]
        if isinstance(valor, str):
            return valor.upper()
    return None

# ✅ EAFP — Intentar y manejar excepciones (Pythonic)
def obtener_valor_eafp(diccionario, clave):
    try:
        return diccionario[clave].upper()
    except (KeyError, AttributeError):
        return None

# Otro ejemplo: abrir archivo
# ❌ LBYL
import os
if os.path.exists("archivo.txt"):
    with open("archivo.txt") as f:
        datos = f.read()

# ✅ EAFP
try:
    with open("archivo.txt") as f:
        datos = f.read()
except FileNotFoundError:
    datos = ""

Walrus Operator := (Python 3.8+)

El operador morsa permite asignar y evaluar en una misma expresión:

# ❌ Sin walrus — lectura repetida
linea = input("Ingresa texto (vacío para salir): ")
while linea:
    print(f"Leído: {linea}")
    linea = input("Ingresa texto (vacío para salir): ")

# ✅ Con walrus — más conciso
while (linea := input("Ingresa texto (vacío para salir): ")):
    print(f"Leído: {linea}")

# Útil en comprehensions con filtro
import re
textos = ["precio: $100", "nota: hola", "precio: $250", "info: dato"]
precios = [
    int(m.group(1))
    for texto in textos
    if (m := re.search(r"\$(\d+)", texto))
]
print(precios)  # [100, 250]

# Útil al leer archivos en bloques
with open("datos.bin", "rb") as f:
    while (bloque := f.read(4096)):
        procesar(bloque)

Comprehensions vs Loops

Las comprehensions son más Pythonic para transformar y filtrar datos:

# ✅ List comprehension (preferido para transformaciones simples)
cuadrados = [x ** 2 for x in range(10)]

# ❌ Equivalente con loop (más verboso)
cuadrados = []
for x in range(10):
    cuadrados.append(x ** 2)

# ✅ Dict comprehension
precios_usd = {"laptop": 999, "mouse": 25, "teclado": 75}
precios_eur = {k: v * 0.92 for k, v in precios_usd.items()}

# ✅ Set comprehension
unicos = {palabra.lower() for palabra in ["Hola", "hola", "HOLA", "Mundo"]}
# {'hola', 'mundo'}

# ⚠️ Pero NO abuses — si es complejo, usa un loop
# ❌ Ilegible
resultado = [
    transformar(x)
    for grupo in datos
    for x in grupo.items
    if x.activo and x.valor > 0
    if x.tipo in tipos_validos
]

# ✅ Más legible como función con loop
def filtrar_y_transformar(datos, tipos_validos):
    resultado = []
    for grupo in datos:
        for x in grupo.items:
            if x.activo and x.valor > 0 and x.tipo in tipos_validos:
                resultado.append(transformar(x))
    return resultado

F-strings Avanzados

nombre = "Ana"
precio = 1234.5678
fecha = "2026-02-23"

# Formateo básico
print(f"Hola, {nombre}")

# Expresiones dentro de f-strings
print(f"2 + 3 = {2 + 3}")
print(f"{'Hola'.upper()}")

# Formato numérico
print(f"Precio: ${precio:.2f}")        # $1234.57
print(f"Precio: ${precio:,.2f}")       # $1,234.57
print(f"Entero: {42:05d}")             # 00042
print(f"Porcentaje: {0.856:.1%}")      # 85.6%
print(f"Binario: {255:08b}")           # 11111111
print(f"Hex: {255:#06x}")             # 0x00ff

# Alineación
print(f"{'Izquierda':<20}|")     # Izquierda           |
print(f"{'Derecha':>20}|")       #             Derecha|
print(f"{'Centro':^20}|")        #        Centro       |

# Debug con = (Python 3.8+)
x = 42
print(f"{x = }")          # x = 42
print(f"{x * 2 = }")      # x * 2 = 84
print(f"{nombre = !r}")    # nombre = 'Ana'

# Multiline f-strings
mensaje = (
    f"Usuario: {nombre}\n"
    f"Precio: ${precio:.2f}\n"
    f"Fecha: {fecha}"
)

Logging Profesional

import logging

# Configuración básica
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)

logger = logging.getLogger(__name__)

# Niveles de logging
logger.debug("Mensaje de depuración")      # No se muestra con INFO
logger.info("Aplicación iniciada")
logger.warning("Disco casi lleno")
logger.error("No se pudo conectar a la BD")
logger.critical("Sistema caído")

# ✅ Usar lazy formatting (más eficiente)
usuario_id = 42
logger.info("Usuario %d conectado", usuario_id)

# ❌ Evitar f-strings en logging (se evalúan siempre)
logger.debug(f"Datos: {datos_enormes}")  # Se evalúa aunque no se muestre

# Logging con excepciones
try:
    resultado = 1 / 0
except ZeroDivisionError:
    logger.exception("Error en el cálculo")  # Incluye traceback

Logging Estructurado con structlog

# pip install structlog
import structlog

logger = structlog.get_logger()

# Logs estructurados con contexto
logger.info("usuario_creado", nombre="Ana", email="ana@test.com")
# 2026-02-23 10:30:00 [info] usuario_creado nombre=Ana email=ana@test.com

# Enlazar contexto permanente
log = logger.bind(request_id="abc-123")
log.info("procesando_pedido", pedido_id=456)
log.info("pedido_completado", total=99.99)

Linters y Formateadores

Ruff — Linter ultrarrápido

# Instalar
pip install ruff

# Verificar código
ruff check mi_modulo.py

# Corregir automáticamente
ruff check --fix mi_modulo.py

# Formatear código (reemplaza black)
ruff format mi_modulo.py

Black — Formateador inflexible

pip install black

# Formatear archivo
black mi_modulo.py

# Verificar sin modificar
black --check mi_modulo.py

isort — Ordenar imports

pip install isort

# Ordenar imports
isort mi_modulo.py

# Perfil compatible con black
isort --profile black mi_modulo.py

Configuración centralizada en pyproject.toml

[tool.ruff]
line-length = 88
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP"]

[tool.black]
line-length = 88
target-version = ["py311"]

[tool.isort]
profile = "black"

[tool.mypy]
python_version = "3.11"
strict = true

Estructura de Proyecto

mi_proyecto/
├── pyproject.toml          # Configuración del proyecto
├── README.md
├── LICENSE
├── src/
│   └── mi_paquete/
│       ├── __init__.py
│       ├── modelos.py
│       ├── servicios.py
│       └── utils.py
├── tests/
│   ├── conftest.py
│   ├── test_modelos.py
│   └── test_servicios.py
├── docs/
│   └── guia.md
└── scripts/
    └── seed_data.py
# pyproject.toml moderno
[project]
name = "mi-proyecto"
version = "1.0.0"
description = "Mi proyecto Python"
requires-python = ">=3.11"
dependencies = [
    "requests>=2.28",
    "pydantic>=2.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "ruff>=0.1",
    "mypy>=1.0",
]

Ejercicio Práctico

Refactoriza el siguiente código aplicando todas las buenas prácticas aprendidas:

# ❌ Código ANTES (anti-Pythonic)
import os, sys, json
from typing import *

def getData(f):
  data=[]
  file=open(f,'r')
  for l in file:
    d=json.loads(l)
    if d['active']==True:
      if d['age']>=18:
        data.append({'Name':d['name'],'Age':d['age']})
  file.close()
  return data

# ✅ Código DESPUÉS (Pythonic)
import json
from pathlib import Path
from typing import Optional

def obtener_usuarios_activos(
    ruta: str | Path,
    edad_minima: int = 18,
) -> list[dict[str, str | int]]:
    """Lee usuarios activos mayores de edad desde un archivo JSON Lines."""
    ruta = Path(ruta)
    usuarios = []

    with ruta.open("r", encoding="utf-8") as f:
        for linea in f:
            try:
                dato = json.loads(linea)
            except json.JSONDecodeError:
                continue

            if dato.get("active") and dato.get("age", 0) >= edad_minima:
                usuarios.append({
                    "nombre": dato["name"],
                    "edad": dato["age"],
                })

    return usuarios

Reto: Toma un script propio o de un proyecto open source y aplica las buenas prácticas de esta lección: PEP 8, EAFP, comprehensions, f-strings, type hints y logging.

Resumen

  • PEP 8 establece las convenciones de estilo: snake_case, 4 espacios, 79-88 caracteres por línea.
  • PEP 20 (Zen de Python) promueve la simplicidad, la legibilidad y lo explícito.
  • EAFP (try/except) es más Pythonic que LBYL (if/else antes de actuar).
  • El walrus operator (:=) asigna y evalúa en la misma expresión.
  • Las comprehensions son preferidas sobre loops para transformaciones simples.
  • Los f-strings ofrecen formateo potente con :.2f, :,, =, alineación y más.
  • Usa logging (no print) en código de producción, con lazy formatting.
  • Ruff, Black e isort mantienen tu código formateado y consistente automáticamente.
  • Centraliza la configuración de herramientas en pyproject.toml.
🔒

Ejercicio práctico disponible

Refactorización y buenas prácticas

Desbloquear ejercicios
// Refactorización y buenas prácticas
// Desbloquea Pro para acceder a este ejercicio
// y ganar +50 XP al completarlo

function ejemplo() {
    // Tu código aquí...
}

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes