はじめに
こんにちは。TIG/DXチームの栗田です。夏休み自由研究連載14日目の記事です。
最近業務ではGoを書くことが増えてきましたが、私は以前Pythonをよく書いていました。
今回はPythonの開発をスムーズに行っていく方法について記載します。
この文章の目的
機械学習やDeep Learnigに後押しされ、今やPythonは非常に人気の言語です。初学者でも扱いやすく、学生の方も数値計算や競技プログラミングあるいは研究で利用しています。
一方で触りやすいがために「Pythonは書ける」のと「Pythonで何かを開発できる」には、意外なギャップが生まれることがあります。
ここではそういった「Pythonは書けるし実際書いているけど独学だったしアプリは作ったことがない」ような人をターゲットとして、初学者向けに紹介されるようなツールをどのように組み合わせていくかを記載します。
目次
venvで開発環境を整えるpytestを書くtoxにpytestを取り込む- さらに
blackやflake8やmypyを組み込んでいく
前提
mypackageというパッケージを作ることとします
開発環境について:venv
仮想環境
Pythonは必要なパッケージを適宜 pip で導入可能なのが便利ですが、そのままインストールするとシステムにそのまま入っていきます。
個人で作業するのであればそれでも問題ないかもしれませんが、複数のバージョン固定が入るようなプロダクトを開発したり、あるいはOSSなどのようにチームで開発したりする場合は、開発対象ごとにパッケージを管理することが望ましいです。
そのために使われるのが仮想環境であり、Python2.x系のときは virtualenv という機能がありました。この virtualenv がPython3.3から標準機能として取り込まれたのが、venv です。
virtualenv is a tool to create isolated Python environments. Since Python 3.3, a subset of it has been integrated into the standard library under the venv module.
Virtualenv - virtualenv documentation
Python標準機能のツールになったので、 venv はPython自体のversion管理はできません
また、Python3.3, 3.4系では仮想環境の作成に pyvenv というツールの仕様が推奨されていましたが、Python3.6系からは非推奨です。
Python3.5系から venv が推奨とされています。
venvの使い方
以下の形で仮想環境を用意します。
$ cd ~/work/dir/path/mypackage |
この仮想環境から抜けるには、以下のようにします。
(.venv) $ deactivate |
仮想環境へのパッケージインストール
先に用意した仮想環境は限りなくバニラな状態で、activate前にインストールしたパッケージは何も入っていません。
そのため、開発に必要なパッケージを改めてインストールする必要があります。例えばパッケージを作るためにsetup.pyを用意すれば、$ python setup.py install 実行時に作ったパッケージ自身とそれに依存するパッケージをインストールできます。
しかし、このコマンドでは開発中のパッケージを他のPythonパッケージのパスに移動するような操作になり、開発中のパッケージを含めて逐一 $ python setup.py install するのは面倒です。
できれば、作業中のディレクトリを直接参照したいです。そのため取られる手法の1つとして、以下のようなやり方があります。
- 依存パッケージは別ファイルに切り出して管理(
requirements.txtなどがそれに該当) - 開発対象のパス自体を参照して仮想環境で
importできるようにする
requirements.txt には依存関係にあるパッケージのバージョンまで記載できます。
このファイルと pip の -e オプションを利用して、上記の2つを満たします。
(.venv) $ pip install -e . -r requirements.txt |
この手法を取ることで、複数人における開発で同じ環境を共有できます。
Pythonによるテスト:pytest
テストツール比較
Python用のテストツールは複数あります。
| ツール名 | 説明 |
|---|---|
| unittest | Pythonの標準ライブラリに含まれるテストモジュール。 |
| doctest | ドキュメンテーション中のインタラクティブなPythonセッションに見えるテキストを検索し、それが正しいかどうかを確認するモジュール。 |
| nose | unittestを拡張して実行できるツール。カバレッジなども取得できる。 |
| pytest | テストフレームワーク。unittestの形で記述されたコードも実行でき、拡張性が高かったりテスト結果がわかりやすかったりする。たまに見受けられる py.test は古いコマンド。 |
| tox | テスト環境の管理を自動化し、複数のインタプリタに対してテストするためのツール。コードチェックツールやフォーマットツールを組み合わせることができる。 |
unittest や nose で書いたテストがそのまま実行できることもあり、 pytest が利用されるケースが多いと思います。
例えば最近のツールでの採用例としては、AWS Glueのテストも pytest で行えます。
今回は、これに flake8 や black を組み合わせた形で、 tox からコールします。有名なflaskやsphinxでも、このような構成となっています。今回はこの構成を実際に構築していきます。
pytest環境の構築
pytest の実行には、強く推奨されるディレクトリ構造があり、このディレクトリ構造であれば pip パッケージの開発をすすめることができます。
今回はその形式に則ります。
. |
ルートディレクトリの直下に src
テストコードについては、test_のプレフィックスを付けて、testsディレクトリ以下に設置します。パスの通りにファイルを作ったら、次のような中身にします。
import numpy as np |
from mypackage.mypackage import mypackage_func |
これで pytest コマンドを実行すると、結果を確認できます。
(.venv) $ pytest |
pytest の場合 assert が基本的な使い方になりますが、もちろん他の方法もできます。
例えば、例外検証する場合は、次のようになります。
from mypackage.mypackage import mypackage_func |
(.venv) $ pytest -rsfp |
試しに、 pytest 実行時にオプションを付けてみました。失敗した内容を良く確認したり、カバレッジを確認したりできます。
他にも pytest には様々なプラグインが存在しています。
$ pip search pytest を実行すれば、たくさんのプラグインがあることがわかります。
pytest の細かい使い方については公式のドキュメントを読んでも良いですし、例えばエムスリーのテックブログに記載されています。
テスト環境自体の整理:tox
自分で使用するために開発しているのだと pytest で十分に思うかもしれませんが、Pythonの場合サポート中の言語が複数バージョン存在します。
パッケージとして提供するのであれば、複数のバージョンのPythonでテストしておきたいですが、そんなときに使えるのがtoxです。
tox は設定ファイルに記載した内容をもとに別々の virtualenv 環境を構築し、各環境の中でテストを実行して表示するツールです。tox の設定ファイルとしては tox.ini というファイルを記載します。
簡単な例としては、次のように書きます。
[tox] |
(.venv) $ tox |
envlist に他のPythonのバージョン、例えば py38 を追加すると、python3.8でのテストも一緒に実行されます。
コードフォーマッター:black
コードレビューの際に「このコードはこうあるべき」というのが気になることがありますが、そんなときに役立つのがコードフォーマッターです。いわずもがな、フォーマットされたコードは読みやすく、またおかしなことをしている部分を見つけやすくなります。
Pythonにもいくつかのコードフォーマッターがあり、古いものだと autopep8、go言語の gofmt を参考にgoogleが作った yapf などがありますが、ここではblackというツールを紹介します。PythonにはPEP8というPythonのコーディング規約があり、これに従うことが基本となります。
black も基本このPEP8に準拠する形ですが、その上で black で定義された形にファイルフォーマットされる、いわばより制約の強いPEP8のような振る舞いをします。black のさらなる特徴としては、上述の2つのツールに比べて自由度が少ないことです。
ユーザーの設定の余地が少ないことから開発者の好みによったフォーマットが起きづらく、見た目が揃いやすいです。PyConJPなど各種イベントの入門者用の発表でも、利用を推奨されているツールです。
blackの実行
pip で簡単にインストールできますので、試してみます。black コマンドだけだと対象ファイルにフォーマットをかけて書き直しますが、--check オプションを付けることで、フォーマットがかかる対象を確認できます。
(.venv) $ pip install black |
特段エラーが無いことがわかりました。
余談ですが、 black の数少ない設定として、除外ファイルの設定ができます。デフォルトでも、よくあるファイルについてはexcludeされています。
(.venv) $ black --help |
ヘルプにかかれているように、 .tox や .venv 以下のファイルについては、最初から black の対象外にされています。
試しにその中のファイルに対して black をかけて見えると、reformatがかかることがわかります。
(.venv) $ black .venv/bin/rst2odt.py --check |
.tox や .venv ともに .gitignore に記載されるような対象ですので、 black で管理する必要もないでしょう。
ちなみに開発環境によって他の除外ファイルを設定したい場合、オプションとして渡すこともできますが、 pyproject.toml ファイルに記載することで、まとめて外すこともできます。
toxへの取り込み
black によってコードチェックができるようになりましたので、これをtoxにも組み込みます。
といっても、 tox.ini を次のようにするだけです。これで、 tox コマンド実行時に black をかけることができます。
[tox] |
depsについては -rrequirements.txt で指定してもいいですが、他のパッケージを毎回インストールするのは時間がかかるので省いて、必要最小限にしています。
文法チェックツール:flake8
flake8 は静的文法チェックツールで、これもPEP8に従う形でコードをチェックし、バグになりやすいソースコードを探します。
flake8の実行
これも pip でインストールできます。
(.venv) $ pip install flake8 |
なにか見つかりましたが、これは 関数の前には2行のblank lineが必要なのに1行しかないよ というエラーです。
import numpy as np |
確かに、 def main(): の前に1行しかblank lineがありません。
なので、1行加えてみます。
import numpy as np |
(.venv) $ flake8 src/ |
先程のE302の表示が消えました。
flake8のtoxへの組み込み
flake8 も tox に組み込んで、自動化しましょう。
これも tox.ini の中に記載します。
[tox] |
先に black を導入していますが、 flake8 と black は併用可能です。ただし、一部非互換の部分があるので、2箇所修正を入れています。
1つは max-line-length ですが、こちらは black 側でデフォルトで88文字なので、それに合わせます。もしもblack側で変更を入れている場合は、それに合わせる形で修正します。もう1つが、E203, W503, W504です。こちらも black 側で許容されてしまうので、ignoreにします。
最後に、開発用のファイルまでチェック対象にすると大変なので、それらのファイルをexcludeします。デフォルトで .git などが含まれていますが、 .tox や .venv が含まれていないので、ここで明示的に設定します。なお、 build や dist は setup.py を使うときに将来的に必要になることを見越して入れています。
静的解析ツール:mypy
Python3.5から型ヒントがサポートされるようになりましたが、これはPythonに変数の型チェックをもたらしました。
コードを見ただけでその変数にどのようなクラスや型の値が入っているかわかるようになりましたが、あくまでヒントであって実行時には役には立ちません。この型を静的にチェックするツールが、mypyです。
mypyの実行
pip でインストールして試してみます。
(.venv) $ pip install mypy |
mypyの使い方は、引数の右側に : 型名 と、関数やメソッドの右側に -> 型名 とつけることによって行います。
試しに、 これまで作っていた src/mypackage/mypackage.py にmypyをかけてみます。
import numpy as np |
(.venv) $ mypy src/mypackage/mypackage.py |
numpy がなにか引っかかりました。
これは、 mypy がサードパーティモジュールについてはStubファイルという型ヒント用のファイルを読み込んでチェックをかけるのですが、現在の環境にはそれがないため怒られています。
Stubファイルを作ることもできますが、よくやる手法としてはサードパーティモジュールの型ヒントを無視することをします。
これには、 mypy の設定ファイルである mypy.ini というファイルを編集します。
[mypy] |
(.venv) $ mypy src/mypackage/mypackage.py |
これで numpy の型ヒントは無視されました。
続いて、 src/mypackage/mypackage.py に対して型ヒントを付与します。
import numpy as np |
このファイルに mypy をかけても、特にエラーはでません。
続いて、わざとエラーを出させてみます。
...前略... |
$ mypy src/mypackage/mypackage.py |
確かにエラーが出ました。
さらに、 mypackage_func() の方もエラーを出させてみます。
...前略... |
$ mypy src/mypackage/mypackage.py |
np.pi は float なので明らかに間違っていますが、 mypy でエラーが出ていません。
今回 numpy はignoreにする対象としていますので、今回のチェック外となっています。
こちらですが、明示的に型指定すると、 mypy でエラーを検知できます。
...前略... |
(.venv) $ mypy src/mypackage/mypackage.py |
toxへのmypyの組み込み
最後に、 mypy も tox.ini に追加します。
[tox] |
無事すべてのテストをパスしました。
(.venv) $ tox |
補足:VSCodeにおけるblackとflake8とmypyを取り込む
今回紹介したツールは都度かければきれいなコードが書けますが、逐次手動で実行するのは面倒です。
テスト実行時に気づけるといっても、できればコーディング中に自動整形してほしいし、適宜指摘してほしいので、そのための設定を行います。
Python用Extensionの導入
あまり言うまでもないかもしれませんが、 Python ms-python.python を導入し、有効化します。
setting.jsonの記述
File->Preference->Settingsと開きます。
Workspaceを選択し、 settings.json を開きます。
次のような感じで記載します。
{ |
あとは、VSCodeをReload(再起動)してください。
これで、開発中に black, flake8, mypy が動作します。
最後に
ここまで作成したコードについては、こちらで公開していますので、必要に応じてご覧ください。
自作したパッケージをPyPIに登録することをしようとすると、 setup.py で固めて twine でアップロードして、などの工程が発生しますが、それらのやり方はインターネット上でいろいろな方が行われているため、ここでは割愛します。
また、これでテスト環境が整いましたので、例えばCircleCIなどに乗せて自動テストできるようにすることもできます。