Pythonでdocstring部分の構造をチェクする numpydocを使う。
numpydoc
Pythonのdocstringの書き方としては Google形式、 Numpy形式、 Sphinx形式 などがあります。
numpydocはそののNumpy形式なdocstringをチェックするための公式ツール。
必要な項目が入っているかどうか、型の定義の仕方、コロンの位置など細かい部分まで チェックしてくれます。
コードとは直接関係のないところなので実行結果には基本的に影響が無いところですが せっかく書くなら決まったフォーマットになってて欲しいので ツールを使ってチェックしていい感じにします。
使い方
pipで
1
|
|
として入れるとvalidate-docstrings
というコマンドが使えるようになります。
1
|
|
のような形でファイルをチェックできます。
PythonのプロジェクトをGitで管理している場合にはpre-commit用のhookも用意されているので、 .pre-commit-config.yamlに、
1 2 3 4 |
|
のように書いておけばcommit時にpythonファイルに対してvalidate-docstrings
がかけられます。
また、 pyproject-pre-commit も対応しているので、特にformatterやlinterを使ってる場合にはこれで一括管理すると便利です。
1 2 3 4 5 6 7 8 |
|
としておいて、
1
|
|
などでpyproject-pre-commitをインストールしておけばblack
などとともに
numpydoc
も環境にインストールされ、pre-commit以外でもコマンドを使えるようになって便利です。
設定ファイル
チェックする項目などの設定は設定ファイルから行います。
設定ファイルはPythonのプロジェクトで使われるpyproject.tomlかsetup.cfgです。
tomlならtool.numpydoc_validation
という項目で、cfgならtool:numpydoc_validation
という項目を作って設定を書いていきます。
設定項目はcheck
, exclude
, override_SS05
というものがありますが、全て必要な項目をリストで与える形になっています。
tomlなら[a, b, ...]
といったリストで、cfgならコンマで区切ったa,b,...
といった形で。
check
主に使うのはchecks
という値で、どの項目をチェックするかをリストで与えます。
何も与えないとすべてチェックしますが、
結構色々ときつく縛られるので必要なものだけ選ぶような形になるかと思います。
チョット特殊なのがall
を最初に与えると、それ以下のものを除く、という形になります。
1
|
|
ならEX01
, SA01
, ES01
のみをチェックする、ですが、
1
|
|
なら、EX01
, SA01
, ES01
を除く他のすべてをチェックする、になります。
調整する場合にはall
でチェックしてみて、エラーが出たらそのままで良いものだな、と思ったら
そのCheck IDをchecks
のall
のあとに加えていく、といった形がやりやすいです。
exclude
リストで与え、それらの値を クラス名や関数名が含んでいたらチェックをスキップするための値。
正規表現が使えて例にある
1 2 3 4 |
|
だとundocumented_method
と__repr__
という関数が除外されます。
最初に\.
でピリオドから始まるような指定をしていますが、
クラスの関数であれば<クラス名>.<関数名>
となりますし、
numpydocではファイルの直下に書かれている関数であればそのファイルをモジュールとみなして
1 2 |
|
ならtest.undocumented_method
というitemとして認識するので
これでundocumented_method
という名前に一致してその前後に別の文字列がついていれば
スキップされません。
1 2 |
|
みたいなシンプルなものにしておけばno_doc
を含むあらゆるものが無視されます。
ただ、numpydocの適用を回避するためだけに名前を変えるのは微妙なので、 無視したい関数がある場合は
1 2 |
|
といった感じでnumpydoc ignore
のコメントでスキップの適用が可能です。
GL08
はドキュメントが無い時にエラーを出すチェックです。
もし、簡単なコメントだけ書いてあって中身のチェックはしなくて良いと言う場合には それらのエラーに関する項目を書く必要があります。
override_SS05
SS05
はコメントの一番最初の行のまとめ文に関するチェックで、
Generates
の様な三人称単数現在形にせずに原型のGenerate
で始めよ、というもの。
が、このチェックが単純なチェックで最後がs
で終わるかどうか、だけを見ています。
https://github.com/numpy/numpydoc/blob/13b0f815763b3be13cb1cd34fd285f186fd5a142/numpydoc/validate.py#L676C1-L677C4
最後がs
で終わる動詞が全て引っかかってしまう状態です。
したがってProcess
やAccess
といった単語で始めた場合もSS05
のエラーが出てしまいます。
これを避けるために始まっても良い単語としてoverride_SS05
を定義しておきます。
例にある通り、
1 2 3 4 5 |
|
辺りは最初から書いておいても良いかと思います。後は必要になったら追加で。
override_XXXX
上のoverride_SS05
はSS05
に関するスキップ項目の追加ですが、
他のものでもIDを入れることでスキップ項目を追加できます。
SS02
は最初のSummary部分が小文字で始まるとエラーとしますが、
1 2 3 |
|
としておけば、test ...
と始まるSummaryに関しては許されます。
ただ他の部分に関してはあまりうまく使えるところは少ないかも。
特に直したところ
GL01: 先頭の部分がクォート行の次に来ないといけない
1 2 3 4 5 6 |
|
みたいなのはだめで、
1 2 3 4 5 6 7 |
|
の様にクォートの行とは分けないといけません。
これに関しては何かのlinterかドキュメントがクォート行と同じにしろ と言っていた気がして敢えてそうしていたのですが、 改めて Style guide を見てもそのようなことはないし、 flake8-docstrings とかを見ても特にそうったものはないし、 何かで思い込みをしていたのかも。
見た目としてはむしろ次の行から始まる形の方が見やすいので素直に従って直します。
PR10: 型を定義する際にその直前のコロンには前後にスペースを入れる
1 2 3 4 5 6 7 8 9 |
|
x: int
ではなくx : int
。
実際のコード中での定義では逆にコロンの前にスペースを空けないことが推奨されているので それとは違う形になり、ちょっと気持ち悪いところですが、 それに対する答えはここに詳細があります。
reStructuredTextの Definition Lists に準拠していて、それがそうなっているから、と。
reStructuredTextではLink関連やblockを作る際にコロンを使うので、 そういったmarkupと区別するため、markupと解釈されないようにスペースを空けて 単独のコロンでコロンそのものとみなされるようにしているようです。
Numpy StyleはreStructuredTextに準拠しているので Sphinxを使ってHTML化することが可能で そういった際にきちんと書き方を揃えて置く必要があります。
が、実際そういったことをしないことも多いので、 この辺はもしかしたらみやすさ的に通常コードと同様のスペースなし、というのもありかもしれません。
個人的にはどれもいずれドキュメント化とかするかもしれないので 一応スペースを空けてしたがっておこうと思ってます。
RT02: Returnsセクションで返り値が1つだけの場合は型のみ書く
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
返り値が1つの場合にはその定義部分で名前とかをつけずに型だけ書きます。
1 2 3 4 5 6 |
|
みたいにしてはだめ。
ただし、2つ以上返すような場合はそれぞれの名前をつけてもOK。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1つでも名前をつけてる所がほとんどだったので直しました。
設定したもの
上のものとかを一通り直して、最終的に設定したものは以下のようなもの。
1 2 3 4 5 6 7 8 9 10 |
|
EX01
, SA01
はExsample
, See Also
のセクションが無いとエラーになりますが、
これらに関してはすべてのオブジェクトに無くても良いと思うのでスキップ。
SA01
は最初にセクションが始まる前、1行summaryの次に1行明けて詳細説明を書くものですが、
それが無いとエラーになります。
特に短い関数で1行で簡潔に説明できるならわざわざそれ以上書くのもあれなので
これもスキップ。
GL08
はすべてのクラスや関数に関してdocstringを要求します。
これはflake8-docstringsとかでも検知する
ものですが、関数内でlambda的に作る関数みたいなものに対しても全てに対して要求するので
ちょっとスキップ。
ほかにもクラス内の関数で書いてないものもあったりで全てに要求しなくても良いかな、とも思うので。
また、モジュールレベル(ファイルレベル)にも無いと怒られるのでちょっときついです。
flake8-docstringsだとクラスのもの、関数のもの、といった感じで個別にスキップできるので 基本的にクラスのものが無いときだけエラーを出すようにしています。
RT01
は引数に関しては頑張って書いていても返り値に関して書いてない部分が多いため。
返り値は関数名や最初のSummaryの説明で自明なものが多いのでそういったものにまで
わざわざ書かなくても良いかなと思って書いてない部分が結構あり。
今後新たに始めるときにはチェックに入れ手も良いかも、程度で。
PR01
はクラス継承で
inherit-docstring
を使ってdocstringを継承したりすると、
書いてない、と怒られてしまうので外してます。
これはツール側でdocstringを解釈した上でチェックしてもらうよう何かラッパーツールみたいのをつくるとか 出来たら嬉しいかも。