Меню

Проверка кода на ошибку питон

Рассмотрим популярные инструменты для анализа кода Python и подробно расскажем об их специфике и основных принципах работы.

Инструменты для анализа кода Python. Часть 1

Автор: Валерий Шагур, teacher assistance на курсе Программирование на Python

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

Командная работа над проектом еще больше повышает требования к качеству кода, поэтому важным условием продуктивной работы команды становится описание формальных требований к написанию кода. Это могут быть соглашения, принятые в языке программирования, на котором ведется разработка, или собственное (внутрикорпоративное) руководство по стилю. Выработанные требования к оформлению кода не исключают появления «разночтений» среди разработчиков и временных затрат на их обсуждение. Кроме этого, соблюдение выработанных требований ложится на плечи программистов в виде дополнительной нагрузки. Все это привело к появлению инструментов для проверки кода на наличие стилистических и логических ошибок. О таких инструментах для языка программирования Python мы и поговорим в этой статье.

Анализаторы и автоматическое форматирование кода

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

И первая, и вторая группы включают в себя как простые утилиты командной строки для решения узкоспециализированных задач (например, проверка docstring или сортировка импортов), так и богатые по возможностям библиотеки, объединяющие в себе более простые утилиты. Средства анализа кода из первой группы принято называть линтерами (linter). Название происходит от lint — статического анализатора для языка программирования Си и со временем ставшего нарицательным. Программы второй группы называют форматировщиками (formatter).

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

Часть 1

  • pycodestyle
  • pydocstyle
  • pyflakes
  • pylint
  • vulture

Часть 2

  • flake8
  • prospector
  • pylama
  • autopep8
  • yapf
  • black

Соглашения принятые в статье и общие замечания

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

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

Установка всех программ в обзоре практически однотипна и сводится к использованию пакетного менеджера pip.

$ python3.6 -m pip install --upgrade <package_name>

Некоторые из библиотек имеют готовые бинарные пакеты в репозиториях дистрибутивов linux или возможность установки с использованием git. Тем не менее для большей определенности и возможности повторения примеров из статьи, установка будет производится с помощью pip.

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

Тестовый скрипт: для примеров использования программ мы создали простенький по содержанию файл example.py. Мы сознательно не стали делать его более разнообразным по наличию в нем ошибок. Во-первых, добавление листингов с выводом некоторых анализаторов в таком случае сильно “раздуло” бы статью. Во-вторых, у нас не было цели детально показать различия в “отлове” тех или иных ошибок для каждой из утилит.

Содержание файла example.py:

import os
import notexistmodule

def Function(num,num_two):
return num

class MyClass:
"""class MyClass """

def __init__(self,var):
self.var=var

def out(var):
print(var)


if __name__ == "__main__":
my_class = MyClass("var")
my_class.out("var")
notexistmodule.func(5)

В коде допущено несколько ошибок:

  • импорт неиспользуемого модуля os,
  • импорт не существующего модуля notexistmodule,
  • имя функции начинается с заглавной буквы,
  • лишние аргументы в определении функции,
  • отсутствие self первым аргументом в методе класса,
  • неверное форматирование.

Руководства по стилям: для тех, кто впервые сталкивается с темой оформления кода, в качестве знакомства предлагаем прочитать официальные руководства по стилю для языка Python PEP8 и PEP257. В качестве примера внутрикорпоративных соглашений можно рассмотреть Google Python Style Guide — https://github.com/google/styleguide/blob/gh-pages/pyguide.md

Pycodestyle

Pycodestyle — простая консольная утилита для анализа кода Python, а именно для проверки кода на соответствие PEP8. Один из старейших анализаторов кода, до 2016 года носил название pep8, но был переименован по просьбе создателя языка Python Гвидо ван Россума.

Запустим проверку на нашем коде:

$ python3 -m pycodestyle example.py 
example.py:4:1: E302 expected 2 blank lines, found 1
example.py:4:17: E231 missing whitespace after ','
example.py:7:1: E302 expected 2 blank lines, found 1
example.py:10:22: E231 missing whitespace after ','
example.py:11:17: E225 missing whitespace around operator

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

<имя файла>: <номер строки> :<положение символа>: <код и короткая расшифровка ошибки>

Возможности программы по проверке соглашений ограничены: нет проверок на правильность именования, проверка документации сводится к проверки длины docstring. Тем не менее функционал программы нельзя назвать “спартанским”, он позволяет настроить необходимый уровень проверок и получить различную информацию о результатах анализа. Запуск с ключом —statistics -qq выводит статистику по ошибкам:

$ python3 -m pycodestyle --statistics -qq example.py 
1 E225 missing whitespace around operator
2 E231 missing whitespace after ','
2 E302 expected 2 blank lines, found 1

Более наглядный вывод можно получить при использовании ключа —show-source. После каждого сообщения об ошибке будет выведена строка исходного кода, в которой содержится ошибка.

$ python3 -m pycodestyle --show-source example.py 
example.py:4:1: E302 expected 2 blank lines, found 1
def Function(num,num_two):
^
example.py:4:17: E231 missing whitespace after ','
def Function(num,num_two):
^
example.py:7:1: E302 expected 2 blank lines, found 1
class MyClass:
^
example.py:10:22: E231 missing whitespace after ','
def __init__(self,var):
^
example.py:11:17: E225 missing whitespace around operator
self.var=var
^

Если есть необходимость посмотреть, какие из соглашений PEP8 были нарушены, используйте ключ — show-pep8. Программа выведет список всех проверок с выдержками из PEP8 для случаев нарушений. При обработке файлов внутри директорий предусмотрена возможность фильтрации по шаблону. Pycodestyle позволяет сохранять настройки поиска в конфигурационных файлах как глобально, так и на уровне проекта.

Pydocstyle

Утилиту pydocstyle мы уже упоминали в статье Работа с документацией в Python: поиск информации и соглашения. Pydocstyle проверяет наличие docstring у модулей, классов, функций и их соответствие официальному соглашению PEP257.

$ python3 -m pydocstyle example.py
example.py:1 at module level:
D100: Missing docstring in public module
example.py:4 in public function `Function`:
D103: Missing docstring in public function
example.py:7 in public class `MyClass`:
D400: First line should end with a period (not 's')
example.py:7 in public class `MyClass`:
D210: No whitespaces allowed surrounding docstring text
example.py:10 in public method `__init__`:
D107: Missing docstring in __init__
example.py:13 in public method `out`:
D102: Missing docstring in public method

Как мы видим из листинга, программа указала нам на отсутствие документации в определениях функции, методов класса и ошибки оформления в docstring класса. Вывод можно сделать более информативным, если использовать ключи —explain и —source при вызове программы. Функционал pydocstyle практически идентичен описанному выше для pycodestyle, различия касаются лишь названий ключей.

Pyflakes

В отличие от уже рассмотренных инструментов для анализа кода Python pyflakes не делает проверок стиля. Цель этого анализатора кода — поиск логических и синтаксических ошибок. Разработчики pyflakes сделали упор на скорость работы программы, безопасность и простоту. Несмотря на то, что данная утилита не импортирует проверяемый файл, она прекрасно справляется c поиском синтаксических ошибок и делает это быстро. С другой стороны, такой подход сильно сужает область проверок.
Функциональность pyflakes — “нулевая”, все что он умеет делать — это выводить результаты анализа в консоль:

$ python3 -m pyflakes example.py 
example.py:1: 'os' imported but unused

В нашем тестовом скрипте, он нашел только импорт не используемого модуля os. Вы можете самостоятельно поэкспериментировать с запуском программы и передачей ей в качестве параметра командной строки Python файла, содержащего синтаксические ошибки. Данная утилита имеет еще одну особенность — если вы используете обе версии Python, вам придется установить отдельные утилиты для каждой из версий.

Pylint

До сих пор мы рассматривали утилиты, которые проводили проверки на наличие либо стилистических, либо логических ошибок. Следующий в обзоре статический инструмент для анализа кода Python — Pylint, который совместил в себе обе возможности. Этот мощный, гибко настраиваемый инструмент для анализа кода Python отличается большим количеством проверок и разнообразием отчетов. Это один из самых “придирчивых” и “многословных” анализаторов кода. Анализ нашего тестового скрипта выдает весьма обширный отчет, состоящий из списка найденных в ходе анализа недочетов, статистических отчетов, представленных в виде таблиц, и общей оценки кода:

$ python3.6 -m pylint --reports=y text example.py
************* Module text
/home/ququshka77/.local/lib/python3.6/site-packages/pylint/reporters/text.py:79:22: W0212: Access to a protected member _splitstrip of a client class (protected-access)
************* Module example
example.py:4:16: C0326: Exactly one space required after comma
def Function(num,num_two):
                           ^ (bad-whitespace)
example.py:10:21: C0326: Exactly one space required after comma
    def __init__(self,var):
                             ^ (bad-whitespace)
example.py:11:16: C0326: Exactly one space required around assignment
        self.var=var
                    ^ (bad-whitespace)
example.py:1:0: C0111: Missing module docstring (missing-docstring)
example.py:2:0: E0401: Unable to import 'notexistmodule' (import-error)
example.py:4:0: C0103: Function name "Function" doesn't conform to snake_case naming style (invalid-name)
example.py:4:0: C0111: Missing function docstring (missing-docstring)
example.py:4:17: W0613: Unused argument 'num_two' (unused-argument)
example.py:13:4: C0111: Missing method docstring (missing-docstring)
example.py:13:4: E0213: Method should have "self" as first argument (no-self-argument)
example.py:7:0: R0903: Too few public methods (1/2) (too-few-public-methods)
example.py:18:4: C0103: Constant name "my_class" doesn't conform to UPPER_CASE naming style (invalid-name)
example.py:19:4: E1121: Too many positional arguments for method call (too-many-function-args)
example.py:1:0: W0611: Unused import os (unused-import)

Report
======
112 statements analysed.

Statistics by type
+----------+----------+---------------+-------------+-------------------+---------------+
|type     |number      |old number        |difference      |%documented       |%badname |
+======+======+========+========+===========+========+
|module   |2           |2                 |=               |50.00             |0.00            |
+-----------+----------+---------------+-------------+-------------------+---------------+
|class    |5           |5                 |=               |100.00            |0.00            |
+-----------+----------+---------------+-------------+-------------------+---------------+
|method   |11          |11                |=               |90.91             |0.00            |
+-----------+----------+---------------+-------------+-------------------+---------------+
|function |4           |4                 |=               |75.00             |25.00          |
+-----------+----------+---------------+-------------+-------------------+---------------+

External dependencies
::
    pylint 
      -interfaces (text)
      -reporters (text)
      | -ureports 
      |   -text_writer (text)
      -utils (text)


Raw metrics
+-------------+----------+-------+-----------+-------------+
|type        |number |%     |previous    |difference |
+=======+======+=====+=====+========+
|code        |128    |48.30 |128         |=               |
+-------------+----------+--------+-----------+------------+
|docstring   |84     |31.70 |84          |=               |
+-------------+----------+--------+-----------+------------+
|comment     |16     |6.04  |16          |=               |
+-------------+----------+--------+-----------+------------+
|empty       |37     |13.96 |37          |=               |
+-------------+----------+--------+-----------+------------+

