Стильный код на Python, или учимся использовать Flake8

  • Tutorial

Автор: Анатолий Соловей, developer

Язык программирования Python очень востребован на современном рынке, он развивается изо дня в день, и вокруг него сложилось активное сообщество. Во избежание конфликтов между разработчиками-питонистами, создатели языка написали соглашение PEP 8, описывающее правила оформления кода, однако даже там отмечено, что:
Many projects have their own coding style guidelines. In the event of any conflicts, such project-specific guides take precedence for that project.

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

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

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

На помощь в этом случае приходят линтеры — инструменты, контролирующие оформление кода в проекте. Именно они помогают поддерживать его чистоту и, в нашем случае, предотвращать создание коммитов, которые могут содержать ошибки. Я для контроля качества использую Flake8 и сейчас постараюсь объяснить, почему выбрал именно его, и расскажу, как его настроить, чтобы получить максимальный результат. Заинтересовались? Добро пожаловать под кат.

Flake8: Your Tool For Style Guide Enforcement


Сам Flake8 — инструмент, позволяющий просканировать код проекта и обнаружить в нем стилистические ошибки и нарушения различных конвенций кода на Python.

Flake8 умеет работать не только с PEP 8, но и с другими правилами, к тому же поддерживает кастомные плагины, поэтому в дальнейшем в этой статье я буду отталкиваться от правил из Google Python Style Guide.

Почему Flake8?


Flake8: pep8 + pyflakes + more


Создатель Flake8 Тарек Зиаде ставил перед собой цель объединить главные популярные инструменты контроля кодстайла в одной библиотеке, с чем в итоге успешно справился — Flake8 получился действительно универсальным.

Легкость установки и конфигурации


Чтобы проверить, отвечает ли код в вашем проекте основным требованиям PEP 8, достаточно установить Flake:

$ pip install flake8

и запустить его — просто ввести в командной строке:

$ flake8 my_project

после чего вы получите список с именами файлов и номерами строк, где были допущены ошибки, и подробное описание самих ошибок:

$ flake8 my_project
myfile.py:1: 'sys' imported but unused
myfile.py:4:1: E302 expected 2 blank lines, found 1

Великолепно, не правда ли? Но и это не всё. Если вы не любитель работать с консолью, то вы можете настроить интеграцию Flake8 с IDE или редактором, который вы предпочитаете использовать.

Интеграция Flake8 с редакторами и IDE


Интеграция с PyCharm
Так же актуально и для любой другой IDE от JetBrains.
Интеграция проводится всего за пару несложных шагов.

Откройте настройки External Tools в File → Settings → Tools и нажмите на “+”, затем заполните поля по этому шаблону:



После этого нажмите на Output Filters, а затем на “+”, чтобы добавить новое правило для вывода сообщений от флейка:


Здесь мы говорим PyCharm, что хотим, чтобы в выводе строки с ошибками были кликабельными и открывали в редакторе файл и место с ошибкой

Все. Интеграция Flake8 с PyCharm закончена. Чтобы вызвать флейк и проверить свой код, кликаем правой кнопкой мыши на файл/директорию, которую мы хотим проверить, и в контекстном меню выбираем External Tools → Flake8.



В выводе PyCharm появится кликабельный список нарушений в выбранном файле/директории:



Интеграция с Atom
Чтобы установить инструмент Flake8 для Atom, используйте Atom package manager в Settings и найдите там linter-flake8:



Или вызовите


apm install flake8

из командной строки.

Затем перейдите в linter-flake8 settings и укажите путь к директории, где установлен flake8:



У linter-flake8 есть собственный ReadMe по настройке, с которым при желании вы можете ознакомиться на странице самого linter-flake8 в Atom.

Наличие Version Control Hooks


Именно это я считаю главным достоинством Flake8, которое выделяет его среди других линтеров. В отличии от большинства подобных инструментов, где для настройки VCS-хуков используются целые отдельные библиотеки и модули (как, например, в Pylint), настройка хуков в флейке проводится буквально в две строчки.

На момент написания этой статьи, Flake8 умеет использовать pre-commit-хуки для Git и Mercurial. Эти хуки позволяют, например, не допускать создания коммита при нарушении каких-либо правил оформления.

Установить хук для Git:

$ flake8 --install-hook git

И настроить сам гит, чтобы учитывать правила Flake8:

$ git config --bool flake8.strict true

