2025, Nov 13 18:02

Почему Black оборачивает длинные цепочки вызовов в скобки и разбивает их на строки

Разбираем, почему Black форматирует длинные цепочки вызовов: добавляет скобки и переносит каждый вызов на новую строку. Что это даёт и можно ли отключить.

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

Пример, который запускает такое поведение

Рассмотрим длинную цепочку вызовов. До форматирования она может выглядеть так:

compute_heavy(alpha_input, beta_input...).next_step().next_step().next_step()

После запуска Black код оборачивается, и каждый вызов в цепочке оказывается на отдельной строке:

(
 compute_heavy(alpha_input, beta_input...)
 .next_step()
 .next_step()
 .next_step()
)

Что происходит и почему

Этот шаблон рассматривается как «цепочка вызовов» (Call Chain), как описано в документации. Black переносит каждый вызов на отдельную строку ради единообразной читаемости и более аккуратных диффов. Такой разнос неизбежно добавляет переводы строки. Без внешней пары скобок эти переводы потребовали бы экранирования на каждой строке — чего Black избегает. Скобки добавляются, чтобы сделать цепочку синтаксически корректной без обратных слешей.

Правило и можно ли его отключить

Правило вытекает из того, как Black обрабатывает цепочки вызовов. Это осознанное решение в пользу единообразия, и менять его вряд ли будут. Иными словами, нет поддерживаемого способа запретить Black оборачивать такие цепочки в скобки, когда он решает, что их нужно разбить на строки.

Переписанный пример с тем же смыслом

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

process_payload(arg_one, arg_two...).pipe().pipe().pipe()

Ради читаемости и аккуратных диффов Black переформатирует её в такой вид, добавив внешнюю пару скобок, чтобы не пришлось использовать символ продолжения строки:

(
 process_payload(arg_one, arg_two...)
 .pipe()
 .pipe()
 .pipe()
)

Почему это важно

Понимание этого поведения избавляет от борьбы с форматтером. Разбиение улучшает обзорность и приводит к меньшим диффам, когда меняются отдельные звенья цепочки. Кроме того, отпадает необходимость ставить обратные слеши в конце каждой строки — это шумно и хрупко.

Можете выбрать любой цвет, пока это — чёрный.

Это и есть философия: единый, принципиальный стиль, последовательно применяемый ко всему коду.

Практический вывод

Если длинная цепочка вызовов требует нескольких строк, Black разобьёт её на один вызов на строку и добавит скобки вокруг всей группы. Это ожидаемое поведение по правилу Call Chain и оно не предназначено для переопределения. Примите этот стиль — пусть форматтер берёт на себя читаемость и аккуратные диффы.