Pythonパッケージコードのスタイル、フォーマット、リンター#

ポイント

  • pyOpenSciは、作者がPEP 8コードフォーマットガイドラインに従うことを要求します。

  • Blackやisortのようなコードフォーマッタを設定することで、PEP 8のスタイルガイドラインを実施し、一貫性のある読みやすいコードフォーマットにすることができます。

  • よく使われるツールは以下の通り: Black、Isort、flake8、Ruff

  • また、コミット前にフックをセットアップして、コミットするたびにコードフォーマッタをローカルで実行することもできます。

  • precommit.ci は、GitHub リポジトリに追加できるボットです。 pre-commit-config.yaml ファイルで指定したツールを使って、すべての PR にコードフォーマットを自動的に適用します。 時間を大幅に節約することができ、新しい貢献者にとっても貢献が簡単になります。

  • 自動化は良いことです! コードクオリティツールにコードを管理させることで、あなたは構造的で価値の高いタスクに集中することができます。

一貫したコードフォーマットとスタイルは、あなたのパッケージにとっても、科学的なPythonエコシステム全体にとっても有用です、なぜなら、同じようなフォーマットを使うことでコードが読みやすくなるからです。

例えば、空白や句読点のないこのような文章を見た場合、脳がそれを処理するのに時間がかかるだろう。

forinstanceifyousawasentencelikethisonewithoutany...

pyOpenSciの査読プロセスでは、標準的な Python PEP 8 format rules にできるだけ忠実に従うことが求められます。

pyOpenSciは特定のコードフォーマットツールを使うことを要求しません。 しかし、コードスタイルに一貫性と読みやすさを求めています。 以下は、その考察です:

  1. ワークフローでリンターとコードフォーマットツールを使用するメリット

  2. 科学的なPythonでよく使われるツール

  3. pre-commitフックとpre-commit.ciボットを設定することで、日々のワークフローやGitHubのプルリクエストでコードフォーマットツールを簡単に使えるようになります。

コードフォーマットツール (または複数のツール) を使って生活を楽にします#

コードフォーマットツール、あるいはフォーマットツールのセットを使うことをお勧めします。PEP 8 のフォーマット仕様をすべて手作業で適用するのは、メンテナにとって時間がかかるだけでなく、潜在的な新規貢献者にとっては障害になりかねないからです。 コードフォーマッタはあなたのコードを自動的に再フォーマットし、PEP 8の標準を遵守し、一貫したスタイル決定を適用します。

コードフォーマット一式のツールを設定します:

  • PEP 8の不整合を修正する時間を、あなたとあなたのメンテナチームに節約させます。

  • フォーマットとスタイルがコードベース全体で一貫していることを確認します。

  • レビューの際に、貢献者や他のメンテナと、個人的なコード形式の好みについて長々と議論するのは避けましょう。

  • コードレビューが付加価値に焦点を当てるように、コードベースにおける純粋な視覚的編集を避けます。

多くのパッケージは、コードフォーマット規則を適用するために一連のツールを使用しており、手作業でコードフォーマット要件を実装する手間を省いています。

(科学的な)Pythonエコシステム内のパッケージ間で統一されたコードフォーマットは、広くコードをスキャンし、理解し、貢献することを容易にします。

リンティング vs. フォーマットとスタイル#

その前に、いくつかの定義を整理しておきましょう。

コードリンティング#

コードリンターは、コードをレビューしてエラーや問題を特定するツールです。 リンターは通常、あなたのコードを修正しません。 エラーが何であり、どの行で発見されたかを教えてくれます。 後述するFlake8は、よく使われるコードリンターの一例です。

コードフォーマッタ(およびスタイラー)#

コードフォーマッタはあなたのコードを再フォーマットしてくれます。 Python に特化したコードフォーマッタは PEP 8 標準に従うことが多いです。 しかし、コードの一貫性に関する文体的な決定も行います。

Blackはよく使われるコードフォーマッターの一例です。 BlackはPEP 8の標準を適用すると同時に、文字列に対する二重引用符の一貫した使用や、リスト内の項目のスペーシングなどについても決定しています。

