EasyТеория3 min

PEP 8 и линтеры

Стандарт стиля PEP 8, ruff, black, isort и pre-commit хуки

PEP 8 и линтеры — чистый код на Python

PEP 8 — стандарт стиля

PEP 8 — официальное руководство по стилю кода Python.

Отступы и длина строк

# GOOD: 4 spaces per indentation level
def calculate_total(
    items: list[dict],
    tax_rate: float = 0.20,
) -> float:
    total = sum(item["price"] * item["qty"] for item in items)
    return total * (1 + tax_rate)

# Max 79 characters per line (or 88 for Black)
long_variable = (
    first_value
    + second_value
    - third_value
)

Именование

# Variables and functions: snake_case
user_name = "Алексей"
def get_user_by_id(user_id: int) -> dict: ...

# Constants: UPPER_SNAKE_CASE
MAX_RETRIES = 3
DATABASE_URL = "postgresql://localhost/mydb"

# Classes: PascalCase
class UserService: ...
class HTTPClient: ...  # Abbreviations in caps

# Private: prefix with underscore
class Account:
    def __init__(self) -> None:
        self._balance = 0.0       # Protected by convention
    def _internal_method(self) -> None: ...

Импорты

# Grouped and sorted:
# 1. Standard library
import os
import sys
from datetime import datetime

# 2. Third-party packages
import httpx
from pydantic import BaseModel

# 3. Local imports
from myapp.models import User

# BAD: wildcard imports
# from os import *          # Never!

ruff — быстрый линтер и форматтер

ruff — замена flake8, isort, pyupgrade и black. Написан на Rust, работает в 10-100 раз быстрее.

pip install ruff

ruff check .           # Lint
ruff check --fix .     # Auto-fix
ruff format .          # Format
# pyproject.toml
[tool.ruff]
target-version = "py312"
line-length = 88

[tool.ruff.lint]
select = [
    "E",   # pycodestyle errors
    "W",   # pycodestyle warnings
    "F",   # pyflakes
    "I",   # isort
    "N",   # pep8-naming
    "UP",  # pyupgrade
    "B",   # flake8-bugbear
    "SIM", # flake8-simplify
    "C4",  # flake8-comprehensions
    "RUF", # Ruff-specific rules
]

[tool.ruff.lint.isort]
known-first-party = ["myapp"]

Что обнаруживает ruff:

# F841: unused variable
x = 42  # never used

# E711: comparison to None
if value == None: ...  # Use 'is None'

# SIM108: use ternary
if cond:
    result = "yes"
else:
    result = "no"
# Better: result = "yes" if cond else "no"

# UP035: deprecated import
from typing import List  # Use list (3.9+)

black — форматтер кода

pip install black
black .              # Format
black --check .      # Check without changing

Замечание: ruff format может полностью заменить black.

mypy — проверка типов

pip install mypy
mypy src/
[tool.mypy]
python_version = "3.12"
strict = true
def add(a: int, b: int) -> int:
    return a + b

result: str = add(1, 2)  # mypy error: incompatible types
greet(42)                 # mypy error: expected str

pre-commit — автоматизация проверок

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

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.13.0
    hooks:
      - id: mypy

  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
pre-commit install              # Install hooks
pre-commit run --all-files      # Run manually

Рекомендуемая конфигурация

# pyproject.toml
[tool.ruff]
target-version = "py312"
line-length = 88

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

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

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --strict-markers"

Резюме инструментов

Инструмент Назначение Замена ruff
ruff Линтинг + форматирование -
black Форматирование ruff format
isort Сортировка импортов ruff check --select I
flake8 Линтинг стиля ruff check
mypy Проверка типов Нет замены
pre-commit Автоматизация Нет замены

Проверь себя

Какой стиль именования используется для функций и переменных в Python (PEP 8)?

Чем ruff превосходит flake8 и isort?

Что делает mypy?

Какой минимальный набор инструментов обеспечивает качество Python-кода?