Ruff en 2026: el linter y formatter de Python que sustituyó a flake8 y black

Si llevas tiempo trabajando con Python, seguro que conoces bien el ritual de arrancar un proyecto nuevo: instalar flake8, añadir black para el formateo, isort para los imports, pyupgrade para modernizar el código, y luego varios plugins de flake8 como flake8-bugbear o flake8-simplify. Cada uno con su propia versión, su propio fichero de configuración o su sección en setup.cfg, y su propio proceso que ejecutar.

El resultado era una cadena de herramientas que funcionaba, pero con coste: en proyectos medianos flake8 tardaba varios segundos, y en proyectos grandes podía irse al minuto o más. Pre-commit se volvía lento, el CI se resentía, y acababas pasando un rato cada vez que alguien actualizaba una de las herramientas y rompía compatibilidad con otra.

Ruff resuelve exactamente eso. Es un linter y formatter escrito en Rust por Astral, la misma empresa que hace uv. Un solo binario que reemplaza flake8, black, isort, pyupgrade, y decenas de plugins. Y lo hace entre 10 y 100 veces más rápido que las alternativas en Python.

Instalación y primeros pasos

La instalación es directa:

# Con pip
pip install ruff

# Con uv (recomendado si ya usas uv)
uv add ruff --dev

Los comandos que vas a usar el 90% del tiempo son cuatro:

# Linta todos los ficheros del directorio actual
ruff check .

# Linta y corrige automáticamente lo que puede arreglar
ruff check --fix .

# Formatea el código (equivalente a black)
ruff format .

# Verifica el formato sin modificar nada (para CI)
ruff format --check .

La primera vez que ejecutas ruff check . en un proyecto heredado, es posible que salgan bastantes avisos. Normal: muchas reglas que antes requerían plugins separados ahora las comprueba Ruff de serie. Puedes ir activando grupos de reglas poco a poco, que es la forma más cómoda de migrar sin que todo explote a la vez.

Configuración en pyproject.toml

Ruff lee su configuración desde pyproject.toml (o desde ruff.toml si prefieres un fichero separado). Un ejemplo habitual para un proyecto Python 3.11+:

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

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
ignore = []

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101"]   # allow assert in tests
"scripts/*.py" = ["T201"]    # allow print() in scripts

La clave está en select: ahí decides qué grupos de reglas activas. Las más habituales son:

  • E / F: pycodestyle y pyflakes, activas por defecto. Errores de estilo básicos e imports sin usar.
  • I: isort. Ordenación de imports según PEP 8.
  • UP: pyupgrade. Moderniza el código a la versión de Python que hayas definido en target-version. Por ejemplo, convierte Optional[X] en X | None automáticamente.
  • B: flake8-bugbear. Detecta patrones que pueden ser bugs: argumentos mutables por defecto, comparaciones con True/False, bucles con rangos innecesarios.
  • S: bandit (parcial). Comprobaciones de seguridad básicas.
  • RUF: reglas propias de Ruff, como detectar comentarios # noqa sin razón (RUF100).

Puedes ver la lista completa de reglas disponibles en docs.astral.sh/ruff/rules. Son más de 800 a día de hoy.

Las reglas que más valor aportan

De toda la colección, estas son las que más problemas reales detectan en el día a día:

  • F401: import no usado. Uno de los avisos más frecuentes al limpiar código.
  • B006: argumento mutable por defecto en una función. El clásico bug de Python que nadie ve hasta que explota en producción. Por ejemplo, def foo(items=[]): comparte la misma lista entre todas las llamadas.
  • UP007: Optional[X] ? X | None. Si apuntas a Python 3.10+, este moderniza los type hints automáticamente con --fix.
  • I001: imports desordenados. Ruff los reordena solo, sin necesidad de isort independiente.
  • E501: línea demasiado larga. Aunque si usas ruff format, esto lo soluciona en el formateo directamente.
  • RUF100: comentario # noqa que no silencia nada. Señal de que en algún momento se arregló el código pero nadie limpió el comentario.

