Communitygithub.com

patrones-python

Modismos pitónicos, estándares PEP 8, sugerencias de tipo y mejores prácticas para construir aplicaciones Python robustas, eficientes y mantenibles. Actívalo al escribir o revisar código Python y la estructura idiomática, el tipado o PEP 8 estén en duda.

¿Qué es patrones-python?

Esta habilidad proporciona un conjunto completo de patrones y mejores prácticas de desarrollo Python, diseñada para ayudar a desarrolladores y agentes de IA a escribir código que sea a la vez idiomático y robusto. Cubre principios fundamentales como la legibilidad, el estilo EAFP y la preferencia por lo explícito sobre lo implícito, todos basados en el Zen de Python y los estándares PEP 8. También incluye secciones detalladas sobre el uso moderno de sugerencias de tipo (incluyendo Protocol), manejo de errores con excepciones específicas y encadenamiento de excepciones, y contexto gestores para una gestión eficiente de recursos. Para el código concurrente, la habilidad presenta patrones de threading y multiprocesamiento usando `concurrent.futures`, así como patrones asíncronos con `asyncio`, asegurando que el desarrollador pueda elegir el enfoque adecuado para cada tarea. Además, se abordan técnicas de rendimiento como el uso de `__slots__`, generadores para datos grandes y evitar la concatenación de cadenas en bucles, junto con la integración de herramientas como `black`, `isort` y `ruff` para un flujo de trabajo moderno de Python. La habilidad es útil en cualquier contexto donde se escriba, revise o refactorice código Python, desde scripts pequeños hasta aplicaciones y paquetes grandes, y está diseñada para funcionar con cualquier plataforma que soporte la ejecución de agentes de IA (como Claude Code, Cursor, Codex, etc.).

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/python-patterns

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Patrones de Desarrollo Python

Patrones Python idiomáticos y mejores prácticas para construir aplicaciones robustas, eficientes y mantenibles.

Cuándo activarlo

  • Escribir código Python nuevo
  • Revisar código Python
  • Refactorizar código Python existente
  • Diseñar paquetes/módulos Python

Principios fundamentales

1. La legibilidad cuenta

Python prioriza la legibilidad. El código debe ser claro y fácil de entender.

# Bueno: Claro y legible
def get_active_users(users: list[User]) -> list[User]:
    """Return only active users from the provided list."""
    return [user for user in users if user.is_active]


# Malo: Astuto pero confuso
def get_active_users(u):
    return [x for x in u if x.a]

2. Explícito es mejor que implícito

Evita la magia; sé claro sobre lo que hace tu código.

# Bueno: Configuración explícita
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# Malo: Efectos secundarios ocultos
import some_module
some_module.setup()  # ¿Qué hace esto?

3. EAFP - Es más fácil pedir perdón que permiso

Python prefiere el manejo de excepciones a verificar condiciones.

# Bueno: Estilo EAFP
def get_value(dictionary: dict, key: str, default_value: Any = None) -> Any:
    try:
        return dictionary[key]
    except KeyError:
        return default_value

# Malo: Estilo LBYL (mira antes de saltar)
def get_value(dictionary: dict, key: str, default_value: Any = None) -> Any:
    if key in dictionary:
        return dictionary[key]
    else:
        return default_value

Sugerencias de tipo

Anotaciones de tipo básicas

from typing import Optional, List, Dict, Any

def process_user(
    user_id: str,
    data: Dict[str, Any],
    active: bool = True
) -> Optional[User]:
    """Procesa un usuario y devuelve el User actualizado o None."""
    if not active:
        return None
    return User(user_id, data)

Sugerencias de tipo modernas (Python 3.9+)

# Python 3.9+ - Usa tipos incorporados
def process_items(items: list[str]) -> dict[str, int]:
    return {item: len(item) for item in items}

# Python 3.8 y anteriores - Usa el módulo typing
from typing import List, Dict

def process_items(items: List[str]) -> Dict[str, int]:
    return {item: len(item) for item in items}

Alias de tipo y TypeVar

from typing import TypeVar, Union

# Alias de tipo para tipos complejos
JSON = Union[dict[str, Any], list[Any], str, int, float, bool, None]

def parse_json(data: str) -> JSON:
    return json.loads(data)

# Tipos genéricos
T = TypeVar('T')

def first(items: list[T]) -> T | None:
    """Devuelve el primer elemento o None si la lista está vacía."""
    return items[0] if items else None

Duck typing basado en Protocol

from typing import Protocol

class Renderable(Protocol):
    def render(self) -> str:
        """Renderiza el objeto a una cadena."""

def render_all(items: list[Renderable]) -> str:
    """Renderiza todos los elementos que implementan el protocolo Renderable."""
    return "\n".join(item.render() for item in items)

Patrones de manejo de errores

Manejo específico de excepciones

# Bueno: Capturar excepciones específicas
def load_config(path: str) -> Config:
    try:
        with open(path) as f:
            return Config.from_json(f.read())
    except FileNotFoundError as e:
        raise ConfigError(f"Archivo de configuración no encontrado: {path}") from e
    except json.JSONDecodeError as e:
        raise ConfigError(f"JSON inválido en la configuración: {path}") from e

# Malo: except desnudo
def load_config(path: str) -> Config:
    try:
        with open(path) as f:
            return Config.from_json(f.read())
    except:
        return None  # ¡Fallo silencioso!

Encadenamiento de excepciones

def process_data(data: str) -> Result:
    try:
        parsed = json.loads(data)
    except json.JSONDecodeError as e:
        # Encadena excepciones para preservar el traceback
        raise ValueError(f"Falló al analizar los datos: {data}") from e

Jerarquía de excepciones personalizada

class AppError(Exception):
    """Excepción base para todos los errores de la aplicación."""
    pass

class ValidationError(AppError):
    """Se lanza cuando falla la validación de entrada."""
    pass

class NotFoundError(AppError):
    """Se lanza cuando no se encuentra un recurso solicitado."""
    pass

# Uso
def get_user(user_id: str) -> User:
    user = db.find_user(user_id)
    if not user:
        raise NotFoundError(f"Usuario no encontrado: {user_id}")
    return user

Gestores de contexto

Gestión de recursos

# Bueno: Usar gestores de contexto
def process_file(path: str) -> str:
    with open(path, 'r') as f:
        return f.read()

# Malo: Gestión manual de recursos
def process_file(path: str) -> str:
    f = open(path, 'r')
    try:
        return f.read()
    finally:
        f.close()

Gestores de contexto personalizados

from contextlib import contextmanager

@contextmanager
def timer(name: str):
    """Gestor de contexto para medir el tiempo de un bloque de código."""
    start = time.perf_counter()
    yield
    elapsed = time.perf_counter() - start
    print(f"{name} tomó {elapsed:.4f} segundos")

# Uso
with timer("procesamiento de datos"):
    process_large_dataset()

Clases de gestores de contexto

class DatabaseTransaction:
    def __init__(self, connection):
        self.connection = connection

    def __enter__(self):
        self.connection.begin_transaction()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.connection.commit()
        else:
            self.connection.rollback()
        return False  # No suprimir excepciones

# Uso
with DatabaseTransaction(conn):
    user = conn.create_user(user_data)
    conn.create_profile(user.id, profile_data)

Comprehensions y generadores

List Comprehensions

# Bueno: List comprehension para transformaciones simples
names = [user.name for user in users if user.is_active]

# Malo: Bucle manual
names = []
for user in users:
    if user.is_active:
        names.append(user.name)

# Las comprehensions complejas deben expandirse
# Malo: Demasiado complejo
result = [x * 2 for x in items if x > 0 if x % 2 == 0]

# Bueno: Usar una función generadora
def filter_and_transform(items: Iterable[int]) -> list[int]:
    result = []
    for x in items:
        if x > 0 and x % 2 == 0:
            result.append(x * 2)
    return result

Expresiones generadoras

# Bueno: Generador para evaluación perezosa
total = sum(x * x for x in range(1_000_000))

# Malo: Crea una lista intermedia grande
total = sum([x * x for x in range(1_000_000)])

Funciones generadoras

def read_large_file(path: str) -> Iterator[str]:
    """Lee un archivo grande línea por línea."""
    with open(path) as f:
        for line in f:
            yield line.strip()

# Uso
for line in read_large_file("huge.txt"):
    process(line)

Clases de datos y tuplas con nombre

Clases de datos

from dataclasses import dataclass, field
from datetime import datetime

@dataclass
class User:
    """Entidad User con __init__, __repr__ y __eq__ automáticos."""
    id: str
    name: str
    email: str
    created_at: datetime = field(default_factory=datetime.now)
    is_active: bool = True

# Uso
user = User(
    id="123",
    name="Alice",
    email="[email protected]"
)

Clases de datos con validación

@dataclass
class User:
    email: str
    age: int

    def __post_init__(self):
        # Validar formato del email
        if "@" not in self.email:
            raise ValueError(f"Email inválido: {self.email}")
        # Validar rango de edad
        if self.age < 0 or self.age > 150:
            raise ValueError(f"Edad inválida: {self.age}")

Tuplas con nombre

from typing import NamedTuple

class Point(NamedTuple):
    """Punto 2D inmutable."""
    x: float
    y: float

    def distance(self, other: 'Point') -> float:
        return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5

