Pythonでのプログラム開発ベストプラクティス
すべてのPython開発者が知っておくべきこと
他の言語やツールと同様に、Pythonにもコードを書く前、書いている最中、書いた後に従うべきベストプラクティスがあります。これらは、コードを読みやすくし、品質を高めることになります。
1. クリーンなコードの重要性
クリーンコードは非常に重要であり、常に意識している必要があります。クリーンコードの実践を学び、それに基づいてコードを書くことです。コミュニティでクリーンについて知ることは重要であり、そうすることで他の人の理解を助け、プログラムをより読みやすくすることができます。
それには、以降で説明するような方法があります。
2. The Zen of Python の遵守
これは、1999年にPythonのメーリングリストで、 Tim Peters氏が「Pythonらしさとはなにか?」と問われて投稿した記事がもとになっていて、この実装は2004年にPEP20 - The Zen of Python として提案されたものです。Python でプログラム開発をするときに指針とするべき非常に重要な考えです。 The Zen of Python, by Tim Peters
Beautiful is better than ugly.
醜いより美しいほうがいい。
Explicit is better than implicit.
暗示するより明示するほうがいい。
Simple is better than complex.
複雑であるよりは平易であるほうがいい。
Complex is better than complicated.
それでも、込み入っているよりは複雑であるほうがまし。
Flat is better than nested.
ネストは浅いほうがいい。
Sparse is better than dense.
密集しているよりは隙間があるほうがいい。
Readability counts.
読みやすいことは重要である。
Special cases aren't special enough to break the rules.
特殊であることはルールを破る理由にならない。
Although practicality beats purity.
しかし、実用性を求めると純粋さが失われることがある。
Errors should never pass silently.
エラーは隠すな、無視するな。
Unless explicitly silenced.
ただし、わざと隠されているのなら見逃せ。
In the face of ambiguity, refuse the temptation to guess.
曖昧なものに出逢ったら、その意味を適当に推測してはいけない。
There should be one-- and preferably only one --obvious way to do it.
誰が見ても明らかな、たったひとつのやり方があるはずだ。
Although that way may not be obvious at first unless you're Dutch.
そのやり方は一目見ただけではわかりにくいかもしれない。オランダ人にだけわかりやすいなんてこともあるかもしれない。
Now is better than never.
ずっとやらないでいるよりは、今やった方がよい。
Although never is often better than *right* now.
でも、今「すぐ」にやるよりはやらないほうがマシなことも多い。
If the implementation is hard to explain, it's a bad idea.
コードの内容を説明するのが難しいのなら、それは悪い実装である。
If the implementation is easy to explain, it may be a good idea.
コードの内容を容易に説明できるのなら、おそらくそれはよい実装である。
Namespaces are one honking great idea -- let's do more of those!
名前空間は優れたアイデアのひとつだ、積極的に利用すべきである。
3. スタイルガイドラインに従ったコードを書く
PEP8 には、コミュニティで作成された素晴らしい提案があります。 PEPとはPython Enhancement Proposalsの略で、開発のためのガイドラインと基準です。これは、すべてのPythonコードが同じように見え、同じように感じられるようにするためのものです。
ガイドラインの1つとして、クラス名をCapWords規則で命名することが挙げられます。
変数、関数、メソッドなどに適切な名前を使用する。
変数、関数、メソッド、パッケージ、モジュールは小文字をアンダースコアで区切る: this_is_a_variable
クラスや例外は単語の先頭文字を大文字にする: CapWords
プロテクトされたメソッドや内部関数: _single_leading_underscore
プライベートメソッドはアンダースコア2つで始める: __double_leading_underscore
定数は大文字でアンダースコアで単語を区切る:CAPS_WITH_UNDERSCORES
インデントには4つのスペースを使用します。規約の詳細については、PEP8を参照してください。
他に読みやすいコードを書くためには次のようなものがあります。
行は72文字までとする。
import文はコメントの直後に記述する。
概要のみの1行、詳細な説明の複数行を記述する。
1行目は概要のみ簡潔に記述する。
複数行の場合は、空行を挟んで説明を記述する。
docstringと対象定義の間に空行を挟まない。
モジュールの場合は、公開するクラス、関数などについて1行の説明を付けて一覧化する。
クラスの場合は、何をするクラスなのかの概要、外部に公開するメソッドやインスタンス変数などを記述する。
クラスの場合は、冒頭ではdocstringとの間に空行を挟む。
関数の場合は、何をするのかの概要、パラメータ、戻り値、発生する例外などについて記述する。
パッケージから全てをインポートすることは避ける。
最後の「パッケージから全てをインポートすることは避ける」については、スタイルというより意図しない不具合を予防するものです。このようなインポートはグローバルな名前空間を汚染してしまい、名前衝突の原因になります。
悪い
code: pytohn
from math import *
a = sqrt(3)
良い
code: python
from math import sqrt
a = sqrt(3)
良い++
code: python
import math
a = math.sqrt(3)
こうしたスタイルガイドに従ったコーディングになっているかを自動的に調べてくれる静的解析ツールがあります。これらを利用することをおすすめします。
4. 積極的にPyPIを使う
Pythonが人気を博している理由のひとつに、PyPI(Python Package Index) があります。この資料作成時点で、34万以上のプロジェクトがあります。PyPIに目的の機能を持つコードがあれば、積極的に使うことで、時間を節約し、より重要なことに集中することができます。 5. 仮想環境の使用する
PyPIで公開されているPythonのパッケージは pip install で簡単にインストールすることができます。しかし、実際にはこの方法では、すべての依存関係が1つのバージョンのPythonインタープリタにインストールされるため、依存関係の競合が発生します。
プロジェクトごとに依存関係をインストールするのが良い方法です。つまり、各プロジェクトはそのプロジェクトに必要な依存関係だけを含み、それ以上は含みません。これにより、異なるプロジェクトで必要とされる異なるパッケージのバージョンの衝突を防ぐこともできます。
この問題を解決するために、仮想環境という概念があります。つまり、それぞれのプロジェクトは、固定のPythonバージョンと、そのプロジェクトに固有の固定の依存関係を持つ、独自の仮想環境を持っているのです。
ここで、Python での「仮想環境」には2つのパッケージが利用されます。
venv: 同じPythonバージョンで、パッケージの競合を避けるために使用される。
pyenv: 異なるPythonバージョンで実行したいときに使用される。
実際のところ、pyenv を利用することで venv の代替になります。
6. パッケージ管理ツールを使用する
複数の仮想環境とパッケージの依存関係の解決ではパッケージ管理ツールを使うとスマートに管理することができます。この場合、pipenv や poetry などが人気となっています。また、poetry などのパッケージ管理ツールを使うことでこのパッケージ作成も簡単に行えるため、作成したパッケージをアップロードしてPythonに貢献するようにしましょう。
7. コードリポジトリの作成とバージョン管理の実施
Kenneth Reitz氏の2013年の記事 では、次のようなレポジト構造 を推奨しています。 code: リポジトリ構造
README.rst
LICENSE
setup.py
requirements.txt
sample/__init__.py
sample/core.py
sample/helpers.py
docs/conf.py
docs/index.rst
tests/test_basic.py
tests/test_advanced.py
GitHubを利用したことがある人は、多くのプロジェクトで、その構造がよく似ていることに気づくはずです。
新しいpythonプロジェクトを開始するときは、コードリポジトリを作成してバージョン管理を行うようにしましょう。
GitHubはそのための方法のひとつです。
License
これは、ルートディレクトリにあり、プロジェクトのライセンスを追加する場所です。
ほとんどの国では、オープンソースであってもライセンスがないコードを合法的に使用することはできません。このため、GitHubでコードを公開する時も、 ライセンスを提示してない場合は No License となり他の人が使えないものになります。 主なライセンスには次のものがあります。
GNU AGPLv3
GNU GPLv3
Mozilla Public License 2.0
GNU LGPLv3
Apache License 2.0
MIT License
BSD License
The Unlicense
README
これはルートディレクトリにあり、あなたのプロジェクトとそれが何をするかを説明する場所です。
これは、Markdown、reStructuredText、またはプレーンテキストで書くことができます。
拡張子でフォーマットを表現することもできます。
README/README.txt:プレーンテキスト
README.rst: reStructuredText
README.md: Markdown
sample/
ここには、サブディレクトリ内やルート内にある実際のコードが格納されます。
requirements.txt
これは必須ではありませんが、これを使う場合は、ルートディレクトリに置きます。
ここでは、プロジェクトのモジュールや依存関係、つまり、パッケージが必要とするものについて記述します。
setup.py
ルートにあるこのスクリプトは、プロジェクトに必要なモジュールをdistutilsがビルドして配布するためのものです。
docs/
読みやすいドキュメントは必須です。これはdocsディレクトリに置かれています。
GitHub では Jupyter Notebook をサポートしているため、最近では追加ドキュメントとして使用されることも多くなりました。
tests/
ほとんどのプロジェクトにはテストがあり、これらはtestsディレクトリに置いておきます。
プロジェクトリポジトリのテンプレートを利用するか、参考にして独自のテンプレートを作成しておくと、以後のプロジェクト開発の初期作業化のための簡単になります。
Pythonプロジェクトのための、依存度が低く、非常にシンプルに始められるプロジェクトテンプレート
データベース、API、管理インターフェイスなどを含むフル機能のFlaskプロジェクト
openapiプロジェクトを始めるためのベースとなるテンプレート。SQLModel、Typer、FastAPI、JWTトークン認証、インタラクティブシェル、管理コマンドを含む。
8. 読みやすいドキュメントの作成
プロジェクトのリポジトリ構造が決まったら、次は読みやすいドキュメントです。負担に感じるかもしれませんが、クリーンなコードを作るためには必要なことです。
このためには、Markdown、reStructuredText、Sphinx、docstrings などが使えます。
MarkdownとreStructuredTextは、プレーンテキスト形式の構文を持つマークアップ言語で、テキストをマークアップしてHTMLやPDFなどの形式に変換することが簡単にできます。
reStructuredTextでは、インラインドキュメントを作成することができます。
Sphinxは、インテリジェントで美しいドキュメントを簡単に作成するためのツールです。既存のreStructuredTextからPythonのドキュメントを生成したり、HTMLなどのフォーマットでドキュメントをエクスポートすることができます。拡張機能を追加することで、Markdown のフォーマットも扱えます。
Docstringsは、各モジュール、クラス、メソッドの先頭にあるドキュメント文字列です。docstrings にも引数や戻り値を説明するときの、スタイルがいくつかあります。
9. 適切なデータ構造を使用する
様々なデータ構造の利点と限界を知り、アルゴリズムをPythonで実装するときに適切なデータ構造を選択するようにしましょう。
10. オブジェクト指向のコードを書く
Pythonはオブジェクト指向の言語であり、Pythonの中のすべてのものはオブジェクトです。Pythonのコードを書くときは、オブジェクト指向のパラダイムを使うべきです。
これには、データの隠蔽性とモジュール性という利点があります。再利用性、モジュール性、ポリモーフィズム、データのカプセル化、継承などが可能です。
11. 壊れたコードはすぐに修正する
"壊れたコードの理論(日本語訳)"で指摘されているように、壊れたコードはすぐに修正しましょう。ここでの「壊れたコード」とは単純に不具合があるコードだけでなく、「クリーンでないコード」も含んでいます。動作はするけれど超絶技巧的なハックで成り立っているようなコードをそのまま放っておくと、他の作業をしている間に、後でもっとひどい問題が起こる可能性があるからです。 TDDを実践すると、たくさんのメリットがあります。そのうちいくつかは次のものです。
TDDでは、新しい機能を追加する前にテストを作成します。つまり、TDDアプローチでは、コード全体がテストでカバーされているので、最終的なアプリケーションのバグが少なくなります。これがTDDアプローチの大きな利点です。
TDDでは、新しい機能を追加する前に、特定のターゲットを持つようにします。つまり、新しい機能を追加する前に、その結果を明確にする必要があります。
アプリケーションでは、あるメソッドが他のメソッドに依存しています。メソッドの前にテストを書くということは、メソッド間のインターフェイスについて明確な考えを持つべきだということです。これにより、自分のメソッドをアプリケーション全体に効率的に統合することができ、アプリケーションのモジュール化にも役立ちます。
不合格のユニットテストを合格させるためでない限り、プロダクションコードを書くことは許されない。
失敗するのに十分な量以上のユニットテストを書くことは許されませんし、コンパイルの失敗は失敗です。
失敗したユニットテストを通過させるのに十分な量以上のプロダクションコードを書くことは許されません。
この3つのルールに従うことで、プログラム開発は常にテストと共に進んでいきます。しかし、それを実行するのは簡単ではありません。
テストは読みやすくなければならない
テストコードの各行で何をしているのかが明確で簡単にわかるようになっていなければなりません。これらのテストを読みやすくするためには、新しいメソッドを作成し、新しいクラスやインターフェイスなどにつなげていく必要があります。テストやこれらすべての初期化を行うためのライブラリやAPIのようなものが実装されるまで、時間をかけてリファクタリングを行う必要があります。
テストの方針
各テストでは特定のものをチェックしますが、そのものがうまく動作するためにはn個のチェックが必要な場合があります。そうであっても、最も重要なことは、一度ののテストでチェックする回数を常に最小限に抑えることです。
F.I.R.S.T.原則に従う
F.I.R.S.T.原則とは、次の項目の頭文字をとったものです。
First (速い): テストは素早く実行されなければなりません。もしテストに時間がかかると、私たちはテストを実行するのが億劫になってしまいます。さらに悪いことに、テストが失敗するようになり、テストを重要視しなくってしまいます。
Independent(独立性); テストの間には依存関係があってはいけません。また、どのような順序でも実行でき、複数のテストをまとめて実行したり、一つだけを実行したりできなければなりません。
Rpeatable(繰り返し可能であること): どのような環境でも実行可能であること。ローカルでは動作していたテストが、他の開発者のコンピュータや開発環境では失敗するということは許されません。
Self-Validating(自己検証可能であること): テストには2つの値しかありません。失敗したか、成功したかのどちらかです。そのため、テストがうまくいったかどうかは、ログを調べたり、値を書いたかどうかデータベースを調べたりすることなく、非常に簡単に知ることができます。
Timely(タイムリー): テストは、テストの対象のコードが作成される直前に作成されなければなりません。もしコードを先に書いてしまうと、テストを書くことがもっと複雑になってしまいます。
こうしたことを積み重ねていくことで、必然的にコードがクリーンになっていきます。
12. タスクランナーを使用する
プロジェクトで行うべき作業はできるだけ、invoke や doit、pypyr などのタスクランナーで実行するようにします。煩雑な作業であっても作業が簡単になるだけでなく、人為的なミスが混入することを防ぐことができます。
参考