転職黙示録 (4) Pythonのツールチェインを整理する ~pipからPoetryへ~

# モチベーション

 パッケージ管理ツールというとJavaScriptならnpm,[^1] Rustならcage, Rubyならgems, ElixirならHexというのがあります. ではPythonにはとpipというのがあります. それぞれ"管理"という言葉にこめられた意味が違っているのですが, pipの場合は公式にはパッケージ・インストーラとありプロジェクトの作成などは含んでいないようです.

> pip is the package installer for Python[^2]

 そのほかにも様々なツールがあってブログやら記事やらの日付を見て古いやつだと後で気付いたりして混乱している次第です.[^3] そもそもの原因は個々のツールの目的を曖昧にしたまま, とりあえず動かせているから理解した気になっていることが原因なのです. 特にpipenvというのが便利そうなんですが他のツールとの使い分け・共存などで混乱したことが大きなきっかけです.

# 簡単な用語の整理

　Pythonのモジュール・システムを解説するわけではないのでかなり適当ですが何を対象にしているのかが分かるようにモジュールとパッケージについて説明します.

## モジュール

　Pythonファイルを定義するとそれはそのままモジュールという単位になる.

```python:greet.py
def sayHello():
   print("Hello, World")
```

　つまりこのファイルを他のファイルから読み込めるということだ.

```python:sample.py
import greet
```
 一方このファイルをスクリプトとして実行することもできる.

```bash
python greet.py
```

 この場合ブートストラップとしてコードを呼び出す場所が必要になる.

```python:sample.py
if __name__ == "__main__":
    sample();
```

　スクリプトとして実行した場合は__main__という文字列が設定されてとしてとトップレベルのモジュールとして実行される. 一方モジュールとして読み込まれた場合はモジュール名が格納されている.[^4]

## モジュールとパッケージの違い

　公式ドキュメントにはパッケージとモジュールは以下のように定義されている. [^5]

> A Python module which can contain submodules or recursively, subpackages. Technically, a package is a Python module with an \_\_path\_\_ attribute.

> An object that serves as an organizational unit of Python code. Modules have a namespace containing arbitrary Python objects. Modules are loaded into Python by the process of importing.

　パッケージもモジュールの一つですが, \_\_path\_\_という属性があるようです. 大雑把にいうとPythonファイルを定義すればモジュールで, それが入ったフォルダがパッケージです.

# パッケージ管理に関する基本的な問題点

## Q. パッケージの取得

　パッケージを取得できないとまずは意味がありません. Batteries-includedと言えど, Pythonをメインストリームに押し上げた科学技術計算用のパッケージとかは欠かせないはずです.

## A. pip

 すでに説明しましたがパッケージのインストール・ツールです. pip installでパッケージを取得します. パッケージはsite-packagesというフォルダにインストールされます.

## Q. 依存性の管理

　pipでパッケージをインストールできました. しかし他のパッケージの機能をインポートして利用するパッケージというのもあります. この他所から機能を提供してくれるパッケージを依存性(依存パッケージ)と言います.依存性は単にパッケージの名前を知っていれば良いというわけではなくて, バージョンに関する情報も必要になります. つまりAという依存パッケージが存在してもバージョン1.0と1.1に互換性が無いという状況が想定されまう. したがってパッケージの名前とバージョンという二つの情報を管理する必要があります. もしこの情報が何らかの形で定義されていないとコードの中から依存性となるパッケージを抽出するという作業が出てきます.

## A. setuptools

 パッケージの配布とビルドのためのパッケージです. pipを通じてパッケージを配布したいときにこのツールを使ってsetup.pyやsetup.cfgなどを書く必要があります. setuptooslのオプションにinstall_requiresがあってここに必要な依存性を記述することになります. 問題は手動なことです.
　
## Q. 開発環境の再現

　もう1つの注意点は開発環境が変わるような場合です. マシンが古くなってきたので新しいのに変えるという時に問題が生じます. 依存性が解決されても環境が再現されないと問題になる場合もあるのです. これも間接的な依存性と言えます.

## A. 環境のダンプ

　開発するときはpipでパッケージをインストールしています. pipはインストール済みのパッケージ一覧を表示してくれます. これを保存しておけばとりあえずは同じ環境が再構築できます. 一般にrequirements.textと呼ばれるファイル名が使われます.

```bash
# Save dependencies
pip freeze > requirements.txt

# Retrieve dependencies
pip install -r requirements.txt
```
　install_requiresと形式は同じなのでrequirements.txtをそのまま指定することもできます.

