За основу взят стиль, описанный в PEP8 (https://peps.python.org/pep-0008/).
Для форматирования кода используется black
(https://github.com/psf/black).
- Максимальная длина строки - 110 символов вместо 78. Это позволяет не сильно дробить строки при написании кода и в то же время просматривать изменения в две колонки в интерфейсе Github (проверил на мониторе с разрешением 1920x1080).
- Используем двойные кавычки для строк (
"string"
), не используем одинарные ('string'
).
Описанные здесь правила взяты напрямую из PEP8 и перечислены здесь для удобства.
Отступы делаются пробелами, по 4 пробела на уровень.
Короткий свод правил:
- Имена классов пишем в
JavaCamelCase
с большой буквы. Аббревиатуры (например,MQTT
,HTTP
) пишутся большими буквами. Это же касается названий типов (например, дляnamedtuple
). - Имена функций, методов, аргументов, переменных пишем в
snake_case
. - Имена полей и методов класса, задуманных как приватные, должны начинаться с нижнего подчёркивания (
_
). - Имена констант пишем в
SNAKE_CASE
заглавными буквами.
При написании кода на python3 может понадобиться создавать классы-интерфейсы (с metaclass=abc.ABCMeta
).
Имена таких классов начинаем с заглавной буквы I
(например, IMyInterface
).
Пример:
from collections import namedtuple
MY_CONST = 42
MyNamedTuple = namedtuple("MyNamedTuple", "arg_1 arg_2")
class MyClass:
"""
docstring for this class
"""
CLASS_PI = 3.14159
def __init__(self, my_arg):
self._my_arg = my_arg # Объявили приватное поле
def print_my_arg(self):
print(self._my_arg)
При написании кода на python3 рекомендуется по возможности использовать аннотации типов (https://docs.python.org/3.5/library/typing.html). При написании аннотаций стоит помнить, что на контроллерах доступна версия Python 3.5, то есть, не все поддерживаемые аннотации типов из Python 3 будут доступны.
Аннотации позволяют использовать автодополнение в редакторах кода, а также позволяют лучше понимать происходящее в коде при чтении.
Можно не увлекаться созданием сложных описаний типов (вроде Iterable[Mapping[str, Union[int, str]]]
),
но простые случаи описывать стоит.
from collections import namedtuple
MyNamedTuple = namedtuple("MyNamedTuple", "arg_1 arg_2")
def format_tuple(tuple: MyNamedTuple, delimiter: str = ", ") -> str:
return str(tuple.arg_1) + delimiter + str(tuple.arg_2)
Black форматирует длинные перечисления аргументов функций или элементов коллекций примерно так:
- пока помещается в строку, положить в одну строку;
- если не поместилось, перенести начало списка на следующую строку;
- если и так не поместилось, писать каждый элемент списка на новой строке.
Это может породить неоднородности при форматировании там, где раньше было хорошо. Например:
# до форматирования
self.parser.add_argument('-m', '--model', dest='device_model', type=str,
help='Модель устройства', required=True, choices=some_long_function_name_call())
self.parser.add_argument('-r', '--hw-rev', dest='hw_rev', type=str,
help='Версия платы', required=False, default=None)
self.parser.add_argument('-p', '--batch', type=validate_batch,
help='Номер партии', required=True)
# после форматирования
self.parser.add_argument(
"-m",
"--model",
dest="device_model",
type=str,
help="Модель устройства",
required=True,
choices=some_long_function_name_call(),
)
self.parser.add_argument(
"-r", "--hw-rev", dest="hw_rev", type=str, help="Версия платы", required=False, default=None
)
self.parser.add_argument("-p", "--batch", type=validate_batch, help="Номер партии", required=True)
Для того чтобы бороться с такими ситуациями, рекомендуется добавлять после последнего аргумента запятую. В таком случае black всегда будет разбивать аргументы по строкам:
# до форматирования
self.parser.add_argument('-m', '--model', dest='device_model', type=str,
help='Модель устройства', required=True, choices=some_long_function_name_call(),)
self.parser.add_argument('-r', '--hw-rev', dest='hw_rev', type=str,
help='Версия платы', required=False, default=None,)
self.parser.add_argument('-p', '--batch', type=validate_batch,
help='Номер партии', required=True,)
# после форматирования
self.parser.add_argument(
"-m",
"--model",
dest="device_model",
type=str,
help="Модель устройства",
required=True,
choices=some_long_function_name_call(),
)
self.parser.add_argument(
"-r",
"--hw-rev",
dest="hw_rev",
type=str,
help="Версия платы",
required=False,
default=None,
)
self.parser.add_argument(
"-p",
"--batch",
type=validate_batch,
help="Номер партии",
required=True,
)
То же самое рекомендуется делать с объявлениями списков и словарей.
Приятным дополнением такого форматирования будет чуть более лаконичный diff при добавлении или перестановке элементов в списке.
Для автоматической проверки кода на CI используется pylint
.
Для проверки форматирования (и непосредственно форматирования) используются black
и isort
.
black
автоматически форматирует файл согласно своим внутренним правилам. isort
группирует и
сортирует список подключаемых модулей.
Файлы с настройками находятся в репозитории codestyle в директории python
.
Что именно происходит на CI - можно посмотреть здесь (поискав по runPythonChecks
; все скрипты берутся из codestyle).
⚠️ На момент подготовки версии 1.0 ошибкиpylint
не будут приводить к падению сборки по умолчанию (при снятой галочке "angry pylint"). Это связано с множеством ложных срабатываний, в частности, на тестах с использованиемpytest
.Тем не менее, проверки будут проводиться и будут собираться их логи для анализа в будущем.
На CI и в локальной системе разработчика тулзы должны быть одинаковыми, поэтому black, isort и pylint устанавливаем путём создания virtualenv внутри каждого проекта. Если нужно, тулзы и конфиги можно доустановить и в существуюший venv проекта.
ℹ️ Isort имеет очень своеобразную захардкоженную внутри логику поиска конфига и определения корня проекта, из-за чего и получались разногласия с запуском локально и на CI.
Похоже, единственно рабочее решение - иметь pyproject.toml в корне проекта.
Если в проекте нет pyproject.toml
, requirements.txt
- выкачать их отсюда и положить в корень проекта.
⚠️ Только для Linux! (в Windows venv через vscode создаётся не в проекте, а системный) Открыть в vscode проект ->Ctrl+Shift+P
->Python: Create Environment
->Venv
Ctrl + Shift + P
-> Python: Create Terminal
-> откроется терминал с уже активным venv; выполнить: pip3 install -r requirements.txt
Выполнить в корне проекта
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip3 install -r requirements.txt
$ deactivate
Можно доустановить requirements.txt из codestyle и в свой venv.
- открыть в командной строке корневую директорию проекта (запустив от имени администратора!)
- выполнить
$ py -m venv .venv
$ .\.venv\Scripts\activate
$ py -m pip install --upgrade pip
$ py -m pip install -r requirements.txt
$ deactivate
- настроить VSCode далее по инструкции; с конфигом для windows
Запуск руками (в директории проекта; pyproject.toml присутствует)
Активировать venv (пример по умолчанию; venv может быть и другим)
$ source .venv/bin/activate
$ python3 -m pylint $(find . -name '*.py')
$ python3 -m black --config pyproject.toml --check --diff $(find . -name '*.py')
$ python3 -m isort --settings-file pyproject.toml --check --diff $(find . -name '*.py')
$ python3 -m black --config pyproject.toml $(find . -name '*.py')
$ python3 -m isort --settings-file pyproject.toml $(find . -name '*.py')
ℹ️ При изменении форматирования в репозитории может сильно испортиться вывод
git blame
.Начиная с версии 2.23,
git
умеет игнорировать изменения из таких коммитов. Для этого при смене форматирования кода надо будет добавлять в репозиторий файл.git-blame-ignore-revs
.Подробнее об этом можно почитать здесь: https://black.readthedocs.io/en/stable/guides/introducing_black_to_your_project.html
по завершению - выйти из venv
$ deactivate
Один раз настраиваем VSCode:
- устанавливаем расширение Python:
Ctrl-Shift-X
(открывает Marketplace), в строке поиска вводимpython
, устанавливаем первое расширение из списка (от Microsoft); - аналогично устанавливаем расширения black, isort, pylint (все от Microsoft)
- открываем редактор настроек VSCode:
Ctrl-Shift-P
, в поиске вводимsettings json
, выбираемPreferences: Open Settings (JSON)
; - в открывшемся редакторе вводим (или добавляем опции в существующий объект);
- если файлы настроек расположены не в
~/.config/wb/
, то заменяем${env:HOME}/.config/wb/
на корректный путь:
{
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python3",
"python.terminal.activateEnvironment": true,
"black-formatter.args": [
"--config=${workspaceFolder}/pyproject.toml"
],
"black-formatter.importStrategy": "fromEnvironment",
"isort.check": true,
"isort.args": [
"--settings-path=${workspaceFolder}"
],
"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
"pylint.lintOnChange": true,
"isort.importStrategy": "fromEnvironment",
"pylint.importStrategy": "fromEnvironment",
"pylint.severity": {
"refactor": "Warning"
},
}
{
"python.defaultInterpreterPath": "${workspaceFolder}\\.venv\\Scripts\\python.exe",
"python.terminal.activateEnvironment": true,
"black-formatter.args": [
"--config=${workspaceFolder}\\pyproject.toml"
],
"black-formatter.importStrategy": "fromEnvironment",
"isort.check": true,
"isort.args": [
"--settings-path=${workspaceFolder}"
],
"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
"pylint.lintOnChange": true,
"isort.importStrategy": "fromEnvironment",
"pylint.importStrategy": "fromEnvironment",
"pylint.severity": {
"refactor": "Warning"
},
}
Для оценки покрытия тестов используется плагин pytest coverage
. Оценка показывает процент выполнившихся строк кода и веток условий относительно общего объема файлов, использованных в процессе тестирования.
В зависимости от того, как вы запускаете тесты, нужно использовать разные сценарии.
Минимально допустимое значение покрытия для прохождения тестов указывается в аргументе --cov-fail-under
- Скачать из codestyle-репозитория файл
.coveragerc
и положить в корень проекта. - Запустить
$ pytest --cov --cov-config=.coveragerc --cov-report=term --cov-branch --cov-fail-under=<limit>
- Настроить venv по инструкции для codestyle-проверок. В venv должен установиться пакет pytest-cov.
- Скачать из codestyle-репозитория файл
.coveragerc
и положить в корень проекта. - Запустить
$ pytest --cov --cov-config=.coveragerc --cov-report=term --cov-branch --cov-fail-under=<limit>
Работает только для wbdev ndeb
и wbdev cdeb
- Скачать из codestyle-репозитория файл
.coveragerc
и положить в корень проекта. - Запустить
$ export WBDEV_PYBUILD_TEST_ARGS="--cov --cov-config=../../../.coveragerc --cov-report=term --cov-branch --cov-fail-under=<limit>"
$ wbdev ndeb
Для сборки внутри devcontainer и внутри venv есть возможность сгенерировать html отчет (для варианта wbdev сложные аргументы запуска, поэтому не приводим его). Для включения генерации нужно добавить опцию запуска
--cov-report=html
В папке проекта создастся папка htmlcov. Чтобы посмотреть отчет, откройте файл index.html в браузере.
- Скачайте из codestyle-репозитория файл
.coveragerc
и положите в корень проекта. - Установите плагин для Python и настройте запуск тестов через pytest
- Откройте настройки workspace: нажмите комбинацию
Ctrl-Shift-P
, в поиске введитеsettings json
, выбираемPreferences: Open Workspace Settings (JSON)
- К массиву опций запуска добавляем:
"--cov","--cov-config",".coveragerc","--cov-report","term","--cov-branch","--cov-fail-under","<limit>"
Должны получиться примерно такие настройки:
{
"python.testing.pytestArgs": [
"tests","--cov","--cov-config",".coveragerc","--cov-report","term","--cov-branch","--cov-fail-under","<limit>"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
- Установите плагин Coverage Gutters
- К опциям запуска добавьте
"--cov-report","xml"
- Проведите тестирование
- Откройте файл, покрытие которого хотите посмотреть. В статус-баре VSCode найдите надпись
Watch
и нажмите ее для включения подсветки.
- Initial version