Duplication
+-------------------------------+------+------------+-------------+
|                            |now      |previous      |difference |
+=================+=====+======+========+
|nb duplicated lines         |0        |0             |=              |
+-------------------------------+-------+------------+------------+
|percent duplicated lines    |0.000    |0.000         |=              |
+-------------------------------+-------+------------+------------+

Messages by category
+--------------+----------+-----------+-------------+
|type            |number |previous |difference |
+========+======+======+========+
|convention      |8       |8       |=               |
+--------------+----------+-----------+-------------+
|refactor        |1       |1       |=               |
+--------------+-----------+----------+-------------+
|warning         |3       |3       |=               |
+--------------+-----------+----------+-------------+
|error           |3       |3       |=               |
+--------------+-----------+----------+-------------+

% errors / warnings by module
+-----------+--------+-----------+----------+--------------+
|module   |error    |warning |refactor |convention   |
+======+=====+======+======+========+
|example  |100.00   |66.67   |100.00   |100.00       |
+-----------+---------+----------+-----------+-------------+
|text     |0.00     |33.33   |0.00     |0.00         |
+-----------+---------+----------+-----------+-------------+

Messages
+-----------------------------+----------------+
|message id                  |occurrences |
+=================+=========+
|missing-docstring           |3                 |
+-----------------------------+----------------+
|bad-whitespace              |3                 |
+------------------------------+---------------+
|invalid-name                |2                 |
+------------------------------+---------------+
|unused-import               |1                 |
+------------------------------+---------------+
|unused-argument             |1                 |
+------------------------------+---------------+
|too-many-function-args      |1                 | 
+------------------------------+---------------+
|too-few-public-methods      |1                 |
+------------------------------+---------------+
|protected-access            |1                 |
+------------------------------+---------------+
|no-self-argument            |1                 |
+------------------------------+---------------+
|import-error                |1                 |
+------------------------------+---------------+

------------------------------------------------------------------------------------------
Your code has been rated at 7.59/10 (previous run: 7.59/10, +0.00)

Программа имеет свою внутреннюю маркировку проблемных мест в коде:

[R]efactor — требуется рефакторинг,
[C]onvention — нарушено следование стилистике и соглашениям,
[W]arning — потенциальная ошибка,
[E]rror — ошибка,
[F]atal — ошибка, которая препятствует дальнейшей работе программы.

Для вывода подробного отчета мы использовали ключ командной строки —reports=y.
Более гибко настроить вывод команды позволяют разнообразные ключи командной строки. Настройки можно сохранять в файле настроек rcfile. Мы не будем приводить подробное описание ключей и настроек, для этого есть официальная документация — https://pylint.readthedocs.io/en/latest/index.html#, остановимся лишь на наиболее интересных, с нашей точки зрения, возможностях утилиты:

— Генерация файла настроек (—generate-rcfile). Позволяет не писать конфигурационный файл с нуля. В созданном rcfile содержатся все текущие настройки с подробными комментариями к ним, вам остается только отредактировать его под собственные требования.

— Отключение вывода в коде. При редактировании кода есть возможность вставить блокирующие вывод сообщений комментарии. Чтобы продемонстрировать это, в определение функции в файле примера example.py добавим строку:

# pylint: disable=unused-argument

и запустим pylint. Из результатов проверки “исчезло” сообщение:

example.py:4:17: W0613: Unused argument 'num_two' (unused-argument)

— Создание отчетов в формате json (—output-format=json). Полезно, если необходимо сохранение или дальнейшая обработка результатов работы линтера. Вы также можете создать собственный формат вывода данных.

— Параллельный запуск (-j 4). Запуск в нескольких параллельных потоках на многоядерных процессорах сокращает время проверки.

— Встроенная документация. Вызов программы с ключом —help-msg=<key> выведет справку по ключевому слову key. В качестве ключевого слова может быть код сообщения (например: E0401) или символическое имя сообщения (например: import-error). Ниже приведен листинг получения справки по ключу import-error:

$ python3.6 -m pylint --help-msg=import-error
:import-error (E0401): *Unable to import %s*
Used when pylint has been unable to import a module. This message belongs to
the imports checker.

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

— Плагины — отличная возможность изменять поведение pylint. Их применение может оказаться полезным в случаях, когда pylint неправильно обрабатывает код и есть “ложные” срабатывания, или когда требуется отличный от стандартного формат вывода результатов.

Vulture

Vulture — небольшая утилита для поиска “мертвого” кода в программах Python. Она использует модуль ast стандартной библиотеки и создает абстрактные синтаксические деревья для всех файлов исходного кода в проекте. Далее осуществляется поиск всех объектов, которые были определены, но не используются. Vulture полезно применять для очистки и нахождения ошибок в больших базовых кодах.

Продолжение следует

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

Еще статьи по Python

  • 26 полезных возможностей Python: букварь разработки от А до Z;
  • ТОП-15 трюков в Python 3, делающих код понятнее и быстрее;
  • Новый Python: 7 возможностей, которые вам понравятся;
  • Крупнейшая подборка Python-каналов на Youtube;
  • Изучение Python: ТОП-10 вопросов разной направленности.

Check your Python code security before your next PR commit and get alerts of critical bugs using our free online Python code checker — powered by Snyk Code.

Sign up for unlimited checks, no credit card required.

How to use the free code checker

Get code security right from your IDE

This free code checker can find critical vulnerabilities and security issues with a click. To take your application security to the next level, we recommend using Snyk Code for free right from your IDE.

Python code security powered by Snyk Code

This free web based Python code checker is powered by Snyk Code. Sign up now to get access to all the features including vulnerability alerts, real time scan results, and actionable fix advice within your IDE.

Learn about Snyk Code

Human-in-the-Loop Python Code Checker

Snyk Code is an expert-curated, AI-powered Python code checker that analyzes your code for security issues, providing actionable advice directly from your IDE to help you fix vulnerabilities quickly.

Real-time

Scan and fix source code in minutes.

Actionable

Fix vulns with dev friendly remediation.

Integrated in IDE

Find vulns early to save time & money.

Ecosystems

Integrates into existing workflow.

More than syntax errors

Comprehensive semantic analysis.

AI powered by people

Modern ML directed by security experts.

In-workflow testing

Automatically scan every PR and repo.

CI/CD security gate

Integrate scans into the build process.

Frequently asked questions

A code checker is automated software that statically analyzes source code and detects potential issues. More specifically, an online code checker performs static analysis to surface issues in code quality and security. Most code checkers provide in-depth insights into why a particular line of code was flagged to help software teams implement coding best practices. These code-level checks often measure the syntax, style, and documentation completeness of source code.

What are the benefits of an AI-powered Python code checker?

An AI-powered Python code checker allows organizations to detect and remediate more complex code issues earlier in the secure software development lifecycle (SSDLC). AI algorithms that have been trained by hundreds of thousands of open source projects to capture symbolic AI rules about possible issues and remediation. By leveraging this learned knowledge from the global open source development community, an AI engine can often detect quality and security issues that may not be caught during peer code reviews or pair programming. That means the efficiency of an AI-powered Python code checker enables developers to fix issues very early — before they reach production and potentially impact end-users.

Why is a Python code checker vital to secure development?

A key part of DevSecOps is shifting left — or detecting and remediating vulnerabilities earlier in the development process. Implementing a Python code checker into your existing continuous integration and continuous delivery (CI/CD) pipeline is one of the most widely accepted best practices. Embedding static analysis into the IDE informs developers of Python vulnerabilities at the earliest possible moment — eliminating Python code security risks at the source.

What is a syntax error in Python?

A Python syntax error is an issue that occurs when Python code is interpreted during execution. Syntax errors are one of three basic types of error, and are almost always fatal because the Python interpreter cannot understand a line of code. Logic errors occur when the code is valid, but the application doesn’t do what the developer intended. Exceptions occur when the Python parser understands a line of code, but the interpreter is unable to execute it during runtime.

Common Python syntax and logical errors

There are a variety of syntax and logical errors, so it’s important to know how to remediate the most common issues that a debugger or code checker may flag. While logical errors aren’t recognized by the Python interpreter, they still prevent the application from performing as the developer originally intended. Here are some tips to avoid some common logical flaws when writing Python code:

  • Remember to invoke a function to start the execution of the program.
  • Check for infinite loops where the program gets stuck in a recurring code block.
  • Use print statements to understand the flow of execution and ensure it’s correct.
  • Avoid complex expressions that make code harder to read and debug.

How to use a Python code checker to improve code quality and security practices

Integrating a Python code checker into the existing developer workflow is a great way to fix code issues earlier, while also helping developers learn about best practices. This can make a significant impact on the quality and security of Python code that developers write going forward. More maintainable code can also improve the customer experience because there are fewer bugs and technical debt to deal with in the future.When it comes to static application security testing (SAST) with a Python code checker, it’s important to choose a developer-first tool that integrates into developer workflows and produces minimal false positives in scan results. A SAST tool also needs to take a comprehensive approach for scanning source code, and be able to combine with linters to check code syntax and style.The most common types of SAST security analysis are:CONFIGURATION:
Ensures that application configuration files are following security best practices and policies.SEMANTIC:
Examines code contextually to estimate what the developer intended, and check whether the code syntax differs.DATA FLOW:
Tracks the flow of data from insecure sources to ensure it’s cleansed before consumption by the Python application.STRUCTURAL:
Determines whether there are inconsistencies with implementing language-specific best practices and cryptographic techniques.The Python code checker you use should also leverage a comprehensive vulnerability database to identify security issues at the code level, as well as known vulnerabilities introduced via open source dependencies.Vulnerability databases help developers stay on top of the latest security exploits as they’re discovered, without spending endless hours researching the current cyber threat landscape. This type of data-driven security works in tandem with threat intelligence to improve the overall security posture of your organization.Finally, detecting Python code security issues is only half the battle. An effective code checker solution will identify flaws, while also giving developers the insights they need to remediate them. This should include the precise source of the issue, and any known publicly available fixes for both security flaws and code anti-patterns.

What is Python code security?

Python code security can be described using the CIA triad — confidentiality, integrity, and availability. The CIA triad is often used as a model for secure systems, and to identify possible vulnerabilities and fixes. Today, applications consist of 80 to 90% open source dependencies. But the remaining 10 to 20% is critical: this code reflects your personal IP, and there is no open source community helping you keep it secure. The best practice is to accept the work of the open source community by scanning and updating software dependencies in your project using scanners like Snyk Open Source — while doing your part by scanning and fixing your code using Snyk Code.Confidentiality
Secure software systems do not disclose information to parties that are not allowed to receive it. That includes malicious external actors as well as unauthorized internal stakeholders.Integrity
Secure software systems make sure that data and processes are not tempered with, destroyed, or altered. Transactions succeed when all sub-transactions succeed, and the stored data does not contradict each other.Availability
A secure system also needs to be able to be used in due time. Blocking a system by overloading parts of it renders the system useless and insecure.

What is Python code quality?

Python code quality is a subjective term, and means something different to every development team. In general, however, the quality of code relates to how closely it follows commonly accepted coding standards and best practices. Here are five frequently used measures of code quality to consider when developers ask, how do I check my Python code?

  1. Reusability

It’s best to write code that’s highly reusable. For example, in object-oriented programming, it’s important to make classes and methods clean and modular, so that code is easier to debug and scale across projects. Restricting access to certain reusable blocks of code through encapsulation can also improve security.

  1. Maintainability

Along with being reusable, it’s important that Python source code is maintainable. As a codebase grows, complexity and technical debt often increase, leading to bugs that are difficult to pinpoint and slow development in the long run. Automated code analysis and peer reviews can ensure that developers are only pushing highly maintainable code into production.

  1. Testability

High-quality Python code should support testing efforts. Along with writing modular code that makes automated testing easier, developers need to prioritize clear and up-to-date documentation. This allows test engineers to more easily understand the purpose of a particular code snippet.

  1. Consistency

Python code should be portable enough that it can run on any development, staging, or production environment without compatibility issues. Docker and other containerization platforms can help ensure Python code and dependencies are consistent across different deployment environments.

  1. Reliability

Software should be designed for reliability from the start. Meaning developers need to proactively prevent technical debt from accruing when they push Python code. Otherwise, software can become less reliable over time and have a decrease in availability, fault tolerance, data integrity, and ability to recover from outages. These lack of reliability can also have a negative impact on the security posture of an application.Perform a semantic check and secure your Python code in your IDE.
Secure your code as you develop. Snyk’s free IDE plugins scan your Python code for vulnerabilities in real-time and provide fix advice.

Анализ кода в Python может быть трудной темой, но очень полезной в тех случаях, когда вам нужно повысить производительность вашей программы. Существует несколько анализаторов кода для Python, которые вы можете использовать для проверки своего кода и выяснить, соответствует ли он стандартам. Самым популярным можно назвать pylint. Он очень удобен в настойках и подключениях. Он также проверяет ваш код на соответствие с PEP8, официальным руководством по стилю ядра Python, а также ищет программные ошибки. Обратите внимание на то, что pylint проверяет ваш код на большую часть стандартов PEP8, но не на все. Также мы уделим наше внимание тому, чтобы научиться работать с другим анализатором кода, а именно pyflakes.

Начнем с pylint

Пакет pylint не входит в Python, так что вам нужно будет посетить PyPI (Python Package Index), или непосредственно сайт пакета для загрузки. Вы можете использовать следующую команду, которая сделает всю работу за вас:

Если все идет по плану, то pylint установится, и мы сможем пойти дальше.

Анализ вашего кода

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

c:Python34Scriptspylint

Теперь нам нужен какой-нибудь код для анализа. Вот часть кода, которая содержит четыре ошибки. Сохраните её в файле под названием crummy_code.py:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

import sys

class CarClass:

    «»»»»»

    def __init__(self, color, make, model, year):

        «»»Constructor»»»

        self.color = color

        self.make = make

        self.model = model

        self.year = year

        if «Windows» in platform.platform():

            print(«You’re using Windows!»)

        self.weight = self.getWeight(1, 2, 3)

    def getWeight(this):

        «»»»»»

        return «2000 lbs»

Можете увидеть ошибки не запуская код? Давайте посмотрим, может ли pylint найти их!

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

Паблик VK

Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!

После запуска этой команды вы увидите большую выдачу на вашем экране. Вот частичный пример:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

c:py101>c:Python34Scriptspylint crummy_code.py

No config file found, using default configuration

************* Module crummy_code

C: 2, 0: Trailing whitespace (trailing-whitespace)

C: 5, 0: Trailing whitespace (trailing-whitespace)

C: 12, 0: Trailing whitespace (trailing-whitespace)

C: 15, 0: Trailing whitespace (trailing-whitespace)

C: 17, 0: Trailing whitespace (trailing-whitespace)

C: 1, 0: Missing module docstring (missing-docstring)

C: 3, 0: Empty class docstring (empty-docstring)

C: 3, 0: Old-style class defined. (old-style-class)

E: 13,24: Undefined variable ‘platform’ (undefined-variable)

E: 16,36: Too many positional arguments for function call (too-many-function-args)

C: 18, 4: Invalid method name «getWeight» (invalid-name)

C: 18, 4: Empty method docstring (empty-docstring)

E: 18, 4: Method should have «self» as first argument (no-self-argument)

R: 18, 4: Method could be a function (no-self-use)

R: 3, 0: Too few public methods (1/2) (too-few-public-methods)

W: 1, 0: Unused import sys (unused-import)

Давайте немного притормозим и разберемся. Сначала нам нужно понять, что означают буквы:

  • С – конвенция (convention)
  • R – рефакторинг (refactor)
  • W – предупреждение (warning)
  • E – ошибка (error)

Наш pylint нашел 3 ошибки, 4 проблемы с конвенцией, 2 строки, которые нуждаются в рефакторинге и одно предупреждение. Предупреждение и 3 ошибки – это как раз то, что я искал. Мы попытаемся исправить этот код и устранить ряд проблем. Для начала мы наведем порядок в импортах, и изменить функцию getWeight на get_weight, в связи с тем, что camelCase не используется в названиях методов. Нам также нужно исправить вызов get_weight, чтобы он передавал правильное количество аргументов и исправить его, чтобы “self” выступал в качестве первого аргумента. Взглянем на новый код:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

# crummy_code_fixed.py

import platform

class CarClass:

    «»»»»»

    def __init__(self, color, make, model, year):

        «»»Constructor»»»

        self.color = color

        self.make = make

        self.model = model

        self.year = year

        if «Windows» in platform.platform():

            print(«You’re using Windows!»)

        self.weight = self.get_weight(3)

    def get_weight(self, this):

        «»»»»»

        return «2000 lbs»

Давайте запустим новый код с pylint и посмотрим, насколько успешно мы провели работу. Для краткости, мы еще раз рассмотрим первую часть:

c:py101>c:Python34Scriptspylint crummy_code_fixed.py

No config file found, using default configuration

************* Module crummy_code_fixed

C: 1,0: Missing docstring

C: 4,0: CarClass: Empty docstring

C: 21,4: CarClass.get_weight: Empty docstring

W: 21,25: CarClass.get_weight: Unused argument ‘this’

R: 21,4: CarClass.get_weight: Method could be a function

R: 4,0: CarClass: Too few public methods (1/2)

Как мы видим, это очень помогло. Если мы добавим docstrings, мы можем снизить количество ошибок вдвое. Теперь мы готовы перейти к pyflakes!

Работаем с pyflakes

Проект pyflakes это часть чего-то, что называется Divmod Project. Pyflakes на самом деле не выполняет проверяемый код также, как и pylint. Вы можете установить pyflakes при помощи pip, easy_install, или из другого источника.

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

Мы начнем с запуска pyflakes в изначальной версии той же части кода, которую мы использовали для проверки pylint. Вот и он:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

import sys

class CarClass:

    «»»»»»

    def __init__(self, color, make, model, year):

        «»»Constructor»»»

        self.color = color

        self.make = make

        self.model = model

        self.year = year

        if «Windows» in platform.platform():

            print(«You’re using Windows!»)

        self.weight = self.getWeight(1, 2, 3)

    def getWeight(this):

        «»»»»»

        return «2000 lbs»

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

c:py101>c:Python34Scriptspyflakes.exe crummy_code.py

crummy_code.py:1: ‘sys’ imported but unused

crummy_code.py:13: undefined name ‘platform’

Несмотря на суперски быструю скорость возврата выдачи, pyflakes не нашел все ошибки. Вызов метода getWeight передает слишком много аргументов, также метод getWeight сам по себе определен некорректно, так как у него нет аргумента self. Что-же, вы, собственно, можете называть первый аргумент так, как вам угодно, но в конвенции он всегда называется self. Если вы исправили код, оперируя тем, что вам сказал pyflakes, код не заработает, несмотря на это.

Подведем итоги

Следующим шагом должна быть попытка запуска pylint и pyflakes в вашем собственном коде, либо же в пакете Python, вроде SQLAlchemy, после чего следует изучить полученные в выдаче данные. Вы можете многое узнать о своем коде, используя данные инструменты. pylint интегрирован с Wingware, Editra, и PyDev. Некоторые предупреждения pylint могут показаться вам раздражительными, или не особо уместными. Существует несколько способов избавиться от таких моментов, как предупреждения об устаревании, через опции командной строки. Вы также можете использовать -generate-rcfile для создания примера файла config, который поможет вам контролировать работу pylint. Обратите внимание на то, что pylint и pyflakes не импортируют ваш код, так что вам не нужно беспокоиться о нежелательных побочных эффектах.

Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.

E-mail: vasile.buldumac@ati.utm.md

Образование
Universitatea Tehnică a Moldovei (utm.md)

  • 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»

Линтеры

В сообществе Python, как и в любой другой группе людей, существует некое
коллективное знание. Множество людей прошлось по всем возможным граблям
и получило опыт через набитые шишки. Затем через какое-то время,
благодаря выступлениям на конференциях, официальным заявлениям,
документам, статьям в блогах, код-ревью и личному общению,
это знание стало коллективным. Теперь мы просто называем его
“хорошими практиками”.

К таким хорошим практикам можно отнести, например, следующие.

  • Форматировать код по PEP8
    — если этого не делать, то другим людям будет намного сложнее понимать
    ваш код; в плохо оформленном коде сложнее увидеть суть,
    потому что мозг постоянно отвлекается на не несущие смысловой нагрузки
    особенности оформления.
  • Не допускать объявленных, но неиспользуемых переменных/функций/импортов
    — опять же, это усложняет восприятие кода; читателю потребуется потратить
    время на то, чтобы осознать, что вот на эту сущность обращать внимания не
    нужно.
  • Писать короткие функции — слишком сложные функции с большим
    количеством ветвлений и циклов тяжело понимать.
  • Не использовать изменяемый объект в качестве значения аргумента
    функции по умолчанию — иначе в результате можно получить
    очень неожиданные эффекты.

Соблюдать (и даже просто помнить) все хорошие практики — не самая простая
задача. Зачастую люди плохо справляются с тем, чтобы отсчитывать пробелы
и контролировать переменные, и вообще склонны допускать ошибки по
невнимательности. Таковы люди, ничего не поделаешь. Машины, наоборот,
прекрасно справляются с такими хорошо определёнными задачами, поэтому
появились инструменты, которые контролируют следование хорошим практикам.

В компилируемых языках ещё на этапе компиляции программист может получить
по щщам первый полезный фидбэк о написанном коде.
Компилятор проверит, что код валиден и может быть скомпилирован, а также может
выдать предупреждения и рекомендации, как сделать код лучше или читаемее.
Т.к. Python является интерпретируемым языком, где этап компиляции как таковой
отсутствует, линтеры особенно полезны. На самом деле, это очень важно и
круто — узнать, что твой код как минимум является валидным Python-кодом,
даже не запуская его.

В этом посте я рассмотрю два самых популярных линтера для Python:

  • flake8;
  • pylint.

Термин “lint” впервые начал использоваться в таком значении в 1979 году.
Так называлась программа для статического анализа кода на C,
которая предупреждала об использовании непортабельных на другие архитектуры
языковых конструкций. С тех пор “линтерами” называют любые статические
анализаторы кода, которые помогают находить распространённые ошибки, делать
его однообразным и более читаемым. А названо оно «lint» в честь вот такой
штуки:

lint roller

flake8

flake8 — это утилита-комбайн, которая органично объединяет в себе несколько
других анализаторов кода (pycodestyle, pyflakes и mccabe), а также
имеет огромную экосистему плагинов, которые могут добавить к стандартной
поставке ещё кучу различных проверок. На данный момент, это самый
популярный линтер для Python-кода. Кроме того, он предельно прост в
настройке и использовании.

Установка

flake8 устанавливается, как и любой другой Python-пакет,
через pip. Внутри виртуального окружения проекта выполните:

Если вы пользуетесь pipenv, то flake8 нужно устанавливать
как dev-зависимость (ведь для работы программы линтер не нужен,
он нужен только для разработчика):

$ pipenv install --dev flake8

Аналогично с poetry:

$ poetry add --dev flake8

Проверим установку:

$ flake8 --version
3.8.1 (mccabe: 0.6.1, pycodestyle: 2.6.0, pyflakes: 2.2.0) CPython 3.8.2 on Linux

Использование

Для работы flake8 нужно просто указать файл или директорию, которые
нужно проверять, например:

# проверить один файл
$ flake8 file.py

# проверить директорию рекурсивно 
$ flake8 src/

# проверить текущую директорию рекурсивно
$ flake8 .

Давайте для демонстрации попытаемся написать программу с как можно большим
количеством “плохих практик”:

Возможно, вам не видно всего, но в этом коде точно есть следующие «запахи кода»:

  • import * — импортирование всех имен из модуля, хотя используется
    из них только одно;
  • import itertools — ненужный импорт;
  • во множестве мест стоят лишние или отсутствующие пробелы;
  • название функции написано в стиле PascalCase;
  • в некоторых местах используются табы для отступов;
  • используется список (изменяемый объект) в качестве значения аргумента
    функции по умолчанию;
  • используется слишком “широкое” выражение except: без указания
    конкретного исключения.

Давайте посмотрим, что flake8 скажет по поводу этого файла:

$ flake8 bad_code.py
bad_code.py:1:1: F403 'from math import *' used; unable to detect undefined names
bad_code.py:2:1: F401 'itertools' imported but unused
bad_code.py:4:1: E302 expected 2 blank lines, found 1
bad_code.py:4:4: E271 multiple spaces after keyword
bad_code.py:4:25: E211 whitespace before '('
bad_code.py:4:33: E202 whitespace before ')'
bad_code.py:5:1: W191 indentation contains tabs
bad_code.py:5:8: E271 multiple spaces after keyword
bad_code.py:5:10: F405 'sqrt' may be undefined, or defined from star imports: math
bad_code.py:5:21: E202 whitespace before ')'
bad_code.py:7:1: E302 expected 2 blank lines, found 1
bad_code.py:7:23: E741 ambiguous variable name 'l'
bad_code.py:8:1: E101 indentation contains mixed spaces and tabs
bad_code.py:9:1: E101 indentation contains mixed spaces and tabs
bad_code.py:11:1: E305 expected 2 blank lines after class or function definition, found 1
bad_code.py:12:1: E101 indentation contains mixed spaces and tabs
bad_code.py:13:1: E101 indentation contains mixed spaces and tabs
bad_code.py:13:20: E225 missing whitespace around operator
bad_code.py:14:1: E101 indentation contains mixed spaces and tabs
bad_code.py:14:67: W291 trailing whitespace
bad_code.py:15:1: E101 indentation contains mixed spaces and tabs
bad_code.py:15:14: W291 trailing whitespace
bad_code.py:16:1: E101 indentation contains mixed spaces and tabs
bad_code.py:16:5: E722 do not use bare 'except'
bad_code.py:17:1: E101 indentation contains mixed spaces and tabs

Как видите, flake8 нашёл кучу ошибок. Для каждой ошибки указана строка
и номер символа в строке (не всегда точный), где произошла ошибка.
Также у каждой категории ошибок есть свой код: E101, W291 и т.д.
Эти коды ошибок могут использоваться для включения/отключения правил.
Тем не менее, не все ошибки были найдены. Давайте установим пару плагинов,
чтобы добавить ещё правил!

Плагины

Как я уже говорил, для flake8 написано множество плагинов.
Обычно плагины легко гуглятся или находятся в списках плагинов.
Есть плагины для всех популярных фреймворков и библиотек — пользуйтесь ими!
Давайте для нашего простого примера установим
flake8-bugbear
(находит распространённые логические ошибки) и
pep8-naming
(проверяет имена на соответствие PEP8).

Плагины устанавливаются так же, как и сам flake8 (для краткости я
не буду писать примеры для pipenv и poetry — сами сможете обобщить):

$ pip install flake8-bugbear pep8-naming

Давайте убедимся, что плагины действительно установились
и flake8 может их найти:

$ flake8 --version
3.8.1 (flake8-bugbear: 20.1.4, mccabe: 0.6.1, naming: 0.10.0, pycodestyle: 2.6.0, pyflakes: 2.2.0) CPython 3.8.2 on Linux

Если вы видите в списке в скобках названия ваших плагинов, то всё хорошо.

Теперь снова проверим наш файл:

$ flake8 bad_code.py
bad_code.py:1:1: F403 'from math import *' used; unable to detect undefined names
bad_code.py:2:1: F401 'itertools' imported but unused
bad_code.py:4:1: E302 expected 2 blank lines, found 1
bad_code.py:4:4: E271 multiple spaces after keyword
bad_code.py:4:6: N802 function name 'CalculateSquareRoot' should be lowercase
bad_code.py:4:25: E211 whitespace before '('
bad_code.py:4:28: N803 argument name 'Number' should be lowercase
bad_code.py:4:33: E202 whitespace before ')'
bad_code.py:5:1: W191 indentation contains tabs
bad_code.py:5:8: E271 multiple spaces after keyword
bad_code.py:5:10: F405 'sqrt' may be undefined, or defined from star imports: math
bad_code.py:5:21: E202 whitespace before ')'
bad_code.py:7:1: E302 expected 2 blank lines, found 1
bad_code.py:7:23: E741 ambiguous variable name 'l'
bad_code.py:7:25: B006 Do not use mutable data structures for argument defaults.  They are created during function definition time. All calls to the function reuse this one instance of that data structure, persisting changes between them.
bad_code.py:8:1: E101 indentation contains mixed spaces and tabs
bad_code.py:9:1: E101 indentation contains mixed spaces and tabs
bad_code.py:11:1: E305 expected 2 blank lines after class or function definition, found 1
bad_code.py:12:1: E101 indentation contains mixed spaces and tabs
bad_code.py:13:1: E101 indentation contains mixed spaces and tabs
bad_code.py:13:20: E225 missing whitespace around operator
bad_code.py:14:1: E101 indentation contains mixed spaces and tabs
bad_code.py:14:67: W291 trailing whitespace
bad_code.py:15:1: E101 indentation contains mixed spaces and tabs
bad_code.py:15:14: W291 trailing whitespace
bad_code.py:16:1: E101 indentation contains mixed spaces and tabs
bad_code.py:16:5: E722 do not use bare 'except'
bad_code.py:16:5: B001 Do not use bare `except:`, it also catches unexpected events like memory errors, interrupts, system exit, and so on.  Prefer `except Exception:`.  If you're sure what you're doing, be explicit and write `except BaseException:`.
bad_code.py:17:1: E101 indentation contains mixed spaces and tabs

В выводе появились новые категории ошибок (N802, B006)
— они как раз добавлены плагинами. На этот раз, как мне кажется,
найдены все ошибки. К сожалению, flake8 не умеет сам чинить
найденные ошибки, поэтому давайте сделаем это вручную:

Обратите внимание на строки 8 и 10, там содержится комментарии # noqa.
При помощи этих комментариев можно заставить flake8 игнорировать ошибки.
Это бывает полезно, когда по какой-то причине код должен остаться именно
таким, например:

  • он автоматически сгенерирован и исправление в нём ошибок не имеет смысла;
  • исправление этой ошибки породит куда более уродливый код,
    чем комментарий # noqa;
  • у вас просто сейчас нет времени, чтобы исправлять эту ошибку
    (плохая отмазка, серьёзно).

Если не указать код ошибки, то будут проигнорированы все ошибки в строке
— я не рекомендую так делать, потому что так можно пропустить
и на самом деле плохие ошибки. Если указать номер правила, то
flake8 будет игнорировать только указанную категорию,
а о других ошибках в этой же строке доложит.
Вообще, комментариями # noqa нужно пользоваться с большой осторожностью.
Считайте, что каждый раз, когда вы это делаете, вы берёте на
себя ответственность за эту строку кода. Если программа сломается
в этом месте, то пеняйте на себя — минздрав линтер вас предупреждал.

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

flake8 для работы не требует никакой конфигурации.
Он имеет достаточно (но не слишком) строгие настройки по умолчанию,
которые подойдут большинству пользователей, но иногда бывает нужно
отключить (или наоборот включить) определённые правила на уровне всего проекта.
Сделать это можно через файлы .flake8 или setup.cfg в корне проекта.
Если у вас в проекте уже есть файл setup.cfg, то можно добавить конфигурацию
flake8 в него. Если вы предпочитаете для каждой утилиты держать
отдельный файл конфигурации, то используйте .flake8. В любом случае,
формат для обоих этих файлов совпадает:

[flake8]
ignore = D203,E741
exclude =
    # No need to traverse our git directory
    .git,
    # There's no value in checking cache directories
    __pycache__,
    # The conf file is mostly autogenerated, ignore it
    docs/source/conf.py,
    # The old directory contains Flake8 2.0
    old,
    # This contains our built documentation
    build,
    # This contains builds of flake8 that we don't want to check
    dist
max-complexity = 10

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

Если же вам не хватает какого-нибудь правила, и его нет даже в уже
готовых плагинах, то написание собственного плагина
— не такая уж и сложная задача.
Я попробовал,
у меня на это ушло 2-3 часа.

pylint

pylint — это ещё один популярный линтер для Python.
Этот линтер значительно умнее и продвинутее flake8.
В pylint из коробки заложено очень много правил и рекомендаций,
и по умолчанию они все включены, так что он достаточно строгий и придирчивый.
Чтобы интегрировать его в существующий большой проект придётся потратить
некоторое время, чтобы выбрать те правила, которые для вас важны.
Так же как и flake8, pylint поддерживает плагины для расширения
базовой функциональности, но насколько я вижу, экосистема плагинов у pylint
значительно беднее.

Также при каждом запуске pylint выводит оценку качества кода
по десятибалльной шкале, а также следит, как эта оценка меняется
с течением времени. Достичь десятки очень сложно, но это благородная цель,
к которой нужно стремиться.

Установка

Установка pylint принципиально ничем не отличается от установки flake8.
Выполнить внутри виртуального окружения проекта:

Для pipenv:

$ pipenv install --dev pylint

Для poetry:

$ poetry add --dev pylint

Использование

pylint можно натравить на определённый файл:

С директориями у pylint дела обстоят чуть сложнее. Все директории он
обрабатывает как питоновские модули, поэтому если в директории нет хотя бы
пустого файла __init__.py, то работать с ней pylint не сможет. Имейте
это ввиду.

Давайте попросим pylint прокомментировать файл с плохими практиками
из предыдущего примера:

$ pylint bad_code.py
************* Module bad_code
bad_code.py:4:25: C0326: No space allowed before bracket
def  CalculateSquareRoot (Number ):
                         ^ (bad-whitespace)
