ruff format — замена Black
Автоматическое форматирование кода в стиле Black, только быстрее.
Философия форматирования
Ruff format — это не просто очередной форматтер. Он разработан как drop-in замена Black — самого популярного Python-форматтера. Это означает, что ruff format стремится быть совместимым с Black на 99%+ и может заменить его в существующих проектах без изменения стиля кода.
Ключевые принципы ruff format:
- Детерминированность — один и тот же код всегда форматируется одинаково
- Минимальная конфигурация — почти все решения принимаются за вас
- Совместимость с PEP 8 — форматтер следует официальному руководству по стилю
- Скорость — ruff format работает в 10-100x быстрее Black
- Magic trailing comma — уважает ваше решение о многострочности
| Характеристика | ruff format | Black |
|---|---|---|
| Скорость (1000 файлов) | ~0.1 сек | ~2 сек |
| Язык | Rust | Python |
| Совместимость с Black | ~99% | 100% |
| Интеграция с линтером | Встроенная (один инструмент) | Требуется flake8 + isort |
| Magic trailing comma | Да (по умолчанию) | Да |
| Настройка quote-style | Да (double/single) | Нет (только double) |
Базовое использование
Команда ruff format форматирует Python-файлы на месте. В отличие от ruff check, она молча применяет изменения — вывод появляется только если файлы действительно изменились.
ruff format . # форматировать всё в текущей директории
ruff format src/ # форматировать папку src
ruff format main.py # форматировать один файл
ruff format src/ tests/ # несколько директорий
ruff format src/main.py src/utils.py # несколько файлов
Ruff format обрабатывает файлы рекурсивно, включая все вложенные директории. По умолчанию он форматирует файлы с расширениями .py, .pyi, .pyx, .pyx.in.
Что форматирует ruff format
Ruff format изменяет только пробельные символы (пробелы, отступы, пустые строки) — он никогда не меняет семантику кода. Вот что он делает:
- Отступы (замена табуляции на пробелы)
- Пробелы вокруг операторов и после запятых
- Перенос строк (line wrapping) при превышении line-length
- Пустые строки между функциями и классами
- Пробелы внутри скобок, списков, словарей
- Тип кавычек (если настроен quote-style)
- Magic trailing comma — многострочность коллекций
Пример: базовое форматирование
def foo(x,y,z):
return x+y+z
result=foo(1,2, 3)
def foo(x, y, z):
return x + y + z
result = foo(1, 2, 3)
Пример: перенос длинных строк
result = some_function_with_long_name(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
result = some_function_with_long_name(
arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8
)
Пример: многострочные коллекции
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
items = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
]
Больше примеров до/после
Пример: форматирование словаря
data = {"name": "Alice","age":30,"city": "Paris","job":"engineer"}
data = {
"name": "Alice",
"age": 30,
"city": "Paris",
"job": "engineer",
}
Пример: условные выражения
if x>0 and y<10 or z==5:
print("hello")
elif a!=b:
print("world")
if x > 0 and y < 10 or z == 5:
print("hello")
elif a != b:
print("world")
Пример: вложенные структуры
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
nested = {"key": {"inner": [1, 2, 3]}}
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
nested = {"key": {"inner": [1, 2, 3]}}
Вложенные структуры ruff format оставляет в одну строку, если они помещаются в лимит длины. Это decision от Black: краткость для простых вложенных структур.
Пример: long conditions
if is_authenticated and has_permission and not is_blocked and user.is_active and user.role == "admin":
grant_access()
if (
is_authenticated
and has_permission
and not is_blocked
and user.is_active
and user.role == "admin"
):
grant_access()
--check — проверка без изменений
Флаг --check проверяет, нужно ли форматирование, не изменяя файлы. Если хотя бы один файл требует форматирования, ruff format --check завершается с кодом возврата 1. Это идеально для CI/CD.
ruff format --check . # проверить нужно ли форматирование
# код возврата:
# 0 — все файлы отформатированы
# 1 — есть неотформатированные файлы
# пример для CI (GitHub Actions):
- name: Check formatting
run: ruff format --check .
# если код не отформатирован, CI упадёт
Exit codes ruff format:
| Код | Ситуация |
|---|---|
| 0 | Всё отформатировано (или --check: не требует форматирования) |
| 1 | Некоторые файлы не отформатированы (только с --check) |
| 2 | Ошибка выполнения (неверный синтаксис, проблемы с файлами) |
--diff — предпросмотр изменений
Флаг --diff показывает unified diff всех изменений, которые будут применены, без фактического изменения файлов. Это стандартный способ просмотреть, что изменит форматтер, перед применением.
ruff format --diff . # показать что изменится без применения
ruff format --diff main.py
# пример вывода:
# --- a/src/main.py
# +++ b/src/main.py
# @@ -1,4 +1,5 @@
# -def foo(x,y):
# - return x+y
# +def foo(x, y):
# + return x + y
# +
#
# -result=foo(1,2,3)
# +result = foo(1, 2, 3)
ruff format --check . — если код не отформатирован, сборка упадёт с кодом 1. Локально используйте ruff format --diff . для предпросмотра.
Magic trailing comma
Magic trailing comma (волшебная завершающая запятая) — это поведение, унаследованное от Black. Если в коллекции (списке, словаре, кортеже, наборе аргументов функции) присутствует запятая после последнего элемента, форматтер обязательно разобьёт коллекцию на несколько строк. Это ваш способ явно сказать форматтеру: "я хочу, чтобы это было многострочным".
# С trailing comma — всегда многострочное:
items = [
"one",
"two",
"three", # <-- trailing comma
]
# Без trailing comma — может схлопнуться в одну строку (если помещается):
items = ["one", "two", "three"]
# С trailing comma в аргументах функции:
def foo(
x,
y,
z, # <-- trailing comma
):
pass
Fluent layout (цепочки вызовов)
Ruff format поддерживает стиль форматирования цепочек вызовов (fluent interface / method chaining). Если цепочка не помещается в одну строку, Ruff переформатирует её со смещением оператора:
# До:
result = dataframe.filter(col("age") > 18).groupBy("city").agg(avg("salary")).orderBy("city")
# После ruff format:
result = (
dataframe.filter(col("age") > 18)
.groupBy("city")
.agg(avg("salary"))
.orderBy("city")
)
# До:
query = db.select(User).where(User.age > 18).join(Profile).order_by(User.name).limit(10)
# После ruff format:
query = (
db.select(User)
.where(User.age > 18)
.join(Profile)
.order_by(User.name)
.limit(10)
)
Этот стиль особенно популярен в проектах, использующих SQLAlchemy, Pandas, Django ORM и другие библиотеки с method chaining.
Совместная работа ruff check и ruff format
Важно понимать, что ruff check и ruff format — это две независимые команды. ruff check не вызывает ruff format, и наоборот. Рекомендуется запускать их последовательно:
# полный цикл проверки:
ruff check . # найти проблемы
ruff check --fix . # исправить что можно
ruff format . # отформатировать код
# для CI:
ruff check --select E,F,I,N,UP,B .
ruff format --check .
# pre-commit хуки:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
Отличия ruff format от Black
Хотя ruff format стремится к совместимости с Black, есть несколько отличий:
| Аспект | ruff format | Black |
|---|---|---|
| Форматирование docstring | Может форматировать код внутри docstring | Не форматирует |
| Тип кавычек | Настраиваемый (double/single) | Только double |
| Trailing comma в функциях | Следует Black, небольшие отличия | Эталонное поведение |
| Обработка комментариев | Отличия в edge cases | Эталонное поведение |
| Zebra-striping (чередование строк) | Не поддерживает | Поддерживает (экспериментально) |
| Скорость | 10-100x быстрее | Медленнее |
Практические сценарии
Сценарий 1: Миграция с Black
# Удалить Black из зависимостей
pip uninstall black
# или
poetry remove black
# Установить Ruff (если ещё не установлен)
pip install ruff
# Отформатировать проект
ruff format .
# Проверить, что форматирование совпадает
ruff format --check .
Сценарий 2: CI проверка форматирования
# GitHub Actions
- name: Check formatting
run: ruff format --check .
# Makefile
format-check:
ruff format --check .
format:
ruff format .
Сценарий 3: Pre-commit интеграция
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.0
hooks:
- id: ruff
args: [--fix, --select, I]
- id: ruff-format
Сценарий 4: Форматирование только изменённых файлов
# отформатировать только файлы, изменённые в текущем коммите
ruff format $(git diff --name-only --diff-filter=AM | grep '\.py$')
# или через pre-commit — само применяется к staged файлам
Шпаргалка
| Команда | Назначение |
|---|---|
ruff format . |
Отформатировать всё |
ruff format --check . |
Проверить без изменений (для CI) |
ruff format --diff . |
Предпросмотр изменений |
ruff format main.py |
Форматировать один файл |
Форматирование .pyi, .ipynb и других файлов
Ruff format поддерживает несколько типов файлов помимо обычных .py:
- .py — обычные Python-файлы
- .pyi — stub-файлы (type hints). Форматируются как py, но с некоторыми отличиями (например, не добавляются пустые строки между функциями в некоторых случаях)
- .pyx — Cython-файлы. Ruff обрабатывает только Python-секции, директивы Cython не форматируются
- .ipynb — Jupyter Notebooks. Ruff форматирует код внутри ячеек (требуется
--previewили настройка)
# форматировать все типы файлов
ruff format .
# только stub-файлы
ruff format "*.pyi"
# Jupyter notebooks (нужно указать явно или настроить include)
ruff format notebook.ipynb
include в конфигурации. По умолчанию Ruff форматирует только .py, .pyi, .pyx.
Обработка комментариев
Ruff format старается сохранять комментарии и их позицию относительно кода. Однако есть некоторые правила:
- Комментарии на той же строке, что и код, остаются на месте
- Комментарии между строками кода сохраняются
- Если строка с комментарием переносится, комментарий остаётся с перенесённой частью (если возможно)
- Docstring-комментарии ("""...""") могут быть переформатированы, если включена опция
docstring-code-format
# Комментарии сохраняются:
def foo(): # это комментарий
x = 1 # и этот
# А этот останется между строками
y = 2
return x + y
Производительность ruff format
Одно из главных преимуществ Ruff перед Black — скорость. Ruff написан на Rust и использует параллельную обработку файлов.
| Проект | Файлов | ruff format | Black |
|---|---|---|---|
| CPython | ~3000 | ~0.4 сек | ~8 сек |
| Django | ~2000 | ~0.3 сек | ~6 сек |
| Small project | ~50 | ~0.02 сек | ~0.5 сек |
Форматирование через LSP
Ruff поставляется со встроенным LSP (Language Server Protocol) сервером. Это позволяет редакторам кода форматировать файлы через стандартный протокол LSP.
# Запуск LSP сервера Ruff
ruff server
# В VS Code — установите расширение Ruff
# В Neovim — используйте ruff_lsp
lvim config:
require("lspconfig").ruff.setup({
cmd = { "ruff", "server" },
on_attach = function(client, bufnr)
-- Включить форматирование через LSP
vim.api.nvim_buf_set_option(bufnr, "formatexpr", "v:lsp.formatexpr")
vim.cmd("set formatprg=ruff\\ format\\ -")
end,
})
Использование LSP обеспечивает форматирование в реальном времени, с подсветкой синтаксиса и мгновенным отображением результата.
Отладка форматирования
Если ruff format ведёт себя не так, как ожидалось, вот несколько способов отладки:
- Используйте
--diffчтобы увидеть, какие изменения будут применены - Проверьте
ruff check --show-settingsчтобы убедиться, что настройки правильные - Проверьте версию Ruff:
ruff --version - Если поведение отличается от Black, проверьте документацию по совместимости
# отладка
ruff format --diff main.py | head -50
ruff check --show-settings
ruff --version
Форматирование лямбд и генераторов
Ruff format обрабатывает лямбда-выражения и генераторные выражения особым образом. Лямбды по возможности остаются в одной строке, но если они слишком длинные — переносятся:
add = lambda x,y: x+y
process = lambda x, y, z: some_function(x, y, z) + another_function(x, y, z)
squares = [x*x for x in range(10) if x % 2 == 0]
add = lambda x, y: x + y
process = lambda x, y, z: some_function(x, y, z) + another_function(
x, y, z
)
squares = [x * x for x in range(10) if x % 2 == 0]
Форматирование объявлений функций
Объявления функций с длинными параметрами форматируются особым образом. Каждый параметр может быть на отдельной строке, если они не помещаются:
def process_data(input_file, output_file, format_type, encoding, overwrite=False, backup=True):
pass
def process_data(
input_file,
output_file,
format_type,
encoding,
overwrite=False,
backup=True,
):
pass
Если же все параметры помещаются на одной строке, они остаются на одной строке:
def add(x, y): # помещается — одна строка
return x + y
Форматирование декораторов
Декораторы в Python могут быть многострочными, и ruff format аккуратно их обрабатывает:
# До:
@app.route("/api/v1/users//posts/", methods=["GET", "POST", "DELETE"])
def handle_post(user_id, post_id):
pass
# После ruff format:
@app.route(
"/api/v1/users//posts/",
methods=["GET", "POST", "DELETE"],
)
def handle_post(user_id, post_id):
pass
Best practices по форматированию
- Не боритесь с форматтером — приняв решение использовать ruff format, доверьтесь ему. Не тратьте время на ручное форматирование.
- Используйте trailing comma для контроля многострочности. Добавили запятую — форматтер оставит многострочным.
- Запускайте ruff format перед ruff check — форматирование может устранить некоторые ошибки E и W.
- В CI используйте --check — это гарантирует, что весь код в репозитории отформатирован одинаково.
- Настраивайте pre-commit для автоматического форматирования перед каждым коммитом.
Часто задаваемые вопросы
Q: Чем отличается ruff format от ruff check --fix?
A: ruff format занимается только пробельным форматированием (отступы, переносы, пробелы). ruff check --fix исправляет семантические нарушения (удаление импортов, сортировка, замена конструкций). Это разные инструменты с разными целями.
Q: Может ли ruff format изменить мой код?
A: Нет. Ruff format меняет только пробельные символы: отступы, пробелы, пустые строки, переносы. Он никогда не меняет семантику кода. Это гарантируется дизайном форматтера.
Q: Нужно ли запускать ruff format перед ruff check?
A: Да, рекомендуется. Ruff check может находить ошибки, которые исчезнут после форматирования (например, пробелы). Порядок: ruff format . && ruff check .
Q: Как отформатировать только изменённые файлы?
A: Используйте ruff format $(git diff --name-only --diff-filter=AM | grep '\.py$') или настройте pre-commit.
Q: Поддерживает ли ruff format автозамену одинарных кавычек на двойные?
A: Да, через настройку quote-style. Если указать "double", Ruff будет заменять одинарные кавычки на двойные (и наоборот).
Урок 3.1: Форматирование кода — ruff format
5 вопросов