Black については後述します。

コードリンティング、フォーマット、スタイリングツール#

Black#

Black はコードフォーマッタです。 Blackは自動的に(そして unapologetically に)スペーシングの問題を修正し、コードフォーマットがパッケージ全体で一貫していることを保証します。 Blackはまた、いくつかの例外を除き、一般的にPEP 8スタイルガイドラインを遵守します。 以下にその例外の例をいくつか示します:

  • Blackのデフォルトの行の長さは、79文字の PEP 8 仕様ではなく、88 (79 + 10%) です。 しかし、行の長さは Black の設定で手動で上書きすることができます。

  • Blackはコメントやdocstringの行の長さを調整しません。

  • このツールはインポートの順番を見直したり修正したりはしません(そのためには isort や ruff が必要です - 下記を参照してください)。

Tip

Black があなたのコードをどのようにフォーマットするか興味がある場合は、 Black playground を使用することができます。

Blackのようなコードフォーマッターを使うことで、書式を気にするよりもコードの機能に時間を割くことができます。

Flake8#

Python の pep8 フォーマットの標準に準拠するために、flake8 をコードフォーマットツールボックスに追加するとよいでしょう。

flake8 は:

  • コード中の79文字を超えるすべての行にフラグを付ける(docstringやコメント中の行も含む)

  • カンマの後にスペースがないなど、PEP 8ガイドラインに抵触するフラグスペースの問題

Flake8はまた、モジュール内の未使用のインポートや未使用の宣言変数にフラグを立てます。

下記は stravalib というパッケージ内の Python ファイルに対してコマンドラインで flake8 filename.py を実行したときの出力です。

PEP 8の行の長さの標準は79文字です。

flake8はコマンドラインでmodel.pyモジュールで見つかった問題のリストを返すことに注意してください。 Pythonファイル自体は変更されません。 この出力を使って、問題を一行ずつ手作業で修正することができます。

(stravalib-dev) username@computer stravalib % flake8 stravalib/model.py
stravalib/model.py:8:1: F401 'os' imported but unused
stravalib/model.py:29:80: E501 line too long (90 > 79 characters)
stravalib/model.py:34:80: E501 line too long (95 > 79 characters)
stravalib/model.py:442:80: E501 line too long (82 > 79 characters)
stravalib/model.py:443:39: E231 missing whitespace after ','
stravalib/model.py:493:20: E225 missing whitespace around operator
stravalib/model.py:496:80: E501 line too long (82 > 79 characters)

Isort#

Python import は、あなたのパッケージのモジュールが必要とする Python パッケージを指します。 importsは常にパッケージ内の各Pythonモジュールの先頭に配置されるべきです。

PEP 8には、これらのimportの順序に関する具体的な基準があります 。これらの基準を以下に示します:

import は以下の順序でグループ化します:

  • 標準ライブラリの import 。

  • 関連するサードパーティの import 。

  • ローカルアプリケーション/ライブラリ固有のインポート。

flake8 はあなたのコードで使用されていないインポートを特定しますが、パッケージのインポートの順序に関する問題を修正したり特定したりすることはできません。

isort は、コードの中でインポートの順番が狂っている箇所を特定します。 そして、あなたのコードを修正し、すべてのインポートの順番を自動的に並べ替えます。 これで、コードを整理するときに考えることが1つ減ります。

isort の使用例#

isort が実行される前にコードがインポートされます:

以下では、pandasはサードパーティのパッケージであり、typingは Python とともに配布されている Python のコアパッケージであり、 examplePy.temperatureはファーストパーティモジュールで、インポートを行うファイルと同じパッケージに属していることを意味します。 また、以下のインポートにスペースがないことに注意してください。

from typing import Sequence

import pandas

from examplePy.temperature import fahrenheit_to_celsius


プロジェクトルートから以下を実行してください:

isort src/examplePy/temporal.py

Python ファイル temporal.py は isort が実行された後にインポートされます。

from typing import Sequence

import pandas

from examplePy.temperature import fahrenheit_to_celsius


Ruff#