Ruff como formatter: diferencias con black

Ruff format está diseñado para ser compatible con black. El estilo de salida es prácticamente idéntico, así que la migración desde black es transparente: sustituyes black . por ruff format . y el resultado es el mismo.

Las diferencias reales son pocas y menores. La más visible es que Ruff format maneja algunas expresiones con strings de forma ligeramente distinta en casos límite, pero en la práctica no notarás diferencia en código normal.

La ventaja principal es la velocidad: al estar escrito en Rust, Ruff format es significativamente más rápido que black. En proyectos pequeños la diferencia no se nota, pero en repos grandes o en pre-commit con muchos ficheros cambiados, sí se aprecia.

Para proyectos nuevos, la elección es sencilla: usa Ruff desde el principio y olvídate de mantener black e isort por separado. Para proyectos existentes, la migración desde black es casi sin fricción. La desde flake8 requiere revisar qué plugins tenías activos y mapearlos a los grupos de reglas de Ruff equivalentes, pero el equipo de Astral tiene documentada la migración paso a paso.

Integración con pre-commit

Añadir Ruff a pre-commit es sencillo. En tu .pre-commit-config.yaml:

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.9.0   # usa la versión más reciente disponible
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

Con esto, antes de cada commit Ruff linta el código modificado (y corrige lo que puede con --fix) y después formatea. Si hay errores que no puede corregir automáticamente, el commit falla y te muestra exactamente qué hay que arreglar.

Una variante útil en CI es --fix --exit-non-zero-on-fix: aplica las correcciones automáticas pero devuelve código de salida distinto de cero si tuvo que cambiar algo. Así el pipeline falla si había código que no pasaba el linter, aunque Ruff lo haya corregido, forzando a que el desarrollador haga commit del código ya limpio.

Ruff en GitHub Actions

Hay dos formas de usarlo en CI. La más directa:

- name: Lint con Ruff
  run: |
    pip install ruff
    ruff check .
    ruff format --check .

Y la forma con la action oficial de Astral, que es más limpia:

- uses: astral-sh/ruff-action@v3
  with:
    args: 'check .'

- uses: astral-sh/ruff-action@v3
  with:
    args: 'format --check .'

Una de las ventajas de Ruff en CI es que no necesita caché. Herramientas como flake8 a veces requieren caché de dependencias para no tardar demasiado en proyectos grandes. Ruff es tan rápido que en la mayoría de proyectos medianos ni te planteas añadir caché: termina antes de que el tiempo de configuración de la caché hubiera compensado.

Ruff vs flake8 + black en 2026

La adopción de Ruff ha sido muy rápida. Proyectos de referencia en el mundo Python como FastAPI, Pydantic, uv, o el propio CPython para algunos scripts ya lo usan. No es una herramienta experimental: está madura y bien mantenida.

La pregunta práctica que se hace mucha gente es: ¿por qué quedarme con flake8? La respuesta honesta en 2026 es: casi por ninguna razón, salvo que uses un plugin muy específico que Ruff todavía no implemente. Y esa lista de excepciones se reduce con cada release.

Las ventajas de consolidar en Ruff son concretas:

  • Una sola versión que gestionar en pyproject.toml o requirements-dev.txt.
  • Un solo lugar de configuración, sin tener que sincronizar [tool.black], [tool.isort] y [tool.flake8].
  • Sin conflictos entre el orden de imports que quiere isort y el que quiere black.
  • Velocidad real: en proyectos con miles de ficheros la diferencia es notable.

Si tienes un proyecto nuevo en 2026, usar Ruff desde el principio es la opción más sensata. Si tienes un proyecto con flake8 y black, merece la pena planificar la migración: no es urgente, pero cada mes que pasa es tiempo gestionando dos herramientas donde podría bastar una.

Para saber más sobre herramientas de calidad de código en Python puedes consultar Python moderno: herramientas de calidad de código en el CLI y calidad de código Python: del linting al profiling.

Imagen: Pexels / Daniil Komov

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP