はじめに
こんにちは。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などに乗せて自動テストできるようにすることもできます。