Перейти к содержанию

Стиль кода Black

Black — это соответствующий PEP 8 форматтер с собственным единым стилем.

Сохранять стиль неизменным между релизами всегда было целью, но стиль Black не высечен в камне. Он развивается: учитываются новые возможности Python и иногда отклики пользователей. Крупные решения по стилю из The Black code style меняются крайне редко; отдельные детали могут меняться согласно политике стабильности ниже. Текущие обсуждения стиля помечаются на GitHub меткой style.

Политика стабильности

Для стиля кода Black в непредрелизных версиях действует следующее правило:

Код, отформатированный Black, не изменится при повторном форматировании с теми же опциями в любой другой версии в рамках того же календарного года.

То есть в 2022 году можно спокойно использовать black ~= 22.0 без риска, что смена форматирования поломает проект. Мы по-прежнему можем исправлять баги (например, падения Black на каком-то коде) и вносить улучшения, не затрагивающие форматирование.

В редких случаях мы можем менять форматирование кода, который ранее не обрабатывался Black (например, исправляли баги с удалением комментариев). Такие правки не нарушают политику стабильности.

Первый релиз в новом календарном году может содержать изменения форматирования; мы стараемся свести их к минимуму. Это нужно и из-за новой синтаксической поддержки в Python, и из-за улучшений логики форматирования.

Флаги --preview и --unstable не подпадают под эту политику. Гарантий стабильности вывода при их использовании нет. Они предназначены для экспериментов с предлагаемыми изменениями стиля. К концу года стиль --preview обычно близок к стабильному стилю следующего года, но мы оставляем за собой право вносить изменения.

Документация по текущему и будущему стилю:


Стиль кода Black (текущий)

Стиль кода

Black стремится к единообразию, общности, читаемости и уменьшению diff’ов в git. Похожие конструкции форматируются по похожим правилам. Опций настройки стиля мало, новые добавляются редко. Предыдущее форматирование по возможности не учитывается (исключения вроде «магической» trailing comma редки). Стиль Black можно рассматривать как строгое подмножество PEP 8.

Ниже описан текущий стиль форматирования. Чтобы попробовать направление развития стиля, см. future style и запуск black --preview.

Как Black переносит строки

Black не опирается на существующее форматирование и применяет единообразные горизонтальные и вертикальные отступы. Правила для горизонтальных пробелов можно свести к: делай так, чтобы был доволен pycodestyle.

По вертикали Black старается помещать одно полное выражение или простое утверждение на строку. Если это укладывается в заданную длину строки — отлично.

# вход:

j = [1,
     2,
     3
]

# выход:

j = [1, 2, 3]

Если не укладывается, Black смотрит на содержимое первых внешних совпадающих скобок и выносит его на отдельную строку с отступом.

# вход:

ImportantClass.important_method(exc, limit, lookup_lines, capture_locals, extra_argument)

# выход:

ImportantClass.important_method(
    exc, limit, lookup_lines, capture_locals, extra_argument
)

Если и этого недостаточно, Black разбивает внутреннее выражение по тому же правилу, каждый раз с отступом для пар скобок. Если внутри пары скобок элементы разделены запятыми (список аргументов, литерал dict и т.п.), Black сначала пытается оставить их на одной строке со скобками. Если не получается — выносит каждый элемент на отдельную строку.

# вход:

def very_important_function(template: str, *variables, file: os.PathLike, engine: str, header: bool = True, debug: bool = False):
    """Applies `variables` to the `template` and writes to `file`."""
    with open(file, 'w') as f:
        ...

# выход:

def very_important_function(
    template: str,
    *variables,
    file: os.PathLike,
    engine: str,
    header: bool = True,
    debug: bool = False,
):
    """Applies `variables` to the `template` and writes to `file`."""
    with open(file, "w") as f:
        ...

Если литерал структуры данных (tuple, list, set, dict) или строка импортов from не помещается в заданную длину, он всегда разбивается по одному элементу на строку. Это уменьшает diff’ы и позволяет быстрее найти коммит, добавивший элемент. Так Black остаётся совместимым с isort (профиль black или ручная настройка).

Закрывающие скобки всегда с уменьшенным отступом, в конце добавляется запятая. Так diff при добавлении/удалении элемента ограничивается одной строкой. Закрывающая скобка с меньшим отступом явно отделяет два блока кода на одном уровне отступа (например, список аргументов и docstring выше).

Black предпочитает скобки обратному слэшу и убирает слэши, если находит их.

# вход:

if some_short_rule1 \
  and some_short_rule2:
      ...

# выход:

if some_short_rule1 and some_short_rule2:
  ...