# Uso
p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance(p2))  # 5.0

Decoradores

Decoradores de funciones

import functools
import time

def timer(func: Callable) -> Callable:
    """Decorador para medir el tiempo de ejecución de la función."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} tomó {elapsed:.4f}s")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)

# slow_function() imprime: slow_function tomó 1.0012s

Decoradores parametrizados

def repeat(times: int):
    """Decorador para repetir una función varias veces."""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                results.append(func(*args, **kwargs))
            return results
        return wrapper
    return decorator

@repeat(times=3)
def greet(name: str) -> str:
    return f"¡Hola, {name}!"

# greet("Alice") devuelve ["¡Hola, Alice!", "¡Hola, Alice!", "¡Hola, Alice!"]

Decoradores basados en clases

class CountCalls:
    """Decorador que cuenta cuántas veces se llama a una función."""
    def __init__(self, func: Callable):
        functools.update_wrapper(self, func)
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} ha sido llamada {self.count} veces")
        return self.func(*args, **kwargs)

@CountCalls
def process():
    pass

# Cada llamada a process() imprime el conteo de llamadas

Patrones de concurrencia

Hilos para tareas vinculadas a E/S

import concurrent.futures
import threading

def fetch_url(url: str) -> str:
    """Obtiene una URL (operación vinculada a E/S)."""
    import urllib.request
    with urllib.request.urlopen(url) as response:
        return response.read().decode()

def fetch_all_urls(urls: list[str]) -> dict[str, str]:
    """Obtiene múltiples URLs concurrentemente usando hilos."""
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        future_to_url = {executor.submit(fetch_url, url): url for url in urls}
        results = {}
        for future in concurrent.futures.as_completed(future_to_url):
            url = future_to_url[future]
            try:
                results[url] = future.result()
            except Exception as e:
                results[url] = f"Error: {e}"
    return results

Multiprocesamiento para tareas vinculadas a CPU

def process_data(data: list[int]) -> int:
    """Cómputo intensivo de CPU."""
    return sum(x ** 2 for x in data)

def process_all(datasets: list[list[int]]) -> list[int]:
    """Procesa múltiples conjuntos de datos usando múltiples procesos."""
    with concurrent.futures.ProcessPoolExecutor() as executor:
        results = list(executor.map(process_data, datasets))
    return results

Async/Await para E/S concurrente

import asyncio

async def fetch_async(url: str) -> str:
    """Obtiene una URL de forma asíncrona."""
    import aiohttp
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def fetch_all(urls: list[str]) -> dict[str, str]:
    """Obtiene múltiples URLs concurrentemente."""
    tasks = [fetch_async(url) for url in urls]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    return dict(zip(urls, results))

Organización de paquetes

Estructura de proyecto estándar

myproject/
├── src/
│   └── mypackage/
│       ├── __init__.py
│       ├── main.py
│       ├── api/
│       │   ├── __init__.py
│       │   └── routes.py
│       ├── models/
│       │   ├── __init__.py
│       │   └── user.py
│       └── utils/
│           ├── __init__.py
│           └── helpers.py
├── tests/
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_api.py
│   └── test_models.py
├── pyproject.toml
├── README.md
└── .gitignore

Convenciones de importación

# Bueno: Orden de importación - stdlib, terceros, local
import os
import sys
from pathlib import Path

import requests
from fastapi import FastAPI

from mypackage.models import User
from mypackage.utils import format_name

# Bueno: Usar isort para ordenar importaciones automáticamente
# pip install isort

init.py para exportaciones de paquetes

# mypackage/__init__.py
"""mypackage - Un paquete Python de ejemplo."""

__version__ = "1.0.0"

# Exportar clases/funciones principales a nivel de paquete
from mypackage.models import User, Post
from mypackage.utils import format_name

__all__ = ["User", "Post", "format_name"]

Memoria y rendimiento

Usar slots para eficiencia de memoria

# Malo: La clase normal usa __dict__ (más memoria)
class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

# Bueno: __slots__ reduce el uso de memoria
class Point:
    __slots__ = ['x', 'y']

    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

Generador para datos grandes

# Malo: Devuelve una lista completa en memoria
def read_lines(path: str) -> list[str]:
    with open(path) as f:
        return [line.strip() for line in f]

# Bueno: Produce líneas una a la vez
def read_lines(path: str) -> Iterator[str]:
    with open(path) as f:
        for line in f:
            yield line.strip()

Evitar la concatenación de cadenas en bucles

# Malo: O(n²) debido a la inmutabilidad de las cadenas
result = ""
for item in items:
    result += str(item)

# Bueno: O(n) usando join
result = "".join(str(item) for item in items)

# Bueno: Usar StringIO para construir
from io import StringIO

buffer = StringIO()
for item in items:
    buffer.write(str(item))
result = buffer.getvalue()

Integración de herramientas Python

Comandos esenciales

# Formateo de código
black .
isort .

# Linting
ruff check .

# Verificación de tipos
mypy .

# Pruebas
pytest -v

# Construcción y publicación
python -m build
twine upload dist/*

Configuración de pyproject.toml

[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "mypackage"
version = "1.0.0"
description = "Un paquete Python de ejemplo"
readme = "README.md"
requires-python = ">=3.9"
license = {text = "MIT"}
authors = [
    {name = "Tú", email = "[email protected]"}
]

[project.optional-dependencies]
dev = [
    "black",
    "isort",
    "ruff",
    "mypy",
    "pytest",
    "pytest-cov",
]

[tool.black]
line-length = 88
target-version = ['py39']

[tool.isort]
profile = "black"
line_length = 88

[tool.ruff]
line-length = 88
target-version = "py39"
select = ["E", "F", "I", "N", "W"]

[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true

[tool.pytest.ini_options]
minversion = "7.0"
testpaths = ["tests"]

Mejores prácticas de pruebas

Escribir pruebas

import pytest
from mypackage.models import User

class TestUser:
    def test_create_user(self):
        user = User("1", "Alice", "[email protected]")
        assert user.name == "Alice"
        assert user.email == "[email protected]"

    def test_user_defaults(self):
        user = User("1", "Alice", "[email protected]")
        assert user.is_active is True
        assert user.created_at is not None

    def test_inactive_user(self):
        user = User("1", "Bob", "[email protected]", is_active=False)
        assert user.is_active is False

Pruebas parametrizadas

@pytest.mark.parametrize("email,expected", [
    ("[email protected]", True),
    ("[email protected]", True),
    ("invalido", False),
    ("@ejemplo.com", False),
    ("user@", False),
])
def test_email_validation(email, expected):
    assert is_valid_email(email) == expected

Fixtures

@pytest.fixture
def sample_users():
    """Crea usuarios de muestra para pruebas."""
    return [
        User("1", "Alice", "[email protected]"),
        User("2", "Bob", "[email protected]", is_active=False),
        User("3", "Charlie", "[email protected]"),
    ]

def test_get_active_users(sample_users):
    active = [u for u in sample_users if u.is_active]
    assert len(active) == 2
    assert all(u.is_active for u in active)

Pruebas de excepciones

def test_invalid_age():
    with pytest.raises(ValueError, match="Edad inválida: -1"):
        User(email="[email protected]", age=-1)

    with pytest.raises(ValueError, match="Edad inválida: 151"):
        User(email="[email protected]", age=151)

    # La edad válida no debe lanzar excepción
    user = User(email="[email protected]", age=25)
    assert user.age == 25

Referencia de estilo PEP 8

Convenciones de nomenclatura

  • NombreDeClase: Utiliza CamelCase para nombres de clases
  • nombre_de_funcion: Utiliza snake_case para funciones y métodos
  • nombre_de_variable: Utiliza snake_case para variables
  • CONSTANTE: Utiliza MAYÚSCULAS para constantes
  • _privado: Prefijo de guion bajo para miembros privados
  • __mangling: Prefijo de doble guion bajo para name mangling

Límites de longitud de línea

# PEP 8 recomienda 79 caracteres para código
# 72 caracteres para docstrings/comentarios
# black por defecto usa 88

# Preferir continuaciones de línea implícitas
def funcion_con_parametros_muy_largos(
    parametro_uno: str,
    parametro_dos: int,
    parametro_tres: float,
    parametro_cuatro: bool = False,
) -> dict[str, Any]:
    pass

Espacios en blanco

# Bueno: Espacios consistentes
def add(a: int, b: int) -> int:
    return a + b

result = add(3, 5)
items = [1, 2, 3]
data = {"key": "value"}

# Malo: Espacios inconsistentes
def add(a:int,b:int)->int:
    return a+b

result = add( 3 ,5 )
items = [1,2,3]
data = {"key":"value"}

Docstrings

def complex_function(param1: str, param2: int) -> bool:
    """
    Describe qué hace esta función en una línea.

    Proporciona una descripción más detallada si es necesario.

    Args:
        param1: Descripción del primer parámetro.
        param2: Descripción del segundo parámetro.

    Returns:
        Descripción del valor de retorno.

    Raises:
        ValueError: Si algún parámetro no es válido.
    """
    if not param1:
        raise ValueError("param1 no debe estar vacío")
    return param2 > 0

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Skills relacionados