惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

雷峰网
雷峰网
WordPress大学
WordPress大学
MyScale Blog
MyScale Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
The Blog of Author Tim Ferriss
U
Unit 42
罗磊的独立博客
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
aimingoo的专栏
aimingoo的专栏
Vercel News
Vercel News
N
Netflix TechBlog - Medium
H
Hackread – Cybersecurity News, Data Breaches, AI and More
云风的 BLOG
云风的 BLOG
Hugging Face - Blog
Hugging Face - Blog
大猫的无限游戏
大猫的无限游戏
F
Fortinet All Blogs
博客园 - 聂微东
Stack Overflow Blog
Stack Overflow Blog
小众软件
小众软件
博客园 - 【当耐特】
H
Help Net Security
The GitHub Blog
The GitHub Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Your Python Pre-commit Hook Took 18 Seconds. Ruff Does It...
Shayan Holakouee · 2026-06-03 · via DEV Community

Here is a pre-commit hook I used to have:

black .
isort .
flake8 .
pylint src/

Enter fullscreen mode Exit fullscreen mode

Four tools. Four configs. Four things that could break. And on a mid-sized project, about 18 seconds of waiting every time I committed.

Now I have this:

ruff check . --fix
ruff format .

Enter fullscreen mode Exit fullscreen mode

Two commands. One tool. 0.3 seconds. Same coverage, fewer headaches.

That tool is Ruff, and if you're still running the old stack, this article will convince you to switch.


What Is Ruff?

Ruff is a Python linter and formatter written in Rust. It was built by Astral, the same team behind uv. The pitch is simple: replace Flake8, Black, isort, pyupgrade, and autoflake with one binary that runs 10 to 100 times faster than any of them individually.

It's not a wrapper around those tools. Ruff re-implements their rules directly in Rust, which is why it's so fast and why it has no Python dependencies at all.

As of 2025, Ruff ships with over 900 lint rules. It's used in production by NumPy, Pandas, FastAPI, Airflow, and thousands of other projects.


Installing Ruff

# Via pip
pip install ruff

# Via uv (recommended)
uv add --dev ruff

# Standalone binary (no Python required)
curl -LsSf https://astral.sh/ruff/install.sh | sh

Enter fullscreen mode Exit fullscreen mode

Check it works:

ruff --version
# ruff 0.x.x

Enter fullscreen mode Exit fullscreen mode


The Two Commands You'll Use Every Day

Ruff has two main modes: check (linting) and format (formatting).

# Lint your code
ruff check .

# Lint and auto-fix what can be fixed
ruff check . --fix

# Format your code (Black-compatible)
ruff format .

# Check formatting without changing files
ruff format . --check

Enter fullscreen mode Exit fullscreen mode

That's the whole surface area for day-to-day use. Everything else is configuration.


What Does It Actually Catch?

Let's take a messy file:

import os
import sys
import json

def greet(name):
    x = 42
    if name == None:
        return "nobody"
    msg = "Hello, %s" % name
    return msg

Enter fullscreen mode Exit fullscreen mode

Run ruff check messy.py:

messy.py:3:1: F401 `json` imported but unused
messy.py:6:5: F841 Local variable `x` is assigned to but never used
messy.py:7:12: E711 Comparison to `None` (use `is None`)
messy.py:8:11: UP031 Use f-string instead of `%`-formatting
Found 4 errors.

Enter fullscreen mode Exit fullscreen mode

Now run ruff check messy.py --fix and look at the result:

import os
import sys


def greet(name):
    if name is None:
        return "nobody"
    msg = f"Hello, {name}"
    return msg

Enter fullscreen mode Exit fullscreen mode

Unused import removed. == None fixed to is None. %-formatting upgraded to an f-string. All automatically.

The x = 42 unused variable is flagged but not auto-fixed because removing a variable assignment could theoretically change behavior in edge cases. Ruff is conservative about what it touches automatically.


Replacing isort

Import sorting is built in. No separate tool, no separate config.

# Before
import sys
import os
from collections import defaultdict
import json
from typing import Optional

Enter fullscreen mode Exit fullscreen mode

# After ruff check . --fix
import json
import os
import sys
from collections import defaultdict
from typing import Optional

Enter fullscreen mode Exit fullscreen mode

Standard library, third-party, and local imports get grouped and sorted correctly. Same behavior as isort, no extra setup.

Enable it explicitly in your config if it's not running by default:

[tool.ruff.lint]
select = ["I"]  # isort rules

Enter fullscreen mode Exit fullscreen mode


Configuration in pyproject.toml

Ruff reads from pyproject.toml, which means no separate .flake8, .isort.cfg, or setup.cfg. One file rules everything.

A solid starting config:

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

[tool.ruff.lint]
select = [
    "E",    # pycodestyle errors
    "W",    # pycodestyle warnings
    "F",    # pyflakes
    "I",    # isort
    "B",    # flake8-bugbear
    "UP",   # pyupgrade
    "RUF",  # ruff-specific rules
]
ignore = [
    "E501",  # line too long (handled by formatter)
]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

Enter fullscreen mode Exit fullscreen mode

The select field is where you pick which rule categories to enable. By default Ruff only runs E and F rules, which is already solid coverage. Adding B (bugbear) and UP (pyupgrade) gets you a lot more value with very little noise.


Rule Categories Worth Knowing

Ruff organizes its 900+ rules into prefixed categories. Here are the ones worth enabling on most projects:

Prefix Origin What It Catches
F Pyflakes Unused imports, undefined names, redefined vars
E / W pycodestyle PEP 8 style issues
I isort Import ordering
B flake8-bugbear Common bugs and design issues
UP pyupgrade Outdated syntax (pre-f-strings, old union types)
N pep8-naming Naming conventions
RUF Ruff-native Rules with no upstream equivalent
SIM flake8-simplify Code that can be simplified
ANN flake8-annotations Missing type annotations

You can look up any rule at docs.astral.sh/ruff/rules. Each one has an explanation and an example.


Ignoring Rules Inline

Sometimes you genuinely need to break a rule. The syntax is the same as Flake8:

import os  # noqa: F401

Enter fullscreen mode Exit fullscreen mode

Or disable for an entire block:

# ruff: noqa: E501
some_very_long_string = "this line is intentionally long because it has to be"

Enter fullscreen mode Exit fullscreen mode

Or skip formatting for a section:

# fmt: off
matrix = [
    1, 0, 0,
    0, 1, 0,
    0, 0, 1,
]
# fmt: on

Enter fullscreen mode Exit fullscreen mode


Editor Integration

VS Code

Install the Ruff extension and add this to your settings:

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "charliermarsh.ruff",
  "[python]": {
    "editor.codeActionsOnSave": {
      "source.fixAll.ruff": "explicit",
      "source.organizeImports.ruff": "explicit"
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Format on save, import sort on save, lint feedback in the gutter. Done.

Neovim

Via conform.nvim and nvim-lint, or through the LSP with nvim-lspconfig. The Ruff LSP server (ruff server) ships with the Ruff binary.


Pre-commit Integration

This is where Ruff's speed makes the biggest practical difference. Replace your old hooks with:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.9.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

Enter fullscreen mode Exit fullscreen mode

Two hooks, no Python dependencies, sub-second feedback. Your teammates will stop disabling pre-commit.


Migrating From the Old Stack

If you're coming from Black + isort + Flake8, the migration path is low-friction.

Ruff's formatter is designed to be Black-compatible, so your diffs should be minimal after switching. Start by running both and comparing output:

black --check .
ruff format --check .

Enter fullscreen mode Exit fullscreen mode

For linting, start conservative: just enable E, F, and I. Fix the violations, then expand select to add more rule categories as your team is ready.

You can also silence existing violations in bulk without fixing them using # noqa comments or per-file-ignores, which lets you enforce Ruff on new code while leaving legacy files alone.


One Honest Caveat

Ruff does not do type checking. It does not replace mypy or pyright. It can catch some type-annotation issues (via the ANN rules) and enforce annotation style, but it does not perform type inference.

If you want static type checking, you still need mypy or pyright alongside Ruff. They solve different problems.


The Bottom Line

Ruff is not an incremental improvement on Python linting. It's a full reset: one tool, one config file, no dependency conflicts, no wrapper scripts, and feedback fast enough that you stop dreading the pre-commit hook.

If you're starting a new project, add it as a dev dependency on day one and configure it in pyproject.toml. If you're on an existing project, swap in the formatter first since it's Black-compatible and then layer in linting rules from there.

The Python ecosystem moved slowly on tooling for a long time. Ruff is what it looks like when someone decides to stop accepting that.


Enjoyed this? I wrote a similar piece on uv, the package manager from the same team, if you want to go further down the Astral rabbit hole. Drop a reaction or share it with someone still waiting 18 seconds for their pre-commit to finish.