# вход:

if some_long_rule1 \
  and some_long_rule2:
    ...

# выход:

if (
    some_long_rule1
    and some_long_rule2
):
    ...

Обратные слэши и многострочные строки — два места в грамматике Python, где нарушается значимый отступ. В слэшах необходимости нет: они заставляют грамматику принять разрыв строки. Из-за этого код с ними сложнее читать и легко сломать при правках. Поэтому Black от них избавляется. Если тянетесь к слэшу — это сигнал, что небольшой рефакторинг даст более ясный код.

Длина строки

Длина строки по умолчанию — 88 символов (на 10% больше 80). Такой выбор даёт заметно более короткие файлы, чем жёсткие 80 или даже 79 (как в стандартной библиотеке). В целом значение около 90 выглядит разумным.

Можно задать меньшую длину через --line-length. Black будет её по возможности соблюдать, но в редких случаях не сможет без нарушения других правил — тогда отформатированный код может превысить лимит.

Лимит можно и увеличить, но длинные строки (больше 100 символов) усложняют работу людям с нарушениями зрения и ухудшают просмотр diff’ов на типичных разрешениях. Длинные строки неудобны и в документации, и в слайдах.

Flake8 и другие линтеры

См. Using Black with other tools про совместимость с линтерами.

Пустые строки

Black избегает лишних пустых строк — в духе PEP 8, где вертикальные отступы внутри функции рекомендуются использовать умеренно.

Black сохраняет одиночные пустые строки внутри функций и одиночные/двойные на уровне модуля, оставленные автором, кроме случаев внутри выражений в скобках. Такие выражения переформатируются в компактный вид, и лишние пустые строки там исчезают.

# вход:

def function(
    some_argument: int,

    other_argument: int = 5,
) -> EmptyLineInParenWillBeDeleted:



    print("One empty line above me will be kept!")

def this_is_okay_too():
    print("No empty line here")

# выход:

def function(
    some_argument: int,
    other_argument: int = 5,
) -> EmptyLineInParenWillBeDeleted:

    print("One empty line above me will be kept!")


def this_is_okay_too():
    print("No empty line here")

Black вставляет пустые строки до и после определений функций: по одной до и после вложенных функций, по две — до и после функций и классов на уровне модуля. Пустых строк между определением функции/класса и комментарием непосредственно перед ним Black не добавляет.

Между docstring класса и первым полем или методом Black оставляет ровно одну пустую строку (согласно PEP 257).

После docstring функции Black не вставляет пустую строку, если только она не нужна из-за следующей сразу за ней вложенной функции.

Запятые в конце (trailing commas)

Black добавляет запятую в конце в выражениях, разбитых по строкам с запятыми, в том числе в сигнатурах функций.

Исключение — сигнатуры с *, *args или **kwargs: запятая в конце там безопасна только в Python 3.6+. Black проверяет, что файл уже рассчитан на 3.6+ (по наличию f-строк и trailing comma в таких сигнатурах), и тогда ставит запятую. Если хотите запятую в таком месте, а Black не распознал безопасность — поставьте её вручную, Black её сохранит.

Уже стоящая в коде trailing comma указывает Black всегда разбивать содержимое текущей пары скобок по одному элементу на строку. Подробнее в разделе «Прагматизм» ниже.

Строки

Black предпочитает двойные кавычки (" и """) одинарным (' и ''') и заменяет последние на первые, если при этом не увеличивается число экранирований обратным слэшем.

Black также нормализует префиксы строк: символы префикса приводятся к нижнему регистру (кроме заглавной «R» для raw), маркер unicode-литерала u удаляется как бессмысленный в Python 3, при нескольких символах «r» идёт первым («raw f-string»).

В escape-последовательностях Black нормализует регистр: например, "\uabcd" и "\uABCD" приводятся к одному виду; для имён символов \N используется верхний регистр, например "\N{MEETEI MAYEK LETTER HUK}".

Единый вид кавычек уменьшает отвлечение при чтении и позволит в будущем объединять подряд идущие строковые литералы на одной строке (см. #26). Двойные кавычки удобны для апострофов в тексте, соответствуют PEP 257, пустая строка "" не спутаешь с одной кавычкой, и они согласованы с C. На некоторых раскладках одинарные кавычки набирать проще — можно продолжать ими пользоваться и позволить Black их заменить.

В больших проектах с устоявшимися соглашениями (например, «одинарные для данных, двойные для человекочитаемых строк») можно передать --skip-string-normalization. Это помощник при внедрении; в новых проектах им лучше не пользоваться.

Black обрабатывает и docstring’и: исправляет отступы кавычек и текста (относительные отступы внутри текста сохраняются), убирает лишние пробелы в конце строк и лишние переводы в конце docstring, ведущие табы заменяет на пробелы (табы внутри текста сохраняются), у однострочных docstring убирает ведущие и завершающие пробелы.

Числовые литералы

Black приводит большинство числовых литералов к нижнему регистру для синтаксических частей и верхнему для цифр: 0xAB вместо 0XAB, 1e10 вместо 1E10.

Переносы строк и бинарные операторы

При разбиении блока на несколько строк Black переносит строку перед бинарным оператором — в соответствии с PEP 8, где такой вариант рекомендуется для читаемости.

Почти все операторы окружаются одиночными пробелами. Исключения: унарные операторы (+, -, ~) и оператор степени, когда оба операнда «простые». Операнд степени считается простым, если это только NAME, числовая CONSTANT или обращение к атрибуту (цепочка атрибутов допускается), возможно с унарным оператором.

# Пробелов не будет:
a = x**y
b = config.base**5.2
c = config.base**runtime.config.exponent
d = 2**5
e = 2**~5

# Пробелы будут:
f = 2 ** get_exponent()
g = get_x() ** get_y()
h = config['base'] ** 2

Срезы

PEP 8 рекомендует считать : в срезах бинарным оператором с наименьшим приоритетом и оставлять одинаковое количество пробелов с обеих сторон (кроме случая, когда параметр опущен, например ham[1 + 1 :]). Для «простых» выражений (ham[lower:upper]) — без пробелов вокруг :, для «сложных» — с пробелами (ham[lower : upper + offset]). Black считает сложным всё, что сложнее имени переменной (ham[lower : upper + 1]). Для расширенных срезов оба : должны иметь одинаковое количество пробелов, кроме опущенных параметров (ham[1 + 1 ::]). Black применяет эти правила последовательно.

Это может вызывать предупреждения E203 whitespace before ':' в инструментах вроде Flake8. Так как E203 не соответствует PEP 8, в Flake8 эти предупреждения стоит отключить.

Скобки

Часть скобок в грамматике Python опциональна. Любое выражение можно обернуть в скобки. Важные случаи: if (...), while (...), for (...) in (...), assert (...), (...), from X import (...), присваивания вида target = (...), target: type = (...), some, *un, packing = (...), augmented += (...). В таких случаях Black убирает скобки, если всё утверждение помещается в одну строку или если внутреннее выражение не содержит разделителей для дальнейшего разбиения. Если разделитель один и выражение начинается или заканчивается скобкой, скобки тоже можно убрать — пара скобок сама организует выражение. В остальных случаях скобки добавляются.

Black не добавляет и не убирает дополнительные вложенные скобки, которые вы могли поставить для ясности. Например, такие скобки не будут удалены:

return not (this or that)
decision = (maybe.this() and values > 0) or (maybe.that() and values < 0)

Цепочки вызовов

Для API с цепочками вызовов (fluent interface, например ORM) Black трактует точку после вызова или индексации как разделитель очень низкого приоритета. Пример:

def example(session):
    result = (
        session.query(models.Customer.id)
        .filter(
            models.Customer.account_id == account_id,
            models.Customer.email == email_address,
        )
        .order_by(models.Customer.id.asc())
        .all()
    )

Файлы заглушек типов (.pyi)

Файлы заглушек с расширением .pyi описывают типы для внешних модулей. Рекомендуемый стиль для них лаконичнее PEP 8: ... на той же строке, что и сигнатура; без пустых строк между подряд идущими функциями/именами/методами на уровне модуля или внутри класса; одна пустая строка между определениями классов верхнего уровня (или ни одной для очень маленьких классов). Black следует этим правилам. Дополнительные рекомендации (пока не enforced): предпочитать ... вместо pass; не использовать строковые литералы в аннотациях (в .pyi есть forward references); использовать аннотации переменных вместо type comments.

Окончания строк

Black нормализует окончания строк (\n или \r\n) по типу первой строки файла.

Символы перевода страницы (form feed)

Black сохраняет символы form feed на иначе пустых строках на уровне модуля. В группе подряд идущих пустых строк сохраняется только один form feed. При двух пустых строках подряд form feed ставится на второй.

Прагматизм

Ранние версии Black в некоторых вещах были бескомпромиссны. С ростом зрелости инструмента появились исключения. Ниже — какие и почему.

«Магическая» trailing comma

Обычно Black не учитывает существующее форматирование. Но если у вас короткая коллекция или вызов, которые вы планируете расширять, ранние версии Black безжалостно сворачивали их в одну строку. Теперь можно явно указать обратное: поставьте trailing comma в коллекции сами — тогда Black всегда будет выносить элементы по одному на строку. Чтобы вернуть сворачивание в одну строку — удалите эту запятую. Поведение старых версий можно вернуть опцией --skip-magic-trailing-comma / -C.

r"strings" и R"strings"

Black нормализует кавычки и префиксы строк в нижний регистр. Исключение — r-строки. Синтаксический подсветчик MagicPython (по умолчанию на GitHub и в VS Code) различает r-строки и R-строки: первые подсвечиваются как регулярные выражения, вторые — как «настоящие» raw-строки. Поэтому заглавная R сохраняется.

AST до и после форматирования

При запуске с --safe (по умолчанию) Black проверяет семантическую эквивалентность кода до и после по AST. В трёх ограниченных случаях AST может отличаться: (1) Black поправляет ведущие и завершающие пробелы в docstring и при необходимости меняет отступы — это популярный запрос пользователей; (2) для утверждения del наличие или отсутствие скобок меняет AST, но семантика в интерпретаторе та же; (3) Black может перемещать комментарии, в том числе type comments (они входят в AST с Python 3.8) — это не меняет поведение в runtime. Проверка эквивалентности — особенность Black, которой у других форматтеров нет; мы считаем её важной и ослаблять не планируем.


Будущее стиля Black

Стиль preview

Экспериментальные и потенциально разрушающие изменения стиля собраны под флагом CLI --preview. К концу года они могут перейти в стиль по умолчанию (см. The Black Code Style). Так как функциональность экспериментальная, отзывы и отчёты об ошибках приветствуются.

В стиль preview входят:

  • wrap_comprehension_in: переносить предложение in в list/dict comprehensions на следующие строки, если иначе превышается максимальная длина строки.
  • wrap_long_dict_values_in_parens: оборачивать длинные значения в словарях в скобки (см. ниже).

Улучшенное управление скобками в dict

Для литералов dict с длинными значениями значения теперь оборачиваются в скобки. Лишние скобки удаляются. Пример:

my_dict = {
    "a key in my dict": a_very_long_variable
    * and_a_very_long_function_call()
    / 100000.0,
    "another key": (short_value),
}

превращается в:

my_dict = {
    "a key in my dict": (
        a_very_long_variable * and_a_very_long_function_call() / 100000.0
    ),
    "another key": short_value,
}

Нестабильный стиль (unstable)

Раньше в preview попадали возможности с известными багами, из-за чего их нельзя было перевести в стабильный стиль. Такие возможности перенесены в стиль --unstable. Всё из --preview ожидаемо войдёт в стабильный стиль следующего года; возможности из --unstable стабилизируются только после исправления проблем. Если баг находят в возможности из --preview, её понижают до --unstable. Чтобы не терять поведение при таком понижении, можно включать отдельные нестабильные возможности флагом --enable-unstable-feature.

В unstable дополнительно входят:

  • hug_parens_with_braces_and_square_brackets: более компактное форматирование вложенных скобок (см. ниже).
  • string_processing: разбиение длинных строковых литералов и связанные изменения (см. ниже).

Улучшенные отступы для единственного аргумента-функции (списки и словари)

Для читаемости и меньшей «вертикальности» Black теперь ставит круглые скобки «(», «)» на одной строке с фигурными «{», «}» и квадратными «[», «]». Пример:

foo(
    [
        1,
        2,
        3,
    ]
)

nested_array = [
    [
        1,
        2,
        3,
    ]
]

превращается в:

foo([
    1,
    2,
    3,
])

nested_array = [[
    1,
    2,
    3,
]]

То же для распаковки списков и словарей:

foo(
    *[
        a_long_function_name(a_long_variable_name)
        for a_long_variable_name in some_generator
    ]
)

становится:

foo(*[
    a_long_function_name(a_long_variable_name)
    for a_long_variable_name in some_generator
])

Магическая trailing comma позволяет избежать такого «прижатия»: по умолчанию Black не будет переформатировать, например:

foo(
    [
        1,
        2,
        3,
    ],
)

Улучшенная обработка строк

Black разбивает длинные строковые литералы и объединяет короткие. Используются скобки где нужно. При разбиении части f-строк, не требующие форматирования, превращаются в обычные строки. f-строки не объединяются, если внутри есть кавычки и изменился бы стиль кавычек. Обратные слэши продолжения строк заменяются на строки в скобках. Лишние скобки убираются. Статус этой возможности отслеживается в этом issue.


Источники: The Black Code Style, current style, future style. Перевод неофициальный.