bad_code.py:4:33: C0326: No space allowed before bracket
def  CalculateSquareRoot (Number ):
                                 ^ (bad-whitespace)
bad_code.py:5:0: W0312: Found indentation with tabs instead of spaces (mixed-indentation)
bad_code.py:5:21: C0326: No space allowed before bracket
    return  sqrt(Number )
                     ^ (bad-whitespace)
bad_code.py:13:19: C0326: Exactly one space required around assignment
        your_number=float(input('Enter your number: '))
                   ^ (bad-whitespace)
bad_code.py:14:66: C0303: Trailing whitespace (trailing-whitespace)
bad_code.py:15:13: C0303: Trailing whitespace (trailing-whitespace)
bad_code.py:1:0: W0622: Redefining built-in 'pow' (redefined-builtin)
bad_code.py:1:0: C0114: Missing module docstring (missing-module-docstring)
bad_code.py:1:0: W0401: Wildcard import math (wildcard-import)
bad_code.py:4:0: C0103: Function name "CalculateSquareRoot" doesn't conform to snake_case naming style (invalid-name)
bad_code.py:4:0: C0103: Argument name "Number" doesn't conform to snake_case naming style (invalid-name)
bad_code.py:4:0: C0116: Missing function or method docstring (missing-function-docstring)
bad_code.py:7:0: W0102: Dangerous default value [] as argument (dangerous-default-value)
bad_code.py:7:0: C0103: Argument name "l" doesn't conform to snake_case naming style (invalid-name)
bad_code.py:7:0: C0116: Missing function or method docstring (missing-function-docstring)
bad_code.py:16:4: W0702: No exception type(s) specified (bare-except)
bad_code.py:1:0: W0614: Unused import acos from wildcard import (unused-wildcard-import)
bad_code.py:1:0: W0614: Unused import acosh from wildcard import (unused-wildcard-import)
bad_code.py:1:0: W0614: Unused import asin from wildcard import (unused-wildcard-import)
bad_code.py:1:0: W0614: Unused import asinh from wildcard import (unused-wildcard-import)
...
bad_code.py:2:0: W0611: Unused import itertools (unused-import)
-------------------------------------
Your code has been rated at -41.43/10

Я немного сократил вывод. Как видите, даже без плагинов pylint нашёл
все ожидаемые ошибки, и даже больше — например, он даже предлагает написать
документацию.

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

$ pylint --help-msg=missing-docstring
$ pylint --help-msg=R0902

Вот какие ошибки pylint находит для файла, который с точки зрения flake8
не содержит никаких ошибок:

$ pylint not_so_bad_code.py 
************* Module not_so_bad_code
not_so_bad_code.py:1:0: C0114: Missing module docstring (missing-module-docstring)
not_so_bad_code.py:4:0: C0116: Missing function or method docstring (missing-function-docstring)
not_so_bad_code.py:8:0: C0103: Argument name "l" doesn't conform to snake_case naming style (invalid-name)
not_so_bad_code.py:8:0: C0116: Missing function or method docstring (missing-function-docstring)
not_so_bad_code.py:20:11: W0703: Catching too general exception Exception (broad-except)
-----------------------------------
Your code has been rated at 6.67/10

А вот так в pylint можно игнорировать отдельную ошибку на строке прямо в файлах
с кодом:

def append_item(item, l=None):  # pylint: disable=C0103
   ...

Ещё pylint умеет игнорировать ошибки в блоках кода:

def test():
    # Disable all the no-member violations in this function
    # pylint: disable=no-member
    ...

И для файлов целиком. Вот так можно отключить все ошибки из категорий
Warning, Convention и Refactor:

А можно не проверять файл вообще:

Подробнее о правилах управления сообщениями
смотрите в документации.
Для более сложной настройки правил, придётся по-настоящему сконфигурировать
pylint.

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

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

$ pylint --generate-rcfile > .pylintrc

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

Плагины

Давайте установим какой-нибудь популярный плагин, например,
pylint-django:

$ pip install pylint-django

Теперь запускать pylint нужно вот так:

$ pylint --load-plugins pylint_django [..other options..] <path_to_your_sources>

либо в .pylintrc нужно исправить директиву load-plugins:

load-plugins=pylint_django

Интеграция линтера в проект

Интегрировать линтер в проект можно на трёх уровнях.
Я рекомендую по возможности использовать все три, но обязательным
является как минимум один (лучше всего, чтобы это была CI система).

Редактор кода или IDE

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

PyCharm автоматически находить установленные flake8 и pylint внутри
интерпретатора проекта
и подключается к ним.

VS Code требует небольшой настройки, которая
описана здесь.

Git-хуки

Также читайте пост про Git-хуки и pre-commit.

В git есть возможность запрограммировать
определенные скрипты (хуки) в ответ на действия пользователя.
Например, можно запускать
какие-нибудь проверки перед коммитом, заново скачивать зависимости проекта
при переключении веток, высылать сообщение в рабочий чат
после пуша в удалённый репозиторий и вообще что угодно.

я запушель

Нас интересует возможность запускать линтер перед коммитом так,
чтобы если линтер найдёт какие-нибудь проблемы, операция коммита прерывалась.
Git-хуки можно настроить, написав несложный shell-скрипт,
но я рекомендую использовать для этого специальные утилиты,
такие как pre-commit.
Вот здесь
можно найти описание процесса настройки запуска flake8 через pre-commit.

Обратите внимание, что Git-хуки нужно будет настроить на машине каждого
разработчика в проекте.

Continuous Integration (CI)

Последний эшелоном защиты от попадания “сломанного” кода в основную ветку
репозитория является система непрерывной интеграции (CI) — такая, как:

  • GitHub Actions;
  • GitLab CI
    (а ещё читайте пост в блоге моего хорошего товарища про
    основы GitLab CI);
  • Travis CI;
  • или другая.

На каждый пуш в репозиторий система непрерывной интеграции должна
запускать проверки (включая все линтеры и тесты), и если что-то идёт
не так, рядом с коммитом должен появиться красный крестик.
Ветку с таким коммитом на конце нельзя будет слить с основной
веткой проекта через пулл-реквест на GitHub (или мёрдж-реквест на GitLab).
Пример того, как настроить GitHub Actions
для запуска flake8 и других питоновских проверок.

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

Заключение

В подзаголовке этой статьи я написал фразу, что линтер способен
сэкономить разработчику один день жизни в месяц. Фраза может показаться
кликбейтной, но, поверьте мне, это так это и работает.
Возможно, я даже преуменьшил.
Чем раньше найдена ошибка, тем быстрее идёт разработка.
Иногда линтер предотвращает баги, иногда спасает от мучительного
траблшутинга. Линтеры абсолютно точно значительно сокращают время,
потраченное коллегами на код-ревью, потому что все тривиальные
ошибки будут отловлены автоматикой.

Не стоит недооценивать линтеры. Это те инструменты,
которые делают из “кодера” настоящего “software engineer”,
из мальчика — мужчину. Если вы до сих пор не пользуетесь каким-нибудь
линтером, то рекомендую всерьез задуматься над внедрением!

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

У pylint тоже есть свои последователи. Его ценят за подробный вывод
и большое количество правил в стандартной поставке.
Мне же pylint всегда казался слишком сложным в эксплуатации.

А кто-то вообще рекомендует устанавливать flake8 и pylint параллельно.

Если понравилась статья, то
подпишитесь на уведомления
о новых постах в блоге, чтобы ничего не пропустить!

Дополнительное чтение

  • документация flake8;
  • исходный код flake8;
  • список плагинов flake8;
  • сайт, где можно посмотреть правила flake8;
  • документация pylint;
  • исходный код pylint;
  • обсуждение “flake8 vs pylint” на Reddit;
  • пост на RealPython про качество кода;
  • статья на Хабре про линтеры.

Обложка: Sa Mu, Traffic Light

Всем доброго!

От нашего стола к вашему…

То есть от нашего курса «Разработчик Python», несмотря на стремительно приближающий Новый год, мы подготовили вам интересный перевод о различных методах тестирования в Python.

Это руководство для тех, кто уже написал классное приложение на Python, но еще не писал для
них тесты.

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

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

Тестирование Кода

Тестировать код можно разными способами. В этом руководстве вы познакомитесь с методами от наиболее простых до продвинутых.

Автоматизированное vs. Ручное Тестирование

Хорошие новости! Скорее всего вы уже сделали тест, но еще не осознали этого. Помните, как вы впервые запустили приложение и воспользовались им? Вы проверили функции и поэкспериментировали с ними? Такой процесс называется исследовательским тестированием, и он является формой ручного тестирования.

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

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

Звучит безрадостно, верно?

Поэтому нужны автоматические тесты. Автоматическое тестирование — исполнение плана тестирования (части приложения, требующие тестирования, порядок их тестирования и ожидаемые результаты) с помощью скрипта, а не руками человека. В Python уже есть набор инструментов и библиотек, которые помогут создать автоматизированные тесты для вашего приложения. Рассмотрим эти инструменты и библиотеки в нашем туториале.

Модульные Тесты VS. Интеграционные Тесты

Мир тестирования полон терминов, и теперь, зная разницу между ручным и автоматизированным тестированием, опустимся на уровень глубже.

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

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

Главная сложность интеграционного тестирования возникает, когда интеграционный тест не дает правильный результат. Сложно оценить проблему, не имея возможности изолировать сломанную часть системы. Если фары не зажглись, возможно лампочки сломаны. Или может аккумулятор разряжен? А может проблема в генераторе? Или вообще сбой в компьютере машины?

Современные машины сами оповестят вас о поломке лампочек. Определяется это с помощью модульного теста.

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

Мы поговорили о двух видах тестов:

  1. Интеграционный тест, проверяющий компоненты системы и их взаимодействие друг с другом;
  2. Модульный тест, проверяющий отдельный компонент приложения.
  3. Вы можете создать оба теста на Python. Чтобы написать тест для встроенной функции sum(), нужно сравнить выходные данные sum() с известными значениями.

Например, вот так можно проверить что сумма чисел (1, 2, 3) равна 6:

>>> assert sum([1, 2, 3]) == 6, "Should be 6"

Значения правильные, поэтому в REPL ничего не будет выведено. Если результат sum() некорректный, будет выдана AssertionError с сообщением “Should be 6” (“Должно быть 6”). Проверим оператор утверждения еще раз, но теперь с некорректными значениями, чтобы получить AssertionError:

>>> assert sum([1, 1, 1]) == 6, "Should be 6"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: Should be 6

В REPL вы увидете AssertionError, так как значение sum() не равно 6.

Вместо REPL, положите это в новый Python-файл с названием test_sum.py и выполните его снова:

def test_sum():
    assert sum([1, 2, 3]) == 6, "Should be 6"

if __name__ == "__main__":
    test_sum()
    print("Everything passed")

Теперь у вас есть написанный тест-кейс (тестовый случай), утверждение и точка входа (командной строки). Теперь это можно выполнить в командной строке:

$ python test_sum.py
Everything passed

Вы видите успешный результат, “Everything passed” (“Все пройдено”).

sum() в Python принимает на вход любой итерируемый в качестве первого аргумента. Вы проверили список. Попробуем протестировать кортеж. Создадим новый файл с названием test_sum_2.py со следующим кодом:

def test_sum():
    assert sum([1, 2, 3]) == 6, "Should be 6"

def test_sum_tuple():
    assert sum((1, 2, 2)) == 6, "Should be 6"

if __name__ == "__main__":
    test_sum()
    test_sum_tuple()
    print("Everything passed")

Выполнив test_sum_2.py, скрипт выдаст ошибку, так как sum() (1, 2, 2) должен быть равен 5, а не 6. В результате скрипт выдает сообщение об ошибке, строку кода и трейсбек:

$ python test_sum_2.py
Traceback (most recent call last):
  File "test_sum_2.py", line 9, in <module>
    test_sum_tuple()
  File "test_sum_2.py", line 5, in test_sum_tuple
    assert sum((1, 2, 2)) == 6, "Should be 6"
AssertionError: Should be 6

Можно увидеть, как ошибка в коде вызывает ошибку в консоли с информацией, где она произошла, и каким был ожидаемый результат.

Такие тесты подойдут для простой проверки, но что если ошибки есть больше, чем в одном? На помощь приходят исполнители тестов (test runners). Исполнитель тестов — особое приложение, спроектированное для проведение тестов, проверки данных вывода и предоставления инструментов для отладки и диагностики тестов и приложений.

Выбор Исполнителя Тестов

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

  • unittest;
  • nose или nose2;
  • pytest.

Важно выбрать исполнитель тестов, соответствующий вашим требованиям и опытности.

unittest

unittest встроен в стандартную библиотеку Python, начиная с версии 2.1. Вы наверняка столкнетесь с ним в коммерческих приложениях Python и проектах с открытым исходным кодом.
В unittest есть тестовый фреймворк и исполнитель тестов. При написании и исполнении тестов нужно соблюдать некоторые важные требования.

unittest требует:

  • Помещать тесты в классы, как методы;
  • Использовать специальные методы утверждения. Класс TestCase вместо обычного встроенного выражения assert.

Чтобы превратить ранее написанный пример в тест-кейс unittest, необходимо:

  1. Импортировать unittest из стандартной библиотеки;
  2. Создать класс под названием TestSum, который будет наследовать класс TestCase;
  3. Сконвертировать тестовые функции в методы, добавив self в качестве первого аргумента;
  4. Изменить утверждения, добавив использование self.assertEqual() метода в классе TestCase;
  5. Изменить точку входа в командной строке на вызов unittest.main().

Следуя этим шагам, создайте новый файл test_sum_unittest.py со таким кодом:

import unittest


class TestSum(unittest.TestCase):

    def test_sum(self):
        self.assertEqual(sum([1, 2, 3]), 6, "Should be 6")

    def test_sum_tuple(self):
        self.assertEqual(sum((1, 2, 2)), 6, "Should be 6")

if __name__ == '__main__':
    unittest.main()

Выполнив это в командной строке, вы получите одно удачное завершение (обозначенное .) и одно неудачное (обозначенное F):

$ python test_sum_unittest.py
.F
======================================================================
FAIL: test_sum_tuple (__main__.TestSum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_sum_unittest.py", line 9, in test_sum_tuple
    self.assertEqual(sum((1, 2, 2)), 6, "Should be 6")
AssertionError: Should be 6

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

Таким образом, вы выполнили два теста с помощью исполнителя тестов unittest.

Примечание: Если вы пишете тест-кейсы для Python 2 и 3 — будьте осторожны. В версиях Python 2.7 и ниже unittest называется unittest 2. При импорте из unittest вы получите разные версии с разными функциями в Python 2 и Python 3.

Чтобы узнать больше о unittest’ах почитайте unittest документацию.

nose

Со временем, после написания сотни, а то и тысячи тестов для приложения, становится все сложнее понимать и использовать данные вывода unittest.

nose совместим со всеми тестами, написанными с unittest фреймворком, и может заменить его тестовый исполнитель. Разработка nose, как приложения с открытым исходным кодом, стала тормозиться, и был создан nose2. Если вы начинаете с нуля, рекомендуется использовать именно nose2.

Для начала работы с nose2 нужно установить его из PyPl и запустить в командной строке. nose2 попытается найти все тестовые скрипы с test*.py в названии и все тест-кейсы, унаследованные из unittest.TestCase в вашей текущей директории:

$ pip install nose2
$ python -m nose2
.F
======================================================================
FAIL: test_sum_tuple (__main__.TestSum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_sum_unittest.py", line 9, in test_sum_tuple
    self.assertEqual(sum((1, 2, 2)), 6, "Should be 6")
AssertionError: Should be 6

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

Так выполняется тест, созданный в test_sum_unittest.py, из исполнителя тестов nose2. nose2 предоставляет множество флагов командной строки для фильтрации исполняемых тестов. Чтобы узнать больше, советуем ознакомиться с документацией Nose 2.

pytest

pytest поддерживает выполнение тест-кейсов unittest. Но настоящее преимущество pytest — его тест-кейсы. Тест-кейсы pytest — серия функций в Python-файле с test_ в начале названия.

Есть в нем и другие полезные функции:

  • Поддержка встроенных выражений assert вместо использования специальных self.assert*() методов;
  • Поддержка фильтрации тест-кейсов;
  • Возможность повторного запуска с последнего проваленного теста;
  • Экосистема из сотен плагинов, расширяющих функциональность.

Пример тест-кейса TestSum для pytest будет выглядеть следующим образом:

def test_sum():
    assert sum([1, 2, 3]) == 6, "Should be 6"

def test_sum_tuple():
    assert sum((1, 2, 2)) == 6, "Should be 6"

Вы избавились от TestCase, использования классов и точек входа командной строки.
Больше информации можно найти на Сайте Документации Pytest.

Написание Первого Теста

Объединим все, что мы уже узнали, и вместо встроенной функции sum() протестируем простую реализацию с теми же требованиями.

Создайте новую папку для проекта, внутри которой создайте новую папку с названием my_sum. Внутри my_sum создайте пустой файл с названием _init_.py. Наличие этого файла значит, что папка my_sum может быть импортирована в виде модуля из родительской директории.

Структура папок будет выглядеть так:

project/

└── my_sum/
└── __init__.py

Откройте my_sum/__init__.py и создайте новую функцию с названием sum(), которая берет на вход итерируемые (список, кортеж, множество) и складывает значения.

def sum(arg):
    total = 0
    for val in arg:
        total += val
    return total

В этом примере создается переменная под названием total, перебираются все значения в arg и добавляются к total. Затем, по завершении итерации, результат возвращается.

Где Писать Тест

Начать написание теста можно с создания файла test.py, в котором будет содержаться ваш первый тест-кейс. Для тестирования у файла должна быть возможность импортировать ваше приложение, поэтому положите test.py в папку над пакетом. Дерево каталогов будет выглядеть следующим образом:

project/

├── my_sum/
│ └── __init__.py
|
└── test.py

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

Примечание: А что есть ваше приложение представляет собой один скрипт?
Вы можете импортировать любые атрибуты скрипта: классы, функции или переменные, с помощью встроенной функции __import__(). Вместо from my_sum import sum напишите следующее:

target = __import__("my_sum.py")
sum = target.sum

При использовании __import__() вам не придется превращать папку проекта в пакет, и вы сможете указать имя файла. Это полезно, если имя файла конфликтует с названиями стандартных библиотек пакетов. Например, если math.py конфликтует с math модулем.

Как Структурировать Простой Тест

Перед написанием тестов, нужно решить несколько вопросов:

  1. Что вы хотите протестировать?
  2. Вы пишете модульный тест или интеграционный тест?

Сейчас вы тестируете sum(). Для него можно проверить разные поведения, например:

  • Можно ли суммировать список целых чисел?
  • Можно ли суммировать кортеж или множество?
  • Можно ли суммировать список чисел с плавающей точкой?
  • Что будет, если дать на вход плохое значение: одно целое число или строку?
  • Что будет, если одно из значений отрицательное?

Проще всего тестировать список целых чисел. Создайте файл test.py со следующим кодом:

import unittest

from my_sum import sum


class TestSum(unittest.TestCase):
    def test_list_int(self):
        """
        Test that it can sum a list of integers
        """
        data = [1, 2, 3]
        result = sum(data)
        self.assertEqual(result, 6)

if __name__ == '__main__':
    unittest.main()

Код в этом примере:

  • Импортирует sum() из пакета my_sum(), который вы создали;
  • Определяет новый класс тест-кейса под названием TestSum, наследующий unittest.TestCase;
  • Определяет тестовый метод .test_list_int() для тестирования целочисленного списка. Метод .test_list_int() сделает следующее

:

  1. Объявит переменную data со списком значений (1, 2, 3);
  2. Присвоит значение my_sum.sum(data) переменной result;
  3. Определит, что значение result равно 6 с помощью метода .assertEqual() на unittest.TestCase классе.

  • Определяет точку входа командной строки, которая запускает исполнителя теста unittest .main().

Если вы не знаете, что такое self, или как определяется .assertEqual(), то можете освежить знания по объектно-ориентированному программированию с Python 3 Object-Oriented Programming.

Как Писать Утверждения

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

  • Проверьте, что тесты повторяемы и запустите их несколько раз, чтобы убедиться, что каждый раз они дают одни и те же результаты;
  • Проверьте и подтвердите результаты, которые относятся к вашим входным данным — проверьте, что результат действительно является суммой значений в примере sum().

В unittest есть множество методов для подтверждения значений, типов и существования переменных. Вот некоторые из наиболее часто используемых методов:

Метод Эквивалент
.assertEqual(a, b) a == b
.assertTrue(x) bool(x) is True
.assertFalse(x) bool(x) is False
.assertIs(a, b) a is b
.assertIsNone(x) x is None
.assertIn(a, b) a in b
.assertIsInstance(a, b) isinstance(a, b)

У .assertIs(), .assertIsNone(), .assertIn(), and .assertIsInstance() есть противоположные методы, называемые .assertIsNot() и тд.

Побочные эффекты

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

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

Запуск Первого Теста

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

Запуск Исполнителей Тестов

Исполнитель тестов — приложение Python, которое выполняет тестовый код, проверяет утверждения и выдает результаты тестирования в консоли. В конец test.py добавьте этот небольшой фрагмент кода:

if __name__ == '__main__':
    unittest.main()

Это точка входа командной строки. Если вы выполните этот скрипт, запустив python test.py в командной строке, он вызовет unittest.main(). Это запускает исполнителя тестов, обнаруживая все классы в этом файле, наследуемые из unittest.TestCase.

Это один из многих способов запуска исполнителя тестов unittest. Если у вас есть единственный тестовый файл с названием test.py, вызов python test.py — отличный способ начать работу.

Другой способ — использовать командную строку unittest. Попробуем:

$ python -m unittest test

Это исполнит тот же самый тестовый модуль (под названием test) через командную строку. Можно добавить дополнительные параметры для изменения выходных данных. Один из них -v для многословности (verbose). Попробуем следующее:

$ python -m unittest -v test
test_list_int (test.TestSum) ... ok

----------------------------------------------------------------------
Ran 1 tests in 0.000s

Мы исполнили один тест из test.py и вывели результаты в консоль. Многословный режим перечислил имена выполненных тестов и результаты каждого из них.

Вместо предоставления имени модуля, содержащего тесты, можно запросить авто-обнаружение при помощи следующего:


$ python -m unittest discover

Эта команда будет искать в текущей директории файлы с test*.py в названии, чтобы протестировать их.

При наличии нескольких тестовых файлов и соблюдении шаблона наименования test*.py, можно передать имя директории при помощи -s флага и названия папки.

$ python -m unittest discover -s tests

unittest запустит все тесты в едином тестовом плане и выдаст результаты.
Наконец, если ваш исходный код находится не в корневом каталоге, а в подкаталоге, например в папке с названием src/, можно с помощью -t флага сообщить unittest, где выполнять тесты, для корректного импорта модулей:

$ python -m unittest discover -s tests -t src

unittest найдет все файлы test*.py в директории src/ внутри tests, а затем выполнит их.

Понимание Результатов Тестирование

Это был очень простой пример, где все прошло успешно, поэтому попробуем понять выходные данные проваленного теста.

sum() должен принимать на вход другие списки числового типа, например дроби.

К началу кода в файле test.py добавьте выражение для импорта типа Fraction из модуля fractions стандартной библиотеки.

from fractions import Fraction

Теперь добавим тест с утверждением, ожидая некорректное значение. В нашем случае, ожидаем, что сумма ¼, ¼ и ⅖ будет равна 1:

import unittest

from my_sum import sum


class TestSum(unittest.TestCase):
    def test_list_int(self):
        """
        Test that it can sum a list of integers
        """
        data = [1, 2, 3]
        result = sum(data)
        self.assertEqual(result, 6)

    def test_list_fraction(self):
        """
        Test that it can sum a list of fractions
        """
        data = [Fraction(1, 4), Fraction(1, 4), Fraction(2, 5)]
        result = sum(data)
        self.assertEqual(result, 1)

if __name__ == '__main__':
    unittest.main()
 

Если вы запустите тесты повторно с python -m unittest test, получите следующее:

$ python -m unittest test
F.
======================================================================
FAIL: test_list_fraction (test.TestSum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 21, in test_list_fraction
    self.assertEqual(result, 1)
AssertionError: Fraction(9, 10) != 1

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

В этих выходных данных вы видите следующее:

  • В первой строке указаны результаты выполнения всех тестов: один проваленный (F), один пройденный (.);
  • FAIL показывает некоторые детали проваленного теста:

  1. Название тестового метода (test_list_fraction);
  2. Тестовый модуль (test) и тест-кейс (TestSum);
  3. Трейсбек строки с ошибкой;
  4. Детали утверждения с ожидаемым результатом (1) и фактическим результатом (Fraction(9, 10))

Помните, можно добавить дополнительную информацию к выходным данным теста с помощью флага -v к команде python -m unittest.

Запуск тестов из PyCharm

Если вы используете PyCharm IDE, то можете запустить unittest или pytest, выполнив следующие шаги:

  1. В окне Project tool, выберите директорию tests.
  2. В контекстном меню выберите команду запуска unittest. Например, ‘Unittests in my Tests…’.

Это выполнит unittest в тестовом окне и выдаст результаты в PyCharm:

Больше информации доступно на сайте PyCharm.

Запуск Тестов из Visual Studio Code

Если вы пользуетесь Microsoft Visual Studio Code IDE, поддержка unittest, nose и pytest уже встроена в плагин Python.

Если он у вас установлен, можно настроить конфигурацию тестов, открыв Command Palette по Ctrl+Shift+P и написав “Python test”. Вы увидите список вариантов:

Выберите Debug All Unit Tests, после чего VSCode отправит запрос для настройки тестового фреймворка. Кликните по шестеренке для выбора исполнителя тестов (unittest) и домашней директории (.).

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

Видим, что тесты выполняются, но некоторые из них провалены.

THE END

В следующей части статьи мы рассмотрим тесты для фреймворков, таких как Django и Flask.

Ждём ваши вопросы и комментарии тут и, как всегда, можно зайти к Станиславу на день открытых дверей.

Вторая часть

python logo

Хочешь знать больше о Python?

Подпишись на наш канал о Python в Telegram!

Подписаться

×

В первой части статьи, опубликованной на pythonist.ru (с которой мы уже знакомили вас ранее), разбиралось, почему качество кода имеет такое большое значение, какой код можно считать качественным и на какие стандарты можно ориентироваться.

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

Для сравнения пропустим одинаковый код через несколько линтеров с дефолтными настройками.

Проверять будем следующий код. В нем есть целый ряд логических и стилистических ошибок:

"""
code_with_lint.py
Example Code with lots of lint!
"""
import io
from math import *


from time import time

some_global_var = 'GLOBAL VAR NAMES SHOULD BE IN ALL_CAPS_WITH_UNDERSCOES'

def multiply(x, y):
    """
    This returns the result of a multiplation of the inputs
    """
    some_global_var = 'this is actually a local variable...'
    result = x* y
    return result
    if result == 777:
        print("jackpot!")

def is_sum_lucky(x, y):
    """This returns a string describing whether or not the sum of input is lucky
    This function first makes sure the inputs are valid and then calculates the
    sum. Then, it will determine a message to return based on whether or not
    that sum should be considered "lucky"
    """
    if x != None:
        if y is not None:
            result = x+y;
            if result == 7:
                return 'a lucky number!'
            else:
                return( 'an unlucky number!')

            return ('just a normal number')

class SomeClass:

    def __init__(self, some_arg,  some_other_arg, verbose = False):
        self.some_other_arg  =  some_other_arg
        self.some_arg        =  some_arg
        list_comprehension = [((100/value)*pi) for value in some_arg if value != 0]
        time = time()
        from datetime import datetime
        date_and_time = datetime.now()
        return

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

ЛИНТЕР КОМАНДА ВРЕМЯ
Pylint pylint code_with_lint.py 1,16 с
PyFlakes pyflakes code_with_lint.py 0,15 с
pycodestyle pycodestyle code_with_lint.py 0,14 с
pydocstyle pydocstyle code_with_lint.py 0,21 с

Теперь давайте посмотрим на результаты.

Pylint

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

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

Итак, вот результат запуска Pylint для приведенного выше кода:

No config file found, using default configuration
* Module code_with_lint
W: 23, 0: Unnecessary semicolon (unnecessary-semicolon)
C: 27, 0: Unnecessary parens after 'return' keyword (superfluous-parens)
C: 27, 0: No space allowed after bracket
return( 'an unlucky number!')
^ (bad-whitespace)
C: 29, 0: Unnecessary parens after 'return' keyword (superfluous-parens)
C: 33, 0: Exactly one space required after comma
def init(self, some_arg, some_other_arg, verbose = False):
^ (bad-whitespace)
C: 33, 0: No space allowed around keyword argument assignment
def init(self, some_arg, some_other_arg, verbose = False):
^ (bad-whitespace)
C: 34, 0: Exactly one space required around assignment
self.some_other_arg = some_other_arg
^ (bad-whitespace)
C: 35, 0: Exactly one space required around assignment
self.some_arg = some_arg
^ (bad-whitespace)
C: 40, 0: Final newline missing (missing-final-newline)
W: 6, 0: Redefining built-in 'pow' (redefined-builtin)
W: 6, 0: Wildcard import math (wildcard-import)
C: 11, 0: Constant name "some_global_var" doesn't conform to UPPER_CASE naming style (invalid-name)
C: 13, 0: Argument name "x" doesn't conform to snake_case naming style (invalid-name)
C: 13, 0: Argument name "y" doesn't conform to snake_case naming style (invalid-name)
C: 13, 0: Missing function docstring (missing-docstring)
W: 14, 4: Redefining name 'some_global_var' from outer scope (line 11) (redefined-outer-name)
W: 17, 4: Unreachable code (unreachable)
W: 14, 4: Unused variable 'some_global_var' (unused-variable)
…
R: 24,12: Unnecessary "else" after "return" (no-else-return)
R: 20, 0: Either all return statements in a function should return an expression, or none of them should. (inconsistent-return-statements)
C: 31, 0: Missing class docstring (missing-docstring)
W: 37, 8: Redefining name 'time' from outer scope (line 9) (redefined-outer-name)
E: 37,15: Using variable 'time' before assignment (used-before-assignment)
W: 33,50: Unused argument 'verbose' (unused-argument)
W: 36, 8: Unused variable 'list_comprehension' (unused-variable)
W: 39, 8: Unused variable 'date_and_time' (unused-variable)
R: 31, 0: Too few public methods (0/2) (too-few-public-methods)
W: 5, 0: Unused import io (unused-import)
W: 6, 0: Unused import acos from wildcard import (unused-wildcard-import)
…
W: 9, 0: Unused time imported from time (unused-import)

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

Обратите внимание, что Pylint добавляет к каждой проблемной области префикс R, C, W, E или F, что означает:

  • [R]efactor — нужен рефакторинг, поскольку показатель «good practice» не на должном уровне.
  • [C]onvention — нарушение соглашения о стандарте кода
  • [W]arning — предупреждение о стилистических проблемах или минорных программных проблемах
  • [E]rror — существенные проблемы в программе (скорее всего баг)
  • [F]atal — ошибки, мешающие дальнейшей работе.

Приведенный список — из пользовательского руководства Pylint.

PyFlakes

Pyflakes «торжественно клянется никогда не жаловаться на стиль и очень усердно стараться не допускать ложнопозитивных срабатываний». То есть Pyflakes не сообщит вам о пропущенных docstrings или о том, что имена аргументов не соответствуют стилю нейминга. Он фокусируется на логических проблемах в коде и потенциальных ошибках.

Преимущество этого инструмента в скорости. PyFlakes обработал файл лишь за небольшую долю времени, которое потребовалось Pylint.

Вывод после запуска Pyflakes для приведенного выше кода:

code_with_lint.py:5: 'io' imported but unused
code_with_lint.py:6: 'from math import *' used; unable to detect undefined names
code_with_lint.py:14: local variable 'some_global_var' is assigned to but never used
code_with_lint.py:36: 'pi' may be undefined, or defined from star imports: math
code_with_lint.py:36: local variable 'list_comprehension' is assigned to but never used
code_with_lint.py:37: local variable 'time' (defined in enclosing scope on line 9) referenced before assignment
code_with_lint.py:37: local variable 'time' is assigned to but never used
code_with_lint.py:39: local variable 'date_and_time' is assigned to but never used

Недостаток Pyflakes в том, что в результатах его работы немного труднее разобраться. Различные проблемы и ошибки никак не помечены и не упорядочены. Но будет ли это для вас проблемой, зависит от вашего использования этого инструмента.

pycodestyle (прежде — pep8)

Этот инструмент проверяет код на соответствие некоторым соглашениям из PEP 8. Нейминг не проверяется, так же как и docstrings. Ошибки и предупреждения, выдаваемые этим инструментом, можно посмотреть в таблице.

Результат использования pycodestyle для приведенного выше кода:

code_with_lint.py:13:1: E302 expected 2 blank lines, found 1
code_with_lint.py:15:15: E225 missing whitespace around operator
code_with_lint.py:20:1: E302 expected 2 blank lines, found 1
code_with_lint.py:21:10: E711 comparison to None should be 'if cond is not None:'
code_with_lint.py:23:25: E703 statement ends with a semicolon
code_with_lint.py:27:24: E201 whitespace after '('
code_with_lint.py:31:1: E302 expected 2 blank lines, found 1
code_with_lint.py:33:58: E251 unexpected spaces around keyword / parameter equals
code_with_lint.py:33:60: E251 unexpected spaces around keyword / parameter equals
code_with_lint.py:34:28: E221 multiple spaces before operator
code_with_lint.py:34:31: E222 multiple spaces after operator
code_with_lint.py:35:22: E221 multiple spaces before operator
code_with_lint.py:35:31: E222 multiple spaces after operator
code_with_lint.py:36:80: E501 line too long (83 > 79 characters)
code_with_lint.py:40:15: W292 no newline at end of file

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

pydocstyle (прежде — pep257)

Этот инструмент очень похож на предыдущий, pycodestyle, за исключением того, что проверяет код не на соответствие PEP 8, а на соответствие PEP 257.

Результат запуска для приведенного выше кода:

code_with_lint.py:1 at module level:
         D200: One-line docstring should fit on one line with quotes (found 3)
 code_with_lint.py:1 at module level:
         D400: First line should end with a period (not '!')
 code_with_lint.py:13 in public function `multiply`:
         D103: Missing docstring in public function
 code_with_lint.py:20 in public function `is_sum_lucky`:
         D103: Missing docstring in public function
 code_with_lint.py:31 in public class `SomeClass`:
         D101: Missing docstring in public class
 code_with_lint.py:33 in public method `__init__`:
         D107: Missing docstring in __init__

Как и pycodestyle, pydocstyle помечает и разбивает по категориям найденные ошибки. Этот список не конфликтует ни с чем из pycodestyle, поскольку все ошибки имеют приставку D (означающую docstring). Список ошибок можно посмотреть здесь.

Код без ошибок

Если учесть предупреждения и исправить ошибки, найденные линтерами, вы получите примерно такой код:

"""Example Code with less lint."""

from math import pi
from time import time
from datetime import datetime

SOME_GLOBAL_VAR = 'GLOBAL VAR NAMES SHOULD BE IN ALL_CAPS_WITH_UNDERSCOES'


def multiply(first_value, second_value):
    """Return the result of a multiplation of the inputs."""
    result = first_value * second_value

    if result == 777:
        print("jackpot!")

    return result


def is_sum_lucky(first_value, second_value):
    """
    Return a string describing whether or not the sum of input is lucky.

    This function first makes sure the inputs are valid and then calculates the
    sum. Then, it will determine a message to return based on whether or not
    that sum should be considered "lucky".
    """
    if first_value is not None and second_value is not None:
        result = first_value + second_value
        if result == 7:
            message = 'a lucky number!'
        else:
            message = 'an unlucky number!'
    else:
        message = 'an unknown number! Could not calculate sum...'

    return message


class SomeClass:
    """Is a class docstring."""

    def __init__(self, some_arg, some_other_arg):
        """Initialize an instance of SomeClass."""
        self.some_other_arg = some_other_arg
        self.some_arg = some_arg
        list_comprehension = [
            ((100/value)*pi)
            for value in some_arg
            if value != 0
        ]
        current_time = time()
        date_and_time = datetime.now()
        print(f'created SomeClass instance at unix time: {current_time}')
        print(f'datetime: {date_and_time}')
        print(f'some calculated values: {list_comprehension}')

    def some_public_method(self):
        """Is a method docstring."""
        pass

    def some_other_public_method(self):
        """Is a method docstring."""
        pass

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

В рассмотренном случае мы запускали линтеры на уже написанном коде. Но это не единственный способ проверки качества кода.

Когда можно проверять качество кода?

Вы можете проверять качество своего кода:

  • по мере написания,
  • перед отправкой,
  • при запуске тестов.

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

Чтобы этого избежать, проверяйте качество кода почаще!

Проверка кода по мере его написания

Вы можете использовать линтеры по мере написания кода, но для этого может понадобиться дополнительно настроить вашу среду разработки. Чаще всего вам нужно будет найти подходящий плагин для вашей IDE или редактора. Но большинство IDE имеют и встроенные линтеры.

По ссылкам вы сможете найти полезную информацию по этой теме для разных редакторов:

  • Sublime Text
  • VS Code
  • Atom
  • Vim
  • Emacs

Проверка кода перед его отправкой

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

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

При запуске тестов

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

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

Заключение

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

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

Благодаря руководствам по стилю ваш код может стать единообразным. PEP8 — отличная отправная точка, если речь идет о Python. Линтеры помогут вам обнаружить проблемные места и стилевые несоответствия. Использовать эти инструменты можно на любой стадии процесса разработки; их можно даже автоматизировать, чтобы код с «пухом» не прошел слишком далеко.

Использование линтеров позволяет избежать ненужных дискуссий о стиле в ходе код-ревью. Некоторым людям морально легче получить объективный фидбэк от инструментов, а не от товарищей по команде. Кроме того, некоторые ревьюеры могут просто не хотеть «придираться» к стилю проверяемого кода. Линтеры не озабочены всеми этими политесами и экономией времени: они жалуются на любое несоответствие.

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

Улучшение качества кода это процесс. Вы можете предпринимать некоторые шаги в этом направлении и не выбрасывая весь несоответствующий стандарту код. Осведомленность — прекрасный первый шаг. Чтобы его сделать, нужно только осознать, насколько важно поддерживать высокое качество кода.

I used to use perl -c programfile to check the syntax of a Perl program and then exit without executing it. Is there an equivalent way to do this for a Python script?

Koterpillar's user avatar

Koterpillar

7,7422 gold badges26 silver badges41 bronze badges

asked Nov 26, 2010 at 10:12

Eugene Yarmash's user avatar

Eugene YarmashEugene Yarmash

138k39 gold badges318 silver badges372 bronze badges

0

You can check the syntax by compiling it:

python -m py_compile script.py

Daniel Fischer's user avatar

answered Dec 8, 2011 at 20:57

Mark Johnson's user avatar

15

import sys
filename = sys.argv[1]
source = open(filename, 'r').read() + 'n'
compile(source, filename, 'exec')

Save this as checker.py and run python checker.py yourpyfile.py.

rvighne's user avatar

rvighne

20.1k11 gold badges51 silver badges73 bronze badges

answered Nov 26, 2010 at 10:39

Rosh Oxymoron's user avatar

Rosh OxymoronRosh Oxymoron

20k6 gold badges41 silver badges43 bronze badges

3

Here’s another solution, using the ast module:

python -c "import ast; ast.parse(open('programfile').read())"

To do it cleanly from within a Python script:

import ast, traceback

filename = 'programfile'
with open(filename) as f:
    source = f.read()
valid = True
try:
    ast.parse(source)
except SyntaxError:
    valid = False
    traceback.print_exc()  # Remove to silence any errros
print(valid)

answered Aug 15, 2019 at 11:15

jmd_dk's user avatar

jmd_dkjmd_dk

11.5k8 gold badges65 silver badges92 bronze badges

5

Pyflakes does what you ask, it just checks the syntax. From the docs:

Pyflakes makes a simple promise: it will never complain about style, and it will try very, very hard to never emit false positives.

Pyflakes is also faster than Pylint or Pychecker. This is largely because Pyflakes only examines the syntax tree of each file individually.

To install and use:

$ pip install pyflakes
$ pyflakes yourPyFile.py

answered Jan 8, 2020 at 19:52

Brian C.'s user avatar

Brian C.Brian C.

5,6773 gold badges30 silver badges40 bronze badges

1

python -m compileall -q .

Will compile everything under current directory recursively, and print only errors.

$ python -m compileall --help
usage: compileall.py [-h] [-l] [-r RECURSION] [-f] [-q] [-b] [-d DESTDIR] [-x REGEXP] [-i FILE] [-j WORKERS] [--invalidation-mode {checked-hash,timestamp,unchecked-hash}] [FILE|DIR [FILE|DIR ...]]

Utilities to support installing Python libraries.

positional arguments:
  FILE|DIR              zero or more file and directory names to compile; if no arguments given, defaults to the equivalent of -l sys.path

optional arguments:
  -h, --help            show this help message and exit
  -l                    don't recurse into subdirectories
  -r RECURSION          control the maximum recursion level. if `-l` and `-r` options are specified, then `-r` takes precedence.
  -f                    force rebuild even if timestamps are up to date
  -q                    output only error messages; -qq will suppress the error messages as well.
  -b                    use legacy (pre-PEP3147) compiled file locations
  -d DESTDIR            directory to prepend to file paths for use in compile-time tracebacks and in runtime tracebacks in cases where the source file is unavailable
  -x REGEXP             skip files matching the regular expression; the regexp is searched for in the full path of each file considered for compilation
  -i FILE               add all the files and directories listed in FILE to the list considered for compilation; if "-", names are read from stdin
  -j WORKERS, --workers WORKERS
                        Run compileall concurrently
  --invalidation-mode {checked-hash,timestamp,unchecked-hash}
                        set .pyc invalidation mode; defaults to "checked-hash" if the SOURCE_DATE_EPOCH environment variable is set, and "timestamp" otherwise.

Exit value is 1 when syntax errors have been found.

Thanks C2H5OH.

answered Apr 19, 2021 at 8:25

user1338062's user avatar

user1338062user1338062

11.4k3 gold badges71 silver badges63 bronze badges

Perhaps useful online checker PEP8 : http://pep8online.com/

answered Sep 10, 2015 at 9:22

shakhmatov's user avatar

shakhmatovshakhmatov

2,8531 gold badge13 silver badges12 bronze badges

0

Thanks to the above answers @Rosh Oxymoron. I improved the script to scan all files in a dir that are python files. So for us lazy folks just give it the directory and it will scan all the files in that directory that are python. you can specify any file ext. you like.

import sys
import glob, os

os.chdir(sys.argv[1])
for file in glob.glob("*.py"):
    source = open(file, 'r').read() + 'n'
    compile(source, file, 'exec')

Save this as checker.py and run python checker.py ~/YOURDirectoryTOCHECK

answered Sep 10, 2020 at 20:19

Josh's user avatar

JoshJosh

86010 silver badges16 bronze badges

for some reason ( I am a py newbie … ) the -m call did not work …

so here is a bash wrapper func …

# ---------------------------------------------------------
# check the python synax for all the *.py files under the
# <<product_version_dir/sfw/python
# ---------------------------------------------------------
doCheckPythonSyntax(){

    doLog "DEBUG START doCheckPythonSyntax"

    test -z "$sleep_interval" || sleep "$sleep_interval"
    cd $product_version_dir/sfw/python
    # python3 -m compileall "$product_version_dir/sfw/python"

    # foreach *.py file ...
    while read -r f ; do 

        py_name_ext=$(basename $f)
        py_name=${py_name_ext%.*}

        doLog "python3 -c "import $py_name""
        # doLog "python3 -m py_compile $f"

        python3 -c "import $py_name"
        # python3 -m py_compile "$f"
        test $! -ne 0 && sleep 5

    done < <(find "$product_version_dir/sfw/python" -type f -name "*.py")

    doLog "DEBUG STOP  doCheckPythonSyntax"
}
# eof func doCheckPythonSyntax

answered Dec 27, 2016 at 11:04

Yordan Georgiev's user avatar

Yordan GeorgievYordan Georgiev

4,8281 gold badge51 silver badges53 bronze badges

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

А вот еще интересные материалы:

  • Яшка сломя голову остановился исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  • Проверка кода на ошибки как называется
  • Проверка кода на ошибки python online