Ruff は、コードクオリティのエコシステムに新しく追加されたもので、リリース以来人気を集めています。 ruff はPythonのリンターであり、コード整形ツールでもあります。 そのため、 ruff はここで紹介する他のすべてのツールの置き換えとして、あるいはいくつかのツールの補完として使用することができます。

ruff には他のリンターと異なる興味深い特徴があります:

  • pyproject.toml のリンター設定

  • 数百のルールが含まれ、その多くは自動的に修正可能です

  • ルールの説明、例については F403 を参照してください。

  • 実行時間が速いので、大規模プロジェクトでも素早いフィードバックループが可能。

以下は ruff を使い始めるための簡単な設定です。 これは pyproject.toml に記述します:

[tool.ruff]
select = [
    "E", # pycodestyle errors
    "W", # pycodestyle warnings
    "F", # pyflakes. "E" + "W" + "F" + "C90" (mccabe complexity) is equivalent to flake8
    "I", # isort
]

プロジェクトによっては、インポートを正しく並べ替えるために以下を追加するとよいです:

[tool.ruff.isort]
known-first-party = ["examplePy"]

ローカルワークフローでコードフォーマッタを使用する方法#

リンター、コードフォーマッター、お気に入りのコーディングツール#

リンターは上記のようにコマンドラインツールとして実行できます。 また、お気に入りのコーディングツール(VScodeやpycharmなど)の中で実行することもできます。 例えば、ファイルを保存するときにBlackやisortのようなツールを実行させたいかもしれません。 エディタによっては、お気に入りのコード整形ツールをオンデマンドで実行するショートカットを設定することもできる。

プレコミットフックを使って、コミット時にコードフォーマッタやリンタを実行#

Pythonのパッケージリポジトリに pre-commit hook をセットアップすることもできます。

プレコミットフックとは、git リポジトリにコミットを適用するときに (複数の) アクションを実行するためのツールです。

pre-commit フックのワークフロー例#

precommit のワークフローは次のようになる: 次のように入力して実行します:

コマンドラインで git commit -m "message here" を実行

  • returnを押すと、pre-commitは .pre-commit-config.yaml ファイルで設定したツールを実行します。

  • pre-commitフックで設定したツールが、コードに変更を加えたりエラーを発見したりすることなく正常に実行されると、コミットがリポジトリに適用されます。

  • フックで設定したツールがファイルにエラーを見つけた場合、コミットはリポジトリに適用されません。 上の説明で、Black のようなコードフォーマッタが実行され、コードを再フォーマットすることを思い出してください。 flake8 のようなリンターは、コードのどこにシンタックス上の問題があるかを詳細に出力してくれます。 そして、それらの問題を手動で修正する必要があります。

  • すべての修正が適用されたら、コミットするファイルを再追加 (stage) することができます。 そしてコミットを再実行します。

左から右へ、 pre-commit ワークフローのステップを示す図。

pre-commit のワークフローは、まず git にステージすべき変更があるファイルを追加することから始まります。次に git commit を実行します。git commit を実行すると、pre-commit フックが実行されます。この例では、コードフォーマッタである Black とリンターである flake8 の両方が実行されます。すべてのファイルが Black と flake8 のチェックをパスすれば、コミットが記録されます。パスしなかった場合、コミットはキャンセルされます。flake8 の問題を修正し、コミットするファイルを再度追加/ステージする必要があります。画像出典#

重要

Python のコードベースがあり、複数のメンテナがそのコードに積極的に取り組んでいて、Black のようなツールを使うつもりなら、チーム全体で調整するようにしてください。 Blackをパッケージ全体に適用する最初のコミットは、おそらくコードのかなりの部分を変更するでしょう。 これは、新しい変更がマージされる前に、オープンなPRや新しいPRでマージ競合が発生する可能性があります。

一般的なコミット前のチェック#

ツールを呼び出すだけでなく、Pre-commitには ビルトインフォーマットフック が用意されています。また、 trailing-whitespace のように、コミット前のワークフローに追加することで、クリーンで合理的なコードファイルを確保できるものもあります。

以下に、 pre-commit-config.yaml ファイルの例を記載します。このファイルに記載されている内容のセットアップ方法の例も記載されています。

pre-commit.ci#

pre-commit.ci は、あなたの新しい親友になるかもしれないボットです。 このボットをリポジトリにセットアップすると、次のようなことができるようになります:

  • コミット前のフック設定をすべて使用して、すべてのプルリクエストをチェックします

  • あなたが望むなら、事前にコミットした修正と一緒にあなたのリポジトリにプルリクエストを提出します、あなたや新しい貢献者は、フォーマットの問題がある pr を再フォーマットする時間を節約できます。

  • また、どのプルリクエストでもボットを呼び出して、実行し / コードを修正することができます。

pre-commit.ci ボットは、ローカルで pre-commit をセットアップするのと同じ pre-commit-config.yaml ファイルを使用します。

このようなボットを設置することは価値があるなぜなら:

  • メンテナにとっては、コードフォーマットの修正に頭を悩ませる必要がなくなり、楽になります。 ボットが代わりにやってくれます。

  • ローカルでpre-commitをセットアップしたり、コードのlintを心配したりする必要がないので、新規参入者も楽になります。GitHub上で直接コードを修正することもできます。

git pre-commit フックの設定#

ローカルで pre-commit をセットアップするには、3つのことをする必要があります:

  1. pre-commitをインストールする(そしてリポジトリに開発要件として含める)

python -m pip install pre-commit

# or

conda install -c conda-forge pre-commit
  1. パッケージディレクトリのルートに .pre-commit-config.yaml ファイルを作成します。

以下は、 pre-commit とpre-commit.ciボットのセットアップに使用できる .pre-commit-cofig.yaml ファイルの例です。

repos:
  - repo: https://github.com/PyCQA/isort
    rev: dac090ce4d9ee313d086e2e89ab1acb8c2664fa1 # frozen: 9.0.0a3
    hooks:
      - id: isort
        files: \.py$
  # Misc commit checks using built in pre-commit checks
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
    # ref: https://github.com/pre-commit/pre-commit-hooks#hooks-available
    hooks:
      # Autoformat: Makes sure files end in a newline and only a newline.
      - id: end-of-file-fixer
      # Lint: Check for files with names that would conflict on a
      # case-insensitive filesystem like MacOS HFS+ or Windows FAT.
      - id: check-case-conflict
      - id: trailing-whitespace
  # Linting: Python code (see the file .flake8)
  - repo: https://github.com/PyCQA/flake8
    rev: c48217e1fc006c2dddd14df54e83b67da15de5cd # frozen: 7.3.0
    hooks:
      - id: flake8
  # Black for auto code formatting
  - repo: https://github.com/psf/black
    rev: c6755bb741b6481d6b3d3bb563c83fa060db96c9 # frozen: 26.3.1
    hooks:
      - id: black
        language_version: python3.10
# Tell precommit.ci bot to update code format tools listed in the file
# versions every quarter
# The default it so update weekly which is too many new pr's for many
# maintainers (remove these lines if you aren't using the bot!)
ci:
  autoupdate_schedule: quarterly

このファイルでは、各 git commit の前に自動的に起動するフックを指定します。この例では、バージョン 6.0.0 を使って flake8 を指定しています。

  1. pre-commit install を使用して、(複数の)コミット前フックをインストールします。 これにより、pre-commit yaml ファイルで指定したすべてのフックがあなたの環境にインストールされます。

上記を実行したら、コードを書き始める準備ができました。 git commit を実行するたびに pre-commit が実行されます。

概要#

pyOpenSciは、プレコミットフック、CI、またはコードフォーマットを管理する他のインフラストラクチャを使用しているかどうかに関係なく、あなたのパッケージにリンターとコードスタイラーをセットアップすることを提案します。 これらのツールをセットアップすることで、あなた(または貢献者)がコードを書いたときに、コードの構造に関する自動的なフィードバックを得ることができます。 また、コードを整形してくれるblackのようなツールを使うことで、コードのフォーマットやスタイルにまつわる決断に必要な労力を減らすことができます。