# ここまでのまとめ

　パッケージの取得に関してはおおよそpipでオッケーです.

# requirements.txtの問題点

　requirements.txtを用いた環境のダンプには二つの問題がありそうです.

* 依存パッケージのバージョンが微妙に違う場合
* 依存性のレベルをコントロールできない

## Q. 依存パッケージのバージョンが微妙に違う場合

　全て最新のバージョンにしておけば良いと言えそうですが, 依存パッケージが何百とあってアップデートのたびにその全ての変更に対応しないといけないのは煩雑に過ぎるわけです. バージョンを固定したいわけですが, 新しいプロジェクトでは新しいバージョンを使い始めるなんてこともありそうです. このように管理するプロジェクトが増えていくとrequirements.txtのような依存性の列挙では解決できないケースが出てきます.

## A. venv/virtualenv

　仮想環境は仮想Pythonを作ってその中で開発を行います. Python自体はインストールされたインタプリタを使いますが, site-packagesなどが環境ごとに違ってきます. よって依存パッケージのバージョンが違おうが種類が違おうが関係なく開発を行えます.

## Q. 依存性のレベルをコントロールできない

　依存性と一口に言っても, 直接依存しているのか依存パッケージがさらに依存しているのかということがrequirements.txtでは区別がつきません. 依存性の解決なのか開発時の環境の再現なのかが分かりにくいのです. これはそのそもpipでインストールしたパッケージをダンプしているだけだからです. 他にもdev-dependenciesのよな開発時にのみ必要なツールなどが分けられません.

## A. Pipenv

　Pipenvはpipとvirtualenvを組み合わせたツールです. このツールを使うとpipで導入する依存パッケージをPipfileというtoml形式のファイルで管理できます. 基本的にプロジェクトが直接依存するパッケージだけが記述されます. またdev-dependenciesの管理もできます.

# Pipenvの問題点

　Pipenvは便利なツールに見えますが内部はpipです. つまりsetuptoolsを使った依存関係の定義が背後にはあります. このため他の言語のツールとは違いアプリケーション用途では使えても基本的なパッケージ管理はpipの形式で記述する必要があるわけです. これはpipenvの問題点というよりはpip自体の問題点ともなのですが, 開発対象ごとにツールが二重になってしまいます.

 またpipenvは仮想環境を使いますが, 今度は複数の環境の切り替えが問題になります. プロジェクト間を移動するたびに環境を切り替える作業を忘れずにできるでしょうか?

 更に仮想環境を作る際のpythonインタプリタのインストールは誰もしてくれません. 仮想環境の元になる本体は別途調達する必要があります.

## Q. 環境の切り替え & 複数のPythonインタプリタの管理

　プロジェクトごとに環境が設定できて, 指定したPythonがなければインストールしてくれれば助かります.

## A. direnv & pyenv

　direnvは環境の切り替えをしてくれます. pyenvは両方してくれます. 言い換えるとプロジェクトごとに環境が設定できるわけです.

# 結局pipの問題点

　pipは便利ツールでした. それをうまく利用したpipenvでアプリ開発に関する準備が簡素化されたと言えます. ただ他の言語と比べると少し物足りない感じはします.

## Q. パッケージ管理はインストールだけじゃない

　pipはパッケージ・インストーラーでした. 実際の多くのことはsetuptoolsが行ってくれたわけです. ただ, あまり便利なツールとは言えません.[^6]

　ぶっちゃけcagoとかnpmみたいのが欲しいです.[^7]

## A. Poetry & Pyenv?

# Rustとの比較

|用途|Rust|Python|
|---|:--:|:----:|
|パッケージ管理|Cargo|Poetry|
|ツールチェインの管理|rustup|pyenv?|

　rustupの部分的な機能はpyenvで代替できそうな気もします.

# まとめ(新しいモチベーション)

 Poetryとpyenvをやってみる.

[^1]: 正確にはNode.jsですが. [npm is the package manager for Node.js.](https://www.npmjs.com/about)
[^2]: [The Python Package Installer](https://pip.pypa.io/en/stable/)
[^3]: 公式にもLegacyの記事があったりして, 後で気付くわけです.
[^4]: greetという文字列になる.
[^5]: [Glossary](https://docs.python.org/3/glossary.html#glossary)
[^6]: 勉強不足とも言えそうですが・・・
[^7]: 個人感想が漏れてしまいました:bow_tone1: