Самый строгий code style: Black

автор рубрика
Самый строгий code style: Black

Чем больше становится проект, тем сильнее он нуждается с средствах форматирования, поскольку код разнообразный код сложнее поддерживать. Мы уже говорили о таком руководстве по форматированию Python-кода, как PEP 8. Сегодня рассмотрим способ форматирования под названием Black, который знаменит своей строгостью. В этой статье вы узнаете о различиях между PEP 8 и Black и в чем преимущества последнего в больших проектах.

Основы форматирования кода с Black

Black во многом наследует принципы форматирования PEP 8. Всё, что касается пробелов на одной строке, остается таким же. Например, 4 пробела — это отступ, не нужно ставить пробелы вокруг скобок и т.д. А вот размещение самих строк уже отличается. Black рассматривает одно полное выражение или операторы на каждой строке.

На одной строке размещаются до 88 знаков, длинные выражения не в почете. Ставятся две пустые строки перед функциями и классами; одна строка перед методами одного класса. Пустая строка после документации функции нужна только в случае, если она содержит другую функцию (функция-декоратор). После документации класса обязательно должна стоять пустая строка, если после нее перечисляются поля или объявляется метод.

Еще одно важное замечание: строки размещаются в двойных кавычках ", а не в одинарных '. И это оправдано. Многие Python-разработчики в этом деле не очень последовательны: в одном проекте применяют оба вида кавычек.

По началу будет сложно привыкнуть к строгости Black, однако по мере роста кода вы ощутите его преимущества:

  • постоянство (поскольку все очень строго и нет пространства для самовыражения);
  • читаемость (к такому коду привыкаешь);
  • удобство разработки (при использовании версий контроля уменьшаются размеры diff’ов).

Размещение строк

Если выражение короткое, то следует разместить его на одной строке:

j = [1, 2, 3]

Если же нет, то внутренние содержимое переводить на следующую строку. Ниже представлены правильный и неправильный Python-код с точки зрения Black.

# неправильно:
ImportantClass.important_method(exc, limit, lookup_lines, capture_locals, extra_argument)

# правильно:
ImportantClass.important_method(
    exc, limit, lookup_lines, capture_locals, extra_argument
)

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

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:
        ...

Обратите внимание, где стоит закрывающая скобка: на следующей строке. PEP 8 разрешал оставить её на последнем аргументе, но Black этого не допускает. Кроме того, PEP 8 разрешает размещать аргументы в виде таблицы, но Black категоричен — либо все на одной строке, либо каждый аргумент на отдельной. Поэтому следующий код на Python не соответствует философии Black:

# Такой код не "черный":
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# Поэтому 1. либо так:
func(one, two three, four)
# 2. либо так:
foo = long_function_name(
    var_one, var_two, var_three, var_four
)
# 3. либо так:
foo = long_function_name(
    var_one,
    var_two,
    var_three,
    var_four
)

Данное правило также распространяется на импорт модулей.

# так:
from typing import (
    Iterator,
    List,
    Sequence,
    Set,
)
# или этак:
from typing import Iterator, List, Sequence, Set

Форматирование условий в соответствии с Black

Условия (if) и циклы (while) подчиняются тому же правилу: если выражение короткое, то не разделяется; если длинное, то разделяется. При длинном выражении сначала ставится открывающая скобка и затем перевод строки.

# Короткое выражение
#   неправильно:
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
):

Обратные слэши в “черном” коде не используются. Логические операторы в длинных выражениях переходят на следующую строку. Условия часто становятся длинными, поэтому, применяя форматирование Black, количество строк увеличивается, однако визуально Python-код становится более читаемым. Пример такого кода:

if (
    leaf.value == "not"
    and leaf.parent
    and leaf.parent.type == syms.comp_op
    and not (
        previous is not None
        and previous.type == token.NAME
        and previous.value == "is"
    )
):
    return COMPARATOR_PRIORITY

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

Длинные выражения могут содержать вызовы методов, идущие друг за другом (особенно это заметно при использовании фреймворка Apache Spark). Так вот точка вместе с самим вызовом переносится на следующую строку без дополнительных отступов. Взгляните на следующий Python-код, который написан в соответствии с 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()
    )

В версиях кода с Black легче разобраться

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

@@ -114,7 +114,8 @@ class MouseMotionEvent(MotionEvent):
             with win.canvas.after:
                 de = (
                     Color(.8, .2, .2, .7),
-                    Ellipse(size=(20, 20), segments=15))
+                    Ellipse(size=(20, 20), segments=15),
+                    Depth(.5))
             self.ud._drawelement = de
         if de is not None:
             self.push()

и с его использованием:

@@ -115,6 +115,7 @@ class MouseMotionEvent(MotionEvent):
                 de = (
                     Color(.8, .2, .2, .7),
                     Ellipse(size=(20, 20), segments=15),
+                    Depth(.5),
                 )
             self.ud._drawelement = de
         if de is not None:

 

Конечно, не обязательно знать все правила, поскольку инструмент форматирования Black можно встроить во многие текстовые редакторы. Инструкция по интеграции размещена в документации. О том, как писать грамотный и читаемый код на языке Python, вы узнаете на бесплатном курсе по машинному обучению «Основы языка Python для анализа данных и решения задач машинного обучения» в лицензированном учебном центре обучения и повышения квалификации разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве.

Источники
  1. Black docs
Комментировать