ХВАТИТ ЭТО ТЕРПЕТЬ: чистый код с PEP 8

автор рубрика
ХВАТИТ ЭТО ТЕРПЕТЬ: чистый код с PEP 8

В среде программистов есть такое понятие как чистый код. Некоторые даже получают эстетическое удовольствие при его виде. Одним из критериев соблюдения чистоты является стиль кодирования (coding style), который определяется внутри организации или команды. Но для тех, кто еще не определился со стилем, предусмотрены официальные рекомендации по написанию кода на Python — PEP 8. В этой статье мы расскажем вам, как форматировать свой код так, чтобы он был читаемый, и чтобы за него не было стыдно.

Используйте 4 пробела для структурного отступа, а не табы

Во многих языках предусмотрены символы открытия и закрытия тела код: begin/end в Pascal, фигурные скобки в C-подобных языках. В Python вложенные тела функций, условия и т.д. должны быть отделены пробелами. В PEP 8 таких пробелов должно быть строго – 4.

Размещение аргументов функции

Если аргументов у функции или класса Python слишком много, то

  • размещайте их либо друг под другом сразу после функции,
  • переведите их все на следующую строку,
  • переведите их все на следующую строку с добавление еще 4-х пробелов.

Следующие три примера согласуются с PEP 8:

# 1. Друг под другом
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# 2. На новой строке. Исопльзовать только в вызывах функций
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)

# 3. Перевод на новую строку с дополнительными пробелами
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    # code
    pass

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

А вот добавлять структурный отступ без перевода первых аргументов на следующую строку и не добавлять дополнительные пробелы при переводе строки в PEP 8 воспрещается. Поэтому следующие два примера НЕПРАВИЛЬНЫ:

# Не выравнены друг под другом
foo = long_function_name(var_one, var_two,
    var_three, var_four)

# Не хватает еще 4 пробелов, поэтому непонятно, где
# зааканчивают аргументы и начинается тело функции
def long_function_name(
    var_one, var_two, var_three,
    var_four):
    print(var_one)

Размещение условия if’ов

Логика условий должна быть понятна сразу. Если их много размещайте на новой строке с “повисшим” оператором, либо перенесите условие вместе с оператором но новую строку, но с добавлением дополнительных пробелов. Можно также добавить комментарий между if’ом и его телом.

Следующие три примера согласно PEP 8 верны:

# 1. Без отступов
if (this_is_one_thing and
    that_is_another_thing):
    do_something()

# 2. Без отступов и с комментарием для различения
if (this_is_one_thing and
    that_is_another_thing):
    # Since both conditions are true, we can frobnicate.
    do_something()

# 3. С дополнительным отступом
if (this_is_one_thing
        and that_is_another_thing):
    do_something()

Размещение условий на очень важно, иначе это может превратиться в кашу, как это:

if ((('0' <= char) and (char <= 'h')) or (('a' <= char) and 
    (char <= 'z')) or (('A' <= char) and (char <= 'Z'))):

При виде такого кода даже не хочется разбираться, что он делает (но люди все равно продолжают так писать, аргументируют это тем, что изменят это когда-то потом). Хотя его можно сделать более понятным, расставив приоритеты:

if ((('0' <= char) and (char <= 'h')) or
    (('a' <= char) and (char <= 'z')) or
    (('A' <= char) and (char <= 'Z'))):

Здесь мы еще добавили еще один пробел, чтобы скобки с and’ами были ровно друг под
другом.

Размещение элементов списка

Если в списке много элементов, то представьте их в виде матрицы, где закрывающую скобку перенесите на следующую строку. В результате Python-списки у вас будут выглядеть так:

my_list = [
    1, 2, 3,
    4, 5, 6,
]

Можно выравнять закрывающую скобку, добавив отступ (такой формат редко, где
увидишь):

my_list = [
    1, 2, 3,
    4, 5, 6,
    ]

Переносить операторы на следующую строку или нет

Бинарные операторы в длинном выражении переводите на следующую строку. Это повысит читаемость кода на Python. Не забудьте разместить выражение в скобки, иначе будет ошибка. Например, это может выглядеть вот так:

income = (gross_wages
          + taxable_interest
          + dividends
          - ira_deduction
          - student_loan_interest)

А вот оставлять их “повисшими” НЕЛЬЗЯ:

income = (gross_wages +
          taxable_interest +
          dividends -
          ira_deduction -
          student_loan_interest)

Группировка выражений

Размещайте арифметические выражения согласно их логике. Не нужно ставить пробел после каждой переменной, еще более не рекомендуется, так это вовсе их не ставить. Вот так вы сделает ваш код более “Pyhonic”:

i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)

А вот так согласно PEP 8 делать НЕЛЬЗЯ:

i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)

Не экономьте на строчках

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

Чего не стоит делать, так это сокращать за счет написания всего на одной строчке Поэтому каждая операция должна быть на своей строчке. Правильное написание кода на Python:

if foo == 'blah':
    do_blah_thing()
do_one()
do_two()
do_three()

А вот так НЕПРАВИЛЬНО:

if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()

for x in lst: total += x
while t < 10: t = delay()

# Самый ужас:
if foo == 'blah': do_blah_thing()
else: do_non_blah_thing()

try: something()
finally: cleanup()

do_one(); do_two(); do_three(long, argument,
                             list, like, this)

Извлечение подсписков

В Python подсписки извлекаются очень просто и является спецификой этого языка. В PEP 8 даже определено, как это делать. Рекомендация здесь такова — не добавляйте пробелы. Правильное извлечение подсписков в Python:

ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]

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

НЕВЕРНЫЕ варианты там, где много пробелов:

ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]

Комментирование

Документировать то, что вы написали нужно как можно раньше. Иначе забудете о том, что понаписали. Комментарии служат для определения цели настоящего кода. Не комментируйте все, вплоть до объявления переменных. Придумывая комментарий, стремитесь отвечать на вопрос “почему?”, а не “как?”.

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

Правильный многострочный комментарий в Python:

def foo():
"""Return a foobang

Optional plotz says to frobnicate the bizbaz first.
"""

Правильный однострочный комментарий в Python:

def function(a, b):
    """Do X and return a list.""

В комментариях можно добавить пояснения по аргументам:

def complex(real=0.0, imag=0.0):
    """Form a complex number.

    Keyword arguments:
    real -- the real part (default 0.0)
    imag -- the imaginary part (default 0.0)
    """
    if imag == 0.0 and real == 0.0:
        return complex_zero

 

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

Записаться на курс

Смотреть раcписание

Источники
  1. https://www.python.org/dev/peps/pep-0257/
Комментировать