DEV Community

Rost
Rost

Posted on • Originally published at glukhov.org

Python Linters: A Guide for Clean Code

Python linters are essential tools that analyze your code for errors, style issues, and potential bugs without executing it.
They enforce coding standards, improve readability, and help teams maintain high-quality codebases.

What is a Python Linter?

A linter is a static code analysis tool that examines your source code without running it. The term originated from the Unix utility "lint" which analyzed C code. Python linters scan your codebase to identify:

  • Syntax errors and potential runtime bugs
  • Code style violations (PEP 8 compliance)
  • Code smells and anti-patterns
  • Security vulnerabilities
  • Unused imports and variables
  • Complex code that needs refactoring

Using linters helps catch bugs early in development, enforces coding standards across teams, and improves code readability. This ultimately saves time during code reviews and debugging sessions. If you're new to Python or need a quick reference for syntax and best practices, check out our Python Cheatsheet for a comprehensive overview.

Popular Python Linters in 2025

Ruff: The Speed Champion

Ruff has emerged as the fastest Python linter, written in Rust and offering 10-100x speed improvements over traditional tools. It can check large codebases in milliseconds and replaces multiple tools:

# Install Ruff
pip install ruff

# Run linting
ruff check .

# Auto-fix issues
ruff check --fix .

# Format code
ruff format .
Enter fullscreen mode Exit fullscreen mode

Ruff combines functionality from Flake8, isort, pyupgrade, and numerous Flake8 plugins into a single performant package. Its configuration uses pyproject.toml:

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

[tool.ruff.lint]
select = ["E", "F", "I", "N", "W"]
ignore = ["E501"]
Enter fullscreen mode Exit fullscreen mode

Which Python linter is the fastest in 2025? Ruff definitively takes this crown, revolutionizing how developers approach code quality with its exceptional performance.

Pylint: The Comprehensive Analyzer

Pylint is a mature, feature-rich linter that provides detailed reports on code quality. It checks for PEP 8 compliance, detects code smells, and generates quality scores:

# Install Pylint
pip install pylint

# Analyze a file
pylint myfile.py

# Generate reports
pylint --output-format=json myfile.py > report.json
Enter fullscreen mode Exit fullscreen mode

Pylint is highly configurable through .pylintrc or pyproject.toml. While slower than Ruff, it offers more detailed analysis and customizable rule sets that can be tailored to specific project needs.

Flake8: The Classic Choice

Flake8 wraps PyFlakes, pycodestyle, and McCabe complexity checker into one tool. It's lightweight and has a rich plugin ecosystem:

# Install Flake8
pip install flake8

# Check code
flake8 myproject/

# With plugins
pip install flake8-docstrings flake8-bugbear
flake8 --doctests myproject/
Enter fullscreen mode Exit fullscreen mode

Configuration via .flake8, setup.cfg, or tox.ini:

[flake8]
max-line-length = 88
exclude = .git,__pycache__,venv
ignore = E203,W503
Enter fullscreen mode Exit fullscreen mode

Flake8 remains popular due to its extensive plugin system, though many teams are migrating to Ruff for its speed advantages.

Pyflakes: The Minimalist

Pyflakes focuses solely on logical errors without enforcing style. It's extremely fast and produces minimal false positives:

pip install pyflakes
pyflakes myproject/
Enter fullscreen mode Exit fullscreen mode

Pyflakes is ideal for quick checks and CI pipelines where you want to catch errors without style enforcement overhead.

Type Checking with mypy

Should I use type hints and mypy in Python projects? Absolutely - type checking has become a standard practice in professional Python development, catching type-related bugs before runtime.

mypy is a static type checker that analyzes type hints:

# Example with type hints
def calculate_total(prices: list[float], tax_rate: float) -> float:
    subtotal = sum(prices)
    return subtotal * (1 + tax_rate)

# mypy catches type errors
result: int = calculate_total([10.0, 20.0], 0.1)  # Error: incompatible types
Enter fullscreen mode Exit fullscreen mode

Install and run mypy:

pip install mypy
mypy myproject/
Enter fullscreen mode Exit fullscreen mode

Configuration in myproject.toml:

[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
Enter fullscreen mode Exit fullscreen mode

Type hints improve IDE autocomplete, enable better refactoring, and serve as inline documentation. Modern Python projects should embrace type checking from the start. For an advanced example of using Python with type constraints, see our guide on Constraining LLMs with Structured Output: Ollama, Qwen3 & Python or Go.

Code Formatters: Linters' Companions

What is the difference between linters and formatters? Linters analyze and report issues without modifying files, while formatters automatically restructure code to match style guidelines.

Black: The Uncompromising Formatter

Black is an opinionated code formatter that eliminates style debates:

pip install black
black myproject/
Enter fullscreen mode Exit fullscreen mode

Black's philosophy is "any color you want, as long as it's black" - minimal configuration, maximum consistency.

isort: Import Statement Organizer

isort sorts and formats import statements:

pip install isort
isort myproject/
Enter fullscreen mode Exit fullscreen mode

Configuration:

[tool.isort]
profile = "black"
line_length = 88
Enter fullscreen mode Exit fullscreen mode

Note: Ruff includes import sorting functionality, potentially eliminating the need for a separate isort installation.

Integrating Linters into Your Workflow

Pre-commit Hooks

What are pre-commit hooks and how do they help with linting? Pre-commit hooks automatically run checks before commits, catching issues locally before they reach the repository.

Install the pre-commit framework:

pip install pre-commit
Enter fullscreen mode Exit fullscreen mode

Create .pre-commit-config.yaml:

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

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.7.1
    hooks:
      - id: mypy
        additional_dependencies: [types-requests]
Enter fullscreen mode Exit fullscreen mode

Install hooks:

pre-commit install
Enter fullscreen mode Exit fullscreen mode

Now linters run automatically on every commit, providing immediate feedback and preventing broken code from entering your repository.

VS Code Integration

Configure linting in VS Code settings:

{
  "python.linting.enabled": true,
  "python.linting.ruffEnabled": true,
  "python.linting.mypyEnabled": true,
  "python.formatting.provider": "black",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.organizeImports": true
  }
}
Enter fullscreen mode Exit fullscreen mode