Я продемонстрирую, как Git hook работает на проекте, который я использовал для примера интеграции Flake8 с PyCharm. В модуле flake8tutorial.py мы видим очевидные ошибки: импортированные и неиспользованные модули, остсутствие докстринга и пустой строки в конце файла.

Первым делом проинициализируем в этом проекте git-0репозиторий, установим flake8 хук и скажем нашему git, что он должен прогонять флейк перед коммитами:



Затем попробуем провести первый коммит:



Как видите, flake8 был вызван перед коммитом и не позволил нам закоммитить невалидные изменения.

Теперь фиксим ошибки, отмеченные флейком, и пытаемся закоммитить валидный код:



Коммит успешно создан. Отлично!

Настройка Flake8 для Mercurial практически идентична. Для начала нужно установить Flake8 Mercurial Hook:

$ flake8 --install-hook mercurial

И настроить сам Mercurial:

$ hg config flake8.strict true

Вот и все, хук для Меrcurial установлен, настроен и готов к использованию!

Подробнее о конфигурации Flake8


Базовая конфигурация


Список дополнительных опций и правил можно передать прямо при вызове из командной строки таким образом:

flake8 --select E123

(в этом примере опцией select мы говорим, чтобы Flake сообщал о нарушениях только правила E123 (это код правила “closing bracket does not match indentation of opening bracket's line”)).

Кстати, полный список опций с описанием вы можете найти в документации к самой библиотеке.

На мой взгляд, куда предпочтительнее настраивать Flake с помощью конфигурационных файлов, вы можете хранить настройки в одном из файлов setup.cfg, tox.ini или.flake8. Для ясности я предпочитаю использовать последний вариант.

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

[flake8]
ignore = D203
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist

В этом файле мы сообщаем Flake, что он не должен оповещать нас о нарушениях правила D203 (“1 blank line required before class docstring”), а также не должен проверять файлы .git, __pycache__, docs/source/conf.py и директории old, build, dist.

В конфигурационных файлах можно оставлять комментарии, это полезно делать, если вы предоставляете большой список правил, которые Flake должен игнорировать:

[flake8]
# it's not a bug that we aren't using all of hacking
ignore =
    # F812: list comprehension redefines ...
    F812,
    # H101: Use TODO(NAME)
    H101,
    # H202: assertRaises Exception too broad
    H202,
    # H233: Python 3.x incompatible use of print operator
    H233,
    # H301: one import per line
    H301,
    # H306: imports not in alphabetical order (time, os)
    H306,
    # H401: docstring should not start with a space
    H401,
    # H403: multi line docstrings should end on a new line
    H403,
    # H404: multi line docstring should start without a leading new line
    H404,
    # H405: multi line docstring summary not separated with an empty line
    H405,
    # H501: Do not use self.__dict__ for string formatting
    H501

Также можно добавить в исключения отдельную строку в вашем модуле, просто оставив на этой строке комментарий noqa. Тогда при проверке модуля Flake8 будет игнорировать ошибки, найденные в строках, помеченных этим комментарием:

import sys # noqa

Модули, расширяющие функциональность


Так как Flake позволяет создавать и использовать кастомные плагины, для него можно найти большое количество open-source плагинов. Я опишу только те, которые использую сам и считаю особенно полезными:

flake8-import-order

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

Этот плагин расширяет список предупреждений Flake, добавляя туда три новых:

  • I100: Your import statements are in the wrong order.
  • I101: The names in your from import are in the wrong order.
  • I201: Missing newline between sections or imports.

Установка:

pip install flake8-import-order

Конфигурация:

[flake8]
application-import-names = my_project, tests # Указываем флейку директории, в которых хранятся локальные пакеты.

import-order-style = google # Указываем флейку на то, в каком порядке должны идти импорты. Как я уже говорил выше, я предпочитаю использовать Google Style Guide.

Более подробно о настройке flake8-import-order можно прочитать на странице библиотеки на Github.

flake8-docstrings

Плагин, добавляющий поддержку функционала из pydocstyle — проверку докстрингов на соответствие конвенциям Питона.

Установка:

pip install flake8_docstrings

Список добавляемых этой библиотекой правил можно найти в документации pydocstyle.

Конфигурация:

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

[flake8]
ignore = D101 # Игнорировать docstrings предупреждение “Missing docstring in public class”

Страница библиотеки на Github тут.

flake8-builtins

Плагин, проверяющий код на использование встроенных имен в качестве переменных или параметров.

Установка:

pip install flake8-builtins

Конфигурация:

Как и в случае с flake8-docstrings, у плагина нет дополнительных настроек, но добавленные им правила можно, например, внести в исключения флейка:

[flake8]
ignore = B001 # Игнорировать builtins предупреждение “<some_builtin> is a python builtin and is being shadowed, consider renaming the variable”

Более подробную информацию об этом плагине можно найти на странице этого плагина на Github.

flake8-quotes

Плагин, позволяющий контролировать тип кавычек, которые будут использоваться в проекте.

Установка:

pip install flake8-quotes

Конфигурация:

[flake8]
inline-quotes = " # Указываем, какой тип кавычек должен использоваться в вашем проекте

Более подробную информацию об этом плагине можно найти на странице этого плагина на Github.

Послесловие


Хотя настройки, описанные выше, в 97,5 % случаев смогут предотвратить появление некачественного кода в репозитории, он так или иначе может оказаться запушенным (например, если деву было лень вводить две строчки для настройки pre-commit hook). Поэтому я настоятельно рекомендую добавить вызов Flake8 на этапе билда пул-реквестов в используемой вами системе continuous integration, чтобы предотвратить мердж невалидных пул-реквестов и попадание ошибок в мастер.

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

Список источников:

DataArt
122,00
Технологический консалтинг и разработка ПО
Поделиться публикацией

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

    0
    Теперь фиксим ошибки, отмеченные флейком, и пытаемся закоммитить валидный код:
    Есть (была?) одна проблема: валидатор проверяет состояние рабочего дерева, а не индекса. Проще говоря, существует возможность нечаянно запихнуть в репозиторий каку:

    1) Пишите невалидный код
    2) Добавляете файл в индекс
    3) Пытаетесь сделать коммит
    4) Валидатор ругается
    5) Доводите файл до валидного состояния, но НЕ добавляете его повторно в индекс
    6) Теперь у вас есть возможность закоммитить невалидный код из п. 2
      0

      git commit -n проще: ничего даже править не надо, сразу есть возможность. Чтобы такого не было, в любом случае надо линтер на ci сервере запускать. Ведь кто-то мог и не настроить хуки вовсе.

        0
        Только что зарепродьюсил эту ошибку с самой новой на данный момент версией (3.2.1). Вроде, в списке issues ничего подобного не проскакивало, так что, возможно они сами не знают об этой ошибке. Я зарепорчу им баг в ближайшем времени, спасибо за информацию!
        +1

        Спасибо.


        Субъективно, насколько Pyflakes8 лучше / удобнее, чем встроенный в Pycharm linter?

          0

          Мне кажется, что в PyCharm PyFlakes не нужен. Встроенные средства лучше. Правда в последний раз смотрел на Pyflakes очень давно. Но даже не знаю, что надо сделать, чтобы превзойти последние версии PyCharm.

            0
            Для меня тут всё очень просто: PyCharm linter очень удобный при написании кода, потому что сразу подсвечивает стилистические ошибки, но относительно Flake у него есть два недостатка:
            1) Он не позволяет предотвращать коммиты ошибок (т.е. пока что интеграции встроенного линтера с системами контроля версий нет, насколько я знаю)
            2) Он находится только в IDE на твоей локальной машине. Ты можешь писать великолепный код, настроив пайчармовский линтер, но недобросовестный или невнимательный коллега всё равно сможет наделать стилистических ошибок в проекте, потому что, например, не настроит все необходимые правила для пайчармовского линтера. Flake же можно вызывать во время билдов на CI, внутри Make файлов и скриптов, чтобы проверить, что с кодом всё в порядке вне контекста одной конкретной среды разработки.
            На мой взгляд, одно другому не мешает, так что для максимального эфекта лучше использовать оба этих инструмента вместе.
            0
            Python хорошо проверяется на ошибки встроенным в PyCharm linter'om. Flake8 видится, как еще один проект проверки кода, по типу проектов JS linter. В большинстве случаев не хватает автокорректировки кода, как делает PostCSS с плагинами.
              0
              PyCharm'овская подсветка синтаксиса, конечно, очень хороша, но если над проектом работает больше одного человека, то её будет недостаточно. Flake8 позволяет подготовить конкретный список стилистических правил, которые должны выполняться в коде всего проекта и, в отличии от PyCharm'овского линтера, он заставляет разработчиков выполнять эти правила с помощью способов, описанных в статье: пре-коммит хуки, прогон билдов в CI и т.д.
              Такая добровольно-принудительная система может быть не всем по нраву, но некоторые заказчики, например, могут быть просто очень требовательны к оформлению кода и тогда флейк поможет избежать возни с ревью и кучей обновлений с фиксами неправильного стиля.
              А по поводу автокорректировки кода, в комментариях ниже рассказали про yapf, который этим как раз и занимается. На хабре по нему пока материала нет совсем, но документация у этой тулзы очень обширная, думаю, вам будет интересно ознакомиться.
              +1
              Экспериментировал с тулзами.
              В итоге пришел к такому решению:
              — flake8 добавлен и для локальных тестов в `tox` — это помогает сразу видеть если написал какую-то ерунду в процессе работы еще до подготовки коммита

              — в прекоммит-хук добавлены вызовы `flake` — спасает от отправки пропущенных помарок, если недочинил их на стадии подготовки

              — туда же добавлен вызов yapf — очень полезная тулза, которая не просто проверяет, а чинит форматирование кода. Причем если она изменила форматирование, то она блокирует коммит. Можно открыть код и перечитать правки. С помощью `git diff --staged` сразу видно, что именно было отформатировано и добавить предлженное в индекс, чтобы заново закоммитить.

              — в планах сюда же начать пользовать pylint — он сейчас прикручен, но из-за специфических срабатываний на «необьявленных» методах класса (т.е. почти все Django методы) пришлось временно отключить.

              — так же добавлена на стадии CI до прогона тестов через `travis` — если все же закоммитил лажу

                +1
                Я бы еще предложил добавлять проверку с помощью mypy — на необязательную проверку типов. Даже если в новом коде нету аннотаций, она может проверять аргументы вызовов стандартной библиотеки, и улучшать качество кода.
                  0
                  Включил хуки, что-то он у меня ругается на logger.conf
                    0
                    «О сколько нам открытий чудных готовит просвещенья дух!» (с)
                    Автору — респектище!
                    Убил (точнее уже не «убил» и «выделил», получается) сегодня целый день на причесывание своего Django-проекта.
                    Узнал о себе много [не]интересного.
                      0
                      Поставьте pylint. Ещё на неделю будет чем заняться :)
                      0
                      Привет, а подскажите — что я делаю не так?
                      Запустил flake, получил список ошибок, почистил код. Но осталось сообщение, которое меня в ступор вводит

                      Есть у меня вот такой импорт — да да, я знаю что это плохо, и поэтому тут вопросов не было, сделал 2 импорта, но:
                      from urllib.request import Request, urlopen
                      

                      Получаю сообщение флака:
                      watchdog.py:6:1: F401 'urllib.request.Request' imported but unused
                      watchdog.py:6:1: F401 'urllib.request.urlopen' imported but unused
                      


                      Мне это показалось странным — поскольку без этих импортов код не работал. Ну да ладно, разделяю импорт на 2, пробую ещё раз:
                      from urllib.request import Request
                      from urllib.request import urlopen
                      

                      Получаю ожидаемое — тоже самое:
                      watchdog.py:7:1: F401 'urllib.request.Request' imported but unused
                      watchdog.py:8:1: F401 'urllib.request.urlopen' imported but unused
                      


                      Ладно, проворачиваю эксперимент — комментирую обе строки, ошибка флака ушла. Запускаю скрипт:
                      Traceback (most recent call last):
                        File "./watchdog.py", line 66, in <module>
                          main()
                        File "./watchdog.py", line 55, in main
                          new_url = bing_req(URL+"?"+enc_data, header, ip, cites)
                        File "./watchdog.py", line 28, in bing_req
                          req = urllib.request.Request(url=item, headers=header)
                      AttributeError: module 'urllib' has no attribute 'request'
                      


                      Бабах, скрипт перестал работать. Поскольку питон кручу от силы недели 2, не понимаю — что упущено или что не так делаю, и кому верить — работающему скрипту или ошибкам флака.
                      Версия python (работаю под pyenv):
                      Python 3.6.0
                      
                        0

                        Верить всем :) Оба варианта не совсем правильные, просто в одном случае ошибка, а в другом предупреждение.


                        Можно сделать либо так:


                        import urllib.request
                        
                        req = urllib.request.Request(url=item, headers=header)

                        либо так:


                        from urllib.request import Request
                        
                        req = Request(url=item, headers=header)

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое