D103 missing docstring in public function как исправить

Flake8 is given me the D103 Missing docstring in public function error for my unit tests. I would not consider my unit tests public functions. I get: test/base_classes/test_component.py:7:1: D103 M...

Flake8 is given me the D103 Missing docstring in public function error for my unit tests. I would not consider my unit tests public functions. I get:

test/base_classes/test_component.py:7:1: D103 Missing docstring in public function
test/base_classes/test_component.py:13:1: D103 Missing docstring in public function
test/base_classes/test_component.py:25:1: D103 Missing docstring in public function
test/base_classes/test_component.py:35:1: D103 Missing docstring in public function
test/base_classes/test_component.py:41:1: D103 Missing docstring in public function

How do I disable this check for my tests?

asked Jul 6, 2022 at 20:18

Jorrick Sleijster's user avatar

0

You can add the following to your .flake8 file at the root of your repository to ignore all D103 errors for test code:

per-file-ignores =
     test/*: D103

answered Jul 6, 2022 at 20:18

Jorrick Sleijster's user avatar

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

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

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

"""
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. Линтеры помогут вам обнаружить
проблемные места и стилевые несоответствия.
Использовать эти инструменты можно на
любой стадии процесса разработки; их
можно даже автоматизировать, чтобы код
с «пухом» не прошел слишком далеко.

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

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

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

Flake8 дает мне ошибку D103 Missing docstring in public function для моих модульных тестов. Я бы не стал рассматривать свои модульные тесты как публичные функции. Я получил:

test/base_classes/test_component.py:7:1: D103 Missing docstring in public function
test/base_classes/test_component.py:13:1: D103 Missing docstring in public function
test/base_classes/test_component.py:25:1: D103 Missing docstring in public function
test/base_classes/test_component.py:35:1: D103 Missing docstring in public function
test/base_classes/test_component.py:41:1: D103 Missing docstring in public function

Как отключить эту проверку для моих тестов?

1 ответ

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

per-file-ignores =
     test/*: D103


0

Jorrick Sleijster
6 Июл 2022 в 23:18

The PEP-8 document says:

sometimes style guide recommendations just aren’t applicable

Here, the recommendation in question is:

Write docstrings for all public modules, functions, classes, and methods. Docstrings are not necessary for non-public methods, but you should have a comment that describes what the method does.

I’d argue that tests are not public modules in the sense of this recommendation, even though a test files technically is a module that could be imported and its contents are technically public. This just isn’t the case.

The intention for using docstrings is that you know how to call the function. But test functions are never called directly. There’s a secondary task of documenting the contents of the function (although normal comments also do the job). However, test functions often have very long and self-documenting names, so this is less of a concern.

Keeping tests somewhat tidy is legitimately important, so it can make sense to run flake8 or other linters over the test code as well. But since testing is a different context from normal code, some values are different. E.g. it is important that tests are independent, less important that tests are DRY. And whereas unused functions indicate a bug in normal code, this is normal in test code.

I would advise one of the following approaches:

  • exclude tests from linting, e.g. via a configuration file or with # flake8: noqa comments. However, this is a fairly drastic approach.
  • disable just this lint within the file, e.g. with # noqa: D103 comments. However, I’m not sure if flake8 allows you to disable a lint for the entire file, or just for the current line.

Николай Коренга

Здравствуйте!
Установил «Wemake Python Styleguide». Линтер выдаёт следующее:

╰─>$ poetry run flake8 BRAIN_GAMES
BRAIN_GAMES/__init__.py:1:1: D400 First line should end with a period
BRAIN_GAMES/cli.py:1:1: D100 Missing docstring in public module
BRAIN_GAMES/cli.py:5:1: I003 isort expected 1 blank line in imports, found 0
--- BRAIN_GAMES/cli.py:before
+++ BRAIN_GAMES/cli.py:after
@@ -2,6 +2,7 @@
 # docstring

 import prompt
+
 # docstring



BRAIN_GAMES/cli.py:8:1: D103 Missing docstring in public function
BRAIN_GAMES/cli.py:10:5: WPS421 Found wrong function call: print
BRAIN_GAMES/cli.py:10:11: P101 format string does contain unindexed parameters
BRAIN_GAMES/cli.py:14:1: D103 Missing docstring in public function
BRAIN_GAMES/scripts/brain_games.py:0:1: WPS453 Found executable mismatch: shebang is present but the file is not executable
BRAIN_GAMES/scripts/brain_games.py:1:1: D100 Missing docstring in public module
BRAIN_GAMES/scripts/brain_games.py:5:1: I003 isort expected 1 blank line in imports, found 0
--- BRAIN_GAMES/scripts/brain_games.py:before
+++ BRAIN_GAMES/scripts/brain_games.py:after
@@ -2,6 +2,7 @@


 from BRAIN_GAMES.cli import welcome_user
+
 # docstring in function



BRAIN_GAMES/scripts/brain_games.py:8:1: D103 Missing docstring in public function
BRAIN_GAMES/scripts/brain_games.py:9:5: WPS421 Found wrong function call: print
BRAIN_GAMES/scripts/brain_games.py:14:1: D103 Missing docstring in public function
BRAIN_GAMES/scripts/brain_games.py:20:1: W391 blank line at end of file
BRAIN_GAMES/scripts/__init__.py:1:1: D104 Missing docstring in public package

Ошибки какие-то нелепые, исправление их приводит к ещё большим ошибкам. Точно возможно исправить все ошибки?
И ещё вопрос: Задания более-менее получаются, но впечатление такое, будто ничему не учишься. Это нормально?

Сергей К.

Добрый день!

WPS – действительно, очень суровый линтер. В самом проекте мы такой задачи не ставим и предлагаем использовать flake8. Но вы можете себя проверить и использовать WPS. Всё же его ошибки можно исправить.

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


0

In this article, we’ll identify high-quality Python code and show you how to improve the quality of your own code.

We’ll analyze and compare tools you can use to take your code to the next level. Whether you’ve been using Python for a while, or just beginning, you can benefit from the practices and tools talked about here.

What is Code Quality?

Of course you want quality code, who wouldn’t? But to improve code quality, we have to define what it is.

A quick Google search yields many results defining code quality. As it turns out, the term can mean many different things to people.

One way of trying to define code quality is to look at one end of the spectrum: high-quality code. Hopefully, you can agree on the following high-quality code identifiers:

  • It does what it is supposed to do.
  • It does not contain defects or problems.
  • It is easy to read, maintain, and extend.

These three identifiers, while simplistic, seem to be generally agreed upon. In an effort to expand these ideas further, let’s delve into why each one matters in the realm of software.

Why Does Code Quality Matter?

To determine why high-quality code is important, let’s revisit those identifiers. We’ll see what happens when code doesn’t meet them.

It does not do what it is supposed to do

Meeting requirements is the basis of any product, software or otherwise. We make software to do something. If in the end, it doesn’t do it… well it’s definitely not high quality. If it doesn’t meet basic requirements, it’s hard to even call it low quality.

It does contain defects and problems

If something you’re using has issues or causes you problems, you probably wouldn’t call it high-quality. In fact, if it’s bad enough, you may stop using it altogether.

For the sake of not using software as an example, let’s say your vacuum works great on regular carpet. It cleans up all the dust and cat hair. One fateful night the cat knocks over a plant, spilling dirt everywhere. When you try to use the vacuum to clean the pile of dirt, it breaks, spewing the dirt everywhere.

While the vacuum worked under some circumstances, it didn’t efficiently handle the occasional extra load. Thus, you wouldn’t call it a high-quality vacuum cleaner.

That is a problem we want to avoid in our code. If things break on edge cases and defects cause unwanted behavior, we don’t have a high-quality product.

It is difficult to read, maintain, or extend

Imagine this: a customer requests a new feature. The person who wrote the original code is gone. The person who has replaced them now has to make sense of the code that’s already there. That person is you.

If the code is easy to comprehend, you’ll be able to analyze the problem and come up with a solution much quicker. If the code is complex and convoluted, you’ll probably take longer and possibly make some wrong assumptions.

It’s also nice if it’s easy to add the new feature without disrupting previous features. If the code is not easy to extend, your new feature could break other things.

No one wants to be in the position where they have to read, maintain, or extend low-quality code. It means more headaches and more work for everyone.

It’s bad enough that you have to deal with low-quality code, but don’t put someone else in the same situation. You can improve the quality of code that you write.

If you work with a team of developers, you can start putting into place methods to ensure better overall code quality. Assuming that you have their support, of course. You may have to win some people over (feel free to send them this article 😃).

How to Improve Python Code Quality

There are a few things to consider on our journey for high-quality code. First, this journey is not one of pure objectivity. There are some strong feelings of what high-quality code looks like.

While everyone can hopefully agree on the identifiers mentioned above, the way they get achieved is a subjective road. The most opinionated topics usually come up when you talk about achieving readability, maintenance, and extensibility.

So keep in mind that while this article will try to stay objective throughout, there is a very-opinionated world out there when it comes to code.

So, let’s start with the most opinionated topic: code style.

Style Guides

Ah, yes. The age-old question: spaces or tabs?

Regardless of your personal view on how to represent whitespace, it’s safe to assume that you at least want consistency in code.

A style guide serves the purpose of defining a consistent way to write your code. Typically this is all cosmetic, meaning it doesn’t change the logical outcome of the code. Although, some stylistic choices do avoid common logical mistakes.

Style guides serve to help facilitate the goal of making code easy to read, maintain, and extend.

As far as Python goes, there is a well-accepted standard. It was written, in part, by the author of the Python programming language itself.

PEP 8 provides coding conventions for Python code. It is fairly common for Python code to follow this style guide. It’s a great place to start since it’s already well-defined.

A sister Python Enhancement Proposal, PEP 257 describes conventions for Python’s docstrings, which are strings intended to document modules, classes, functions, and methods. As an added bonus, if docstrings are consistent, there are tools capable of generating documentation directly from the code.

All these guides do is define a way to style code. But how do you enforce it? And what about defects and problems in the code, how can you detect those? That’s where linters come in.

Linters

What is a Linter?

First, let’s talk about lint. Those tiny, annoying little defects that somehow get all over your clothes. Clothes look and feel much better without all that lint. Your code is no different. Little mistakes, stylistic inconsistencies, and dangerous logic don’t make your code feel great.

But we all make mistakes. You can’t expect yourself to always catch them in time. Mistyped variable names, forgetting a closing bracket, incorrect tabbing in Python, calling a function with the wrong number of arguments, the list goes on and on. Linters help to identify those problem areas.

Additionally, most editors and IDE’s have the ability to run linters in the background as you type. This results in an environment capable of highlighting, underlining, or otherwise identifying problem areas in the code before you run it. It is like an advanced spell-check for code. It underlines issues in squiggly red lines much like your favorite word processor does.

Linters analyze code to detect various categories of lint. Those categories can be broadly defined as the following:

  1. Logical Lint
    • Code errors
    • Code with potentially unintended results
    • Dangerous code patterns
  2. Stylistic Lint
    • Code not conforming to defined conventions

There are also code analysis tools that provide other insights into your code. While maybe not linters by definition, these tools are usually used side-by-side with linters. They too hope to improve the quality of the code.

Finally, there are tools that automatically format code to some specification. These automated tools ensure that our inferior human minds don’t mess up conventions.

What Are My Linter Options For Python?

Before delving into your options, it’s important to recognize that some “linters” are just multiple linters packaged nicely together. Some popular examples of those combo-linters are the following:

Flake8: Capable of detecting both logical and stylistic lint. It adds the style and complexity checks of pycodestyle to the logical lint detection of PyFlakes. It combines the following linters:

  • PyFlakes
  • pycodestyle (formerly pep8)
  • Mccabe

Pylama: A code audit tool composed of a large number of linters and other tools for analyzing code. It combines the following:

  • pycodestyle (formerly pep8)
  • pydocstyle (formerly pep257)
  • PyFlakes
  • Mccabe
  • Pylint
  • Radon
  • gjslint

Here are some stand-alone linters categorized with brief descriptions:

Linter Category Description
Pylint Logical & Stylistic Checks for errors, tries to enforce a coding standard, looks for code smells
PyFlakes Logical Analyzes programs and detects various errors
pycodestyle Stylistic Checks against some of the style conventions in PEP 8
pydocstyle Stylistic Checks compliance with Python docstring conventions
Bandit Logical Analyzes code to find common security issues
MyPy Logical Checks for optionally-enforced static types

And here are some code analysis and formatting tools:

Tool Category Description
Mccabe Analytical Checks McCabe complexity
Radon Analytical Analyzes code for various metrics (lines of code, complexity, and so on)
Black Formatter Formats Python code without compromise
Isort Formatter Formats imports by sorting alphabetically and separating into sections

Comparing Python Linters

Let’s get a better idea of what different linters are capable of catching and what the output looks like. To do this, I ran the same code through a handful of different linters with the default settings.

The code I ran through the linters is below. It contains various logical and stylistic issues:

"""
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

The comparison below shows the linters I used and their runtime for analyzing the above file. I should point out that these aren’t all entirely comparable as they serve different purposes. PyFlakes, for example, does not identify stylistic errors like Pylint does.

Linter Command Time
Pylint pylint code_with_lint.py 1.16s
PyFlakes pyflakes code_with_lint.py 0.15s
pycodestyle pycodestyle code_with_lint.py 0.14s
pydocstyle pydocstyle code_with_lint.py 0.21s

For the outputs of each, see the sections below.

Pylint

Pylint is one of the oldest linters (circa 2006) and is still well-maintained. Some might call this software battle-hardened. It’s been around long enough that contributors have fixed most major bugs and the core features are well-developed.

The common complaints against Pylint are that it is slow, too verbose by default, and takes a lot of configuration to get it working the way you want. Slowness aside, the other complaints are somewhat of a double-edged sword. Verbosity can be because of thoroughness. Lots of configuration can mean lots of adaptability to your preferences.

Without further ado, the output after running Pylint against the lint-filled code from above:

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)

Note that I’ve condensed this with ellipses for similar lines. It’s quite a bit to take in, but there is a lot of lint in this code.

Note that Pylint prefixes each of the problem areas with a R, C, W, E, or F, meaning:

  • [R]efactor for a “good practice” metric violation
  • [C]onvention for coding standard violation
  • [W]arning for stylistic problems, or minor programming issues
  • [E]rror for important programming issues (i.e. most probably bug)
  • [F]atal for errors which prevented further processing

The above list is directly from Pylint’s user guide.

PyFlakes

Pyflakes “makes a simple promise: it will never complain about style, and it will try very, very hard to never emit false positives”. This means that Pyflakes won’t tell you about missing docstrings or argument names not conforming to a naming style. It focuses on logical code issues and potential errors.

The benefit here is speed. PyFlakes runs in a fraction of the time Pylint takes.

Output after running against lint-filled code from above:

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

The downside here is that parsing this output may be a bit more difficult. The various issues and errors are not labeled or organized by type. Depending on how you use this, that may not be a problem at all.

pycodestyle (formerly pep8)

Used to check some style conventions from PEP8. Naming conventions are not checked and neither are docstrings. The errors and warnings it does catch are categorized in this table.

Output after running against lint-filled code from above:

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

The nice thing about this output is that the lint is labeled by category. You can choose to ignore certain errors if you don’t care to adhere to a specific convention as well.

pydocstyle (formerly pep257)

Very similar to pycodestyle, except instead of checking against PEP8 code style conventions, it checks docstrings against conventions from PEP257.

Output after running against lint-filled code from above:

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__

Again, like pycodestyle, pydocstyle labels and categorizes the various errors it finds. And the list doesn’t conflict with anything from pycodestyle since all the errors are prefixed with a D for docstring. A list of those errors can be found here.

Code Without Lint

You can adjust the previously lint-filled code based on the linter’s output and you’ll end up with something like the following:

"""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

That code is lint-free according to the linters above. While the logic itself is mostly nonsensical, you can see that at a minimum, consistency is enforced.

In the above case, we ran linters after writing all the code. However, that’s not the only way to go about checking code quality.

When Can I Check My Code Quality?

You can check your code’s quality:

  • As you write it
  • When it’s checked in
  • When you’re running your tests

It’s useful to have linters run against your code frequently. If automation and consistency aren’t there, it’s easy for a large team or project to lose sight of the goal and start creating lower quality code. It happens slowly, of course. Some poorly written logic or maybe some code with formatting that doesn’t match the neighboring code. Over time, all that lint piles up. Eventually, you can get stuck with something that’s buggy, hard to read, hard to fix, and a pain to maintain.

To avoid that, check code quality often!

As You Write

You can use linters as you write code, but configuring your environment to do so may take some extra work. It’s generally a matter of finding the plugin for your IDE or editor of choice. In fact, most IDEs will already have linters built in.

Here’s some general info on Python linting for various editors:

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

Before You Check In Code

If you’re using Git, Git hooks can be set up to run your linters before committing. Other version control systems have similar methods to run scripts before or after some action in the system. You can use these methods to block any new code that doesn’t meet quality standards.

While this may seem drastic, forcing every bit of code through a screening for lint is an important step towards ensuring continued quality. Automating that screening at the front gate to your code may be the best way to avoid lint-filled code.

When Running Tests

You can also place linters directly into whatever system you may use for continuous integration. The linters can be set up to fail the build if the code doesn’t meet quality standards.

Again, this may seem like a drastic step, especially if there are already lots of linter errors in the existing code. To combat this, some continuous integration systems will allow you the option of only failing the build if the new code increases the number of linter errors that were already present. That way you can start improving quality without doing a whole rewrite of your existing code base.

Conclusion

High-quality code does what it’s supposed to do without breaking. It is easy to read, maintain, and extend. It functions without problems or defects and is written so that it’s easy for the next person to work with.

Hopefully it goes without saying that you should strive to have such high-quality code. Luckily, there are methods and tools to help improve code quality.

Style guides will bring consistency to your code. PEP8 is a great starting point for Python. Linters will help you identify problem areas and inconsistencies. You can use linters throughout the development process, even automating them to flag lint-filled code before it gets too far.

Having linters complain about style also avoids the need for style discussions during code reviews. Some people may find it easier to receive candid feedback from these tools instead of a team member. Additionally, some team members may not want to “nitpick” style during code reviews. Linters avoid the politics, save time, and complain about any inconsistency.

In addition, all the linters mentioned in this article have various command line options and configurations that let you tailor the tool to your liking. You can be as strict or as loose as you want, which is an important thing to realize.

Improving code quality is a process. You can take steps towards improving it without completely disallowing all nonconformant code. Awareness is a great first step. It just takes a person, like you, to first realize how important high-quality code is.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

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

(Есть ли термин юридического жаргона, который часто встречается в начале исходного файла источника? Любые примеры? Я не знаю, правильно ли публиковать такой тривиальный вопрос отдельно.)

Ответ 1

Для модуля Python хорошо иметь строку документации, объясняющую, что делает модуль, что он предоставляет, примеры того, как использовать классы. Это отличается от комментариев, которые вы часто видите в начале файла с информацией об авторском праве и лицензии, который IMO не должен указывать в строке документации (некоторые даже утверждают, что они вообще должны исчезнуть, см., Например, http://hackerboss.com/get-rid-of-templates/)

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

  • C0114 (missing-module-docstring)
  • C0115 (missing-class-docstring)
  • C0116 (missing-function-docstring)

Таким образом, следующий файл .pylintrc должен работать:

[MASTER]
disable=
    C0114, # missing-module-docstring

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

Итак, я предлагаю добавить эту маленькую недостающую строку документации, говоря что-то вроде:

"""
high level support for doing this and that.
"""

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

Ответ 2

Уже поздно, но все же я нашел это полезным. Так что делюсь.
Нашел это здесь.

Вы можете добавить флаг «—errors-only» для pylint, чтобы отключить предупреждения.

Для этого зайдите в настройки. Отредактируйте следующую строку:

"python.linting.pylintArgs": []

Как

"python.linting.pylintArgs": ["--errors-only"]

И тебе хорошо идти!

Ответ 3

Я пришел искать ответ, потому что, как сказал @cerin, в проектах Django громоздко и избыточно добавлять модули docstrings к каждому из файлов, которые django автоматически создает при создании нового приложения.

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

pylint */*.py --msg-template='{path}: {C}:{line:3d},{column:2d}: {msg}' | grep docstring | grep -v module

Вам нужно обновить шаблон msg, чтобы при grep вы все равно узнаете имя файла. Это возвращает все другие отсутствующие типы docstring, исключая модули.

Затем вы можете исправить все эти ошибки, а затем просто запустить:

pylint */*.py --disable=missing-docstring

Ответ 4

Нет. В настоящее время Pylint не позволяет вам различать предупреждения строки документа.

Однако вы можете использовать flake8 для проверки всего кода Python вместе с расширением doc-string, чтобы игнорировать это предупреждение.

Установите расширение doc-string с помощью pip (внутренне оно использует pydocstyle).

pip install flake8_docstrings

Затем вы можете просто использовать переключатель --ignore D100. Например, flake8 file.py --ignore D100

Ответ 5

Я думаю, что исправление относительно легко, не отключая эту функцию.

def kos_root():
    """Return the pathname of the KOS root directory."""
    global _kos_root
    if _kos_root: return _kos_root

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

Ответ 6

Edit "C:UsersYour UserAppDataRoamingCodeUsersettings.json" and add 
python.linting.pylintArgs lines at the end as shown below:

{
    "team.showWelcomeMessage": false,
    "python.dataScience.sendSelectionToInteractiveWindow": true,
    "git.enableSmartCommit": true,
    "powershell.codeFormatting.useCorrectCasing": true,
    "files.autoSave": "onWindowChange",
    "python.linting.pylintArgs": [
        "--load-plugins=pylint_django",
        "--errors-only"
    ],
}

Ответ 7

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

  • C0114 (missing-module-docstring)
  • C0115 (missing-class-docstring)
  • C0116 (missing-function-docstring)

Таким образом, следующий файл .pylintrc должен работать:

[MASTER]
disable=
    C0114, # missing-module-docstring

Понравилась статья? Поделить с друзьями:
  • D02c24 ошибка bmw f30
  • D01c0c ошибка bmw
  • D01b2a ошибка bmw f15
  • D019c3 ошибка bmw
  • D019bf ошибка bmw