This setup provides real-time feedback as you type, highlighting issues immediately.

CI/CD Integration

How do I integrate linters into my CI/CD pipeline? Add linting steps that run before tests and fail the build if critical issues are found.

GitHub Actions example:

name: Lint

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: |
          pip install ruff mypy
      - name: Run Ruff
        run: ruff check .
      - name: Run mypy
        run: mypy .
Enter fullscreen mode Exit fullscreen mode

This ensures all code merged into your main branch meets quality standards. For a real-world example of Python deployment with CI/CD best practices, see our guide on Building a Dual-Mode AWS Lambda with Python and Terraform.

Configuring Multiple Linters

How do I configure multiple linters to work together? Use a unified configuration file and ensure rules don't conflict.

Modern Python projects typically use pyproject.toml:

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

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

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

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

[tool.isort]
profile = "black"
Enter fullscreen mode Exit fullscreen mode

Each tool focuses on different aspects:

  • Ruff/Flake8: Style and common errors
  • mypy: Type checking
  • Black: Code formatting

Ruff vs Traditional Tools

Can I use Ruff as a complete replacement for Flake8 and other tools? For most projects, yes - Ruff can replace Flake8, isort, pyupgrade, and many plugins with significantly better performance.

Ruff advantages:

  • 10-100x faster than traditional tools
  • Single installation for multiple checks
  • Active development and modern features
  • Built-in auto-fix capabilities
  • Growing plugin ecosystem

When to keep traditional tools:

  • Projects with highly customized Pylint rules
  • Teams preferring Black's specific formatting choices
  • Legacy codebases with extensive custom configurations

Most new projects should start with Ruff, adding mypy for type checking. This combination provides comprehensive coverage with excellent performance.

Best Practices

  1. Start early: Introduce linters at project inception, not after thousands of lines exist
  2. Automate everything: Use pre-commit hooks and CI/CD integration
  3. Fix gradually: For existing projects, use # noqa comments strategically while fixing issues incrementally
  4. Customize thoughtfully: Start with defaults, customize only when needed
  5. Document decisions: Maintain a style guide explaining why certain rules are disabled
  6. Keep updated: Linters evolve - review configurations periodically
  7. Combine tools: Use linters for analysis, formatters for style, type checkers for correctness

Common Pitfalls and Solutions

Ignoring too many rules: Don't disable rules without understanding why they exist. If a rule consistently causes friction, discuss with your team before disabling it.

Conflicting configurations: When using multiple tools, ensure line length and formatting rules align. Use Black-compatible settings for other tools.

Performance issues: If linting is slow, consider switching to Ruff or limit scope to changed files in CI.

Type checking overhead: Start with basic mypy configuration, gradually increase strictness. Don't enable strict = true immediately on existing codebases.

Practical Examples

Setting Up a New Project

# Create project structure
mkdir myproject && cd myproject
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dev dependencies
pip install ruff mypy pre-commit black
# Or use uv for faster package management - see our guide on uv

# Initialize pre-commit
pre-commit install

# Create configuration
cat > pyproject.toml << EOF
[tool.ruff]
line-length = 88
target-version = "py311"

[tool.mypy]
python_version = "3.11"
warn_return_any = true
EOF

# Run initial check
ruff check .
mypy .
Enter fullscreen mode Exit fullscreen mode

For modern Python package and project management, consider using uv - New Python Package, Project, and Environment Manager, which offers significantly faster dependency resolution and installation compared to traditional pip.

Fixing Common Issues

# Before linting
import os, sys
from typing import List

def processData(data:List[int]):
    result=[]
    for i in data:
        if i>0:
            result.append(i*2)
    return result

# After linting and formatting
import os
import sys

def process_data(data: list[int]) -> list[int]:
    """Process positive integers by doubling them."""
    result = []
    for item in data:
        if item > 0:
            result.append(item * 2)
    return result
Enter fullscreen mode Exit fullscreen mode

Useful Links

Conclusion

Python linters are indispensable tools for modern software development. They catch bugs early, enforce standards, and improve code quality across teams. With tools like Ruff offering exceptional performance and mypy providing robust type checking, there's never been a better time to integrate linting into your workflow.

Start with Ruff and mypy for new projects, configure pre-commit hooks for automatic checks, and integrate linting into your CI/CD pipeline. Your future self - and your teammates - will thank you for the cleaner, more maintainable codebase.

Top comments (0)