Заметки про Machine Learning, Data Science и Analytics Engineering

Простые шаги сделать ваш Python код лучше

У многих из вас есть GIT- репозитории с кодом, в этой заметке я расскажу как сделать ваш Python код лучше.

В качестве примера я буду использовать этот репозиторий: https://github.com/Aykhan-sh/pandaseda

Форкнем его и попробуем сделать код лучше.

Улучшим читаемость кода

Улучшить читаемость вашего кода очень просто. Мы будем использовать библиотеки для синтаксического форматирования и проверки.

Для начала создадим в репозитории файлы конфигураций для flake8, mypy и black

Установим их для начала:

pip install black flake8 mypy

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

Файл setup.cfg для flake8 и mypy

[flake8]
max-line-length = 120
exclude =.git,__pycache__,docs/source/conf.py,build,dist,tests
ignore = I101,D100,D101,D102,D103,D104,D105,D107,D401,E203,I900,N802,N806,N812,W503,S311,S605,S607,ISC003,ISC001,T101,T000,F541,PL123
per-file-ignores = __init__.py:F401,F403

[mypy]
ignore_missing_imports = True
disallow_untyped_defs = True
check_untyped_defs = True
warn_redundant_casts = True
no_implicit_optional = True
strict_optional = True

[mypy-tests.*]
ignore_errors = True

Разберём блок [flake8]:

  • max-line-length — максимальная длина строки
  • exclude — список папок, которые исключаются из сканирования flake8
  • ignore — список ошибок/ предупреждений, которые так же исключаются. Например: I101: The names in your from import are in the wrong order и D100 — Missing docstring
  • per-file-ignores — исключить из сканирования определенный файл

flake8 запустить очень просто:

flake8

Помните, что flake8 не модифицирует код, а просто проверят его. Подправить ошибки придется в ручную.

Теперь поговорим про mypy. У Python нет обязательной статической типизации, но рекомендуется добавлять типы в аргументы функции и возвращаемые типы. Для этого просто запустим mypy и не забудьте подправить ошибки:

mypy .

Теперь создадим файл pyproject.toml для black. black поможет форматировать ваш код в соответствии со стандартом.

Файл pyproject.toml

[tool.black]
line-length = 119
target-version = ['py36']
include = '\.pyi?$'
exclude = '''
/(
    \.eggs
  | \.git
  | \.hg
  | \.mypy_cache
  | \.tox
  | \.venv
  | _build
  | buck-out
  | build
  | dist
)/
'''

Разберём код:

  • line-length — длина строки
  • target-version — версии Python. py36 — Python 3.6, можно и для других версий.
  • include — список того, что включаем в форматирование
  • exclude — список того, что исключаем из форматирования

Запуск тоже очень простой:

black .

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

Так выглядел код до правки:

Код до применения black
Код до применения black

А так после правки:

Код после применения black
Код после применения black

Код стал читабельнее и теперь у нас статическая типизация. Особое внимание обратите на тип Union. Использовать его необходимо когда допускается использование не любых типов, а только некоторых. Перечислить их нужно в квадратных скобках.

isort

isort — это библиотека Python для сортировки импорта по алфавиту с автоматическим разделением на разделы и по типу.

Установка:

pip install isort

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

isort .

Так выглядели наши импорты до правки:

python code before isort
Импорты до isort

А так после применения isort:

python code after isort
Импорты после isort

Так же рекомендую сохранить отредактированный файл.

pre-commit hook

Мы можем запускать black, flake8 и mypy вручную, но это не удобно. Мы можем автоматизировать процесс с помощью pre-commit hook.

Файл .pre-commit-config.yaml

repos:
  - repo: https://github.com/asottile/pyupgrade
    rev: v2.19.4
    hooks:
      - id: pyupgrade
        args: [ "--py38-plus" ]
  - repo: https://github.com/pre-commit/mirrors-isort
    rev: 1ba6bfc # Use the revision sha / tag you want to point at
    hooks:
      - id: isort
        args: ["--profile", "black"]
  - repo: https://github.com/psf/black
    rev: 21.6b0
    hooks:
      - id: black
  - repo: https://gitlab.com/pycqa/flake8
    rev: 3.9.2
    hooks:
      - id: flake8
        language_version: python3
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.0.1
    hooks:
      - id: check-docstring-first
      - id: check-json
      - id: check-merge-conflict
      - id: check-yaml
      - id: debug-statements
      - id: end-of-file-fixer
      - id: trailing-whitespace
      - id: requirements-txt-fixer
  - repo: https://github.com/pre-commit/mirrors-pylint
    rev: 56b3cb4
    hooks:
      - id: pylint
        args:
          - --max-line-length=120
          - --ignore-imports=yes
          - -d duplicate-code
  - repo: https://github.com/pre-commit/pygrep-hooks
    rev: v1.9.0
    hooks:
      - id: python-check-mock-methods
      - id: python-use-type-annotations
      - id: python-check-blanket-noqa
      - id: python-use-type-annotations
      - id: text-unicode-replacement-char
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: 9feadeb
    hooks:
      - id: mypy
        exclude: ^tests/
        args:
          [
              --disallow-untyped-defs,
              --check-untyped-defs,
              --warn-redundant-casts,
              --no-implicit-optional,
              --strict-optional
          ]

Теперь каждый коммит будет запускаться наше форматирование и проверки. Теперь сделаем, что бы Github запускал их при каждом pull request.

Github Actions

Для наших целей будем использовать Github Action. Создадим файл ci.yaml

Файл .github/workflows/ci.yaml

name: Python package

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.6]
    steps:
    - uses: actions/checkout@v2
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}
    # You can test your matrix by printing the current Python version
    - name: Display Python version
      run: python -c "import sys; print(sys.version)"
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements_dev.txt
    - name: Run black
      run:
        black --check .
    - name: Run flake8
      run: flake8
    - name: Run Mypy
      run: mypy pandaseda

Основное внимание обратите на:

- name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements_dev.txt
    - name: Run black
      run:
        black --check .
    - name: Run flake8
      run: flake8
    - name: Run Mypy
      run: mypy pandaseda

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

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

Github Action
Github Action

В случае ошибок на почту будет отправлен email:

Github Action email
Github Action email

Саму ошибку можно так же посмотреть в Action:

Github Action Log
Github Action Log

Заключение

В этой заметке получилось рассмотреть несколько библиотек для работы с вашим кодом. Отдельно рассмотрели pre-commit hook и Github Actions.

Дополнительный материал

Share it

Если вам понравилась заметка - подписывайтесь на мой канал в телеграме https://t.me/renat_alimbekov или вы можете поддержать меня Become a Patron!


Интересные записи в этой рубрике: