Python パッケージングの標準を知ろう
田村 真一
こんにちは、CET チームの田村真一です。リクルートライフスタイル Advent Calendar 2019 最終日の記事をお届けします。
本記事では Python のパッケージングに焦点を当て、2019年末時点で
- ライブラリのパッケージングについて
- 標準がどう定められているのか
を紹介します。
逆に言うと
- アプリケーションの依存管理の話
- ベストプラクティスや便利なツールの紹介
- Pipenv と Poetry どちらを使うべきか論争
は一切しませんのでご了承ください。
そもそも「標準」とは
さて、本題に入る前に Python における「標準」とは何なのか確認しておきましょう。
まず言語仕様に関していえば、C や Ruby のような国際規格に則った「仕様」は存在しません。 かわりに language reference と CPython の実装 がデファクトスタンダードとなっています。
いっぽうで、Python における「標準」を定めるためのものとして PEP (Python Enhancement Proposal) があります。 PEP で採択されたら標準になるというわけですね。 ちょうどインターネットにおける RFC (Request for Comment) のようなものです。
有名所でいえば
- PEP 8 ―― Style Guide for Python Code:
pep8
コマンドでおなじみ - PEP 373 ―― Python 2.7 Release Schedule: Python 2 系 EOL まであと1週間!
- PEP 484 ―― Type Hints: Python にも型の恩恵を!
- PEP 572 ―― Assignment Expressions: Guido の BDFL 引退の引き金
あたりでしょうか。さまざまなことが PEP で決まっていることが分かりますね。
なおそれぞれのステータスは、実はさまざまです。 PEP 8 や PEP 373 は「永遠に完成しない」active ステータス。 PEP 572 は「受領されたが標準実装には入っていない」accepted ステータス(final の1つ手前)。 そして PEP 484 は「基本的には accepted だが、final になる前にユーザーからのフィードバックを必要とする」provisional (=“provisionally accepted”) ステータス。 各ステータスの意味と遷移図は PEP 1 を参照してください。
パッケージングの歴史
これに対しパッケージングに関するプラクティスは、従来はあまり PEP でまとめられてこなかったのも事実です。 そこで、まずは Python パッケージングの歴史を追ってみることにしましょう。
最初に登場したのは distutils
です。
これは Python に標準ライブラリとして含まれているパッケージングツールです。 登場した当時は無論これしかなく、インストールツールは何もないという状況でした。 現在ではその存在が意識されることはありませんが、今でも各種ツールのコアで使われ続けています。
次に登場したのがパッケージングのための setuptools
とそれに付属するインストールツール easy_install
です。
setuptools
は distutils
を拡張したもので人気を博しました。
これらは標準ライブラリではなく、PyPA というワーキンググループによって開発されています。
そういえば、setuptools
が開発停滞していたころにフォークされた distribute
というツールもありましたね。
これは現在では setuptools
にマージされていますので、もう存在しません。
最後発で登場したのが、皆さんご存知 pip
です。
これは easy_install
をさらに拡張したインストールツールで、やはり PyPA によって開発されています。
現在ではデファクトといえるほど使われており、PyPA 自身も「推奨ツール」として紹介しています。
ここまでの登場人物を整理すると、下図のようになります。
パッケージングの標準
ここからいよいよ本題です。
Python には sdist と bdist という2種類のパッケージ配布形式があります。
- bdist …… Built Distribution: ビルド済み配布物
- 「置く」だけでインストールできる状態になっている配布形式(ユーザーがソースビルドしなくて良い)
- sdist …… Source Distribution: ソースコード配布物
- ソースコードをそのまま zip/tar.gz で固めて配布し、インストール時にビルドしてもらう形式
それぞれについて見ていきましょう。
bdist 形式に関する標準: wheel
distutils
自体は RPM や Windows インストーラーなどのプラットフォームごとの bdist 形式を含んでいました。これに対し setuptools
は2004年、独自の egg というフォーマットを策定しました。
setuptools
で利用されていたこの形式ですが、PEP もなく、また C API を利用しないライブラリでも Python のバージョンごとにビルドし直す必要があるという問題がありました。
それを解決するために登場したのが wheel です(やはりこれも PyPA 管轄のプロジェクトです)。
この形式は、次のような PEP で egg の問題点を解決しました。
- PEP 427 ―― The Wheel Binary Package Format 1.0 (status: final)
- wheel のフォーマットや互換性を明示するファイル名の仕様
- PEP 425 ―― Compatibility Tags for Built Distributions (status: final)
- wheel ファイル名で使われる「互換性タグ」の仕様(Python・ABI・プラットフォームの識別)
- PEP 376 ―― Database of Installed Python Distributions (status: final)
- インストール済みパッケージの管理に dist-info ディレクトリを使うこと
- PEP 513 ―― A Platform Tag for Portable Linux Built Distributions (status: active)
- 「一般的な Linux 環境」としての
manylinux1
の基準の定義(アーキテクチャやインストールされていることが期待されている shared library など)
- 「一般的な Linux 環境」としての
こうして、ライブラリを配布する側は wheel 形式にパッケージを固めて配布するだけ、利用する側はそれをダウンロードして展開するだけで良いという世界観が確立されました。
なお、ファイル形式としての wheel と、それを作るためのライブラリである wheel
を混同しないよう注意しましょう。
前者は配布者と利用者が共通して使う「形式(≒プロトコル)」です。
いっぽう後者は配布者がパッケージングのために使うライブラリで、利用者は基本的には使いません。……「基本的に」といったのはもちろん例外があるからなのですが、それについては次節で触れます。
sdist 形式に関する標準: pyproject.toml
sdist 形式のパッケージからライブラリをインストールするには、利用者側が setup.py
を実行して bdist 形式のパッケージを作りそれをインストールする、というのがデファクトスタンダードである pip
の挙動でした。ここで使われる bdist 形式は、wheel
ライブラリがあれば wheel、なければ egg です。
しかし setup.py
も1つの Python コードです。
それが何かしらの 3rd party ライブラリに依存していた場合、どうしたらいいでしょうか。
実際、ほとんどの setup.py
は setuptools
という 3rd party ライブラリに依存しています。
そこでまず決まったのが PEP 453 です。
- PEP 453 ―― Explicit bootstrapping of pip in Python installations (status: final)
- 「
pip
とsetuptools
をインストールする」ensurepip
というライブラリを標準ライブラリに含めること
- 「
pip
そのものは標準ではないが pip
のインストーラーは標準、というなかなか絶妙なさじ加減ですね。
一見良さそうに見えるこのアプローチですが、依然として問題がありました。
それは、setuptools
以外の依存についてはうまく扱えないということです。
このままでは、パッケージングに関する改善に PyPA 以外のコミュニティが貢献できないことにもなります。
こうして登場したのが pyproject.toml
です。
- PEP 518 ―― Specifying Minimum Build System Requirements for Python Projects (status: provisional)
- インストール作業に必要な依存を
pyproject.toml
ファイルのbuild-system
テーブルで定義すること - 各ツールが独自情報保存のため
tool.${TOOL_NAME}
テーブルを自由に使えること
- インストール作業に必要な依存を
- PEP 517 ―― A build-system independent format for source trees (status: provisional)
- インストール作業とは何を実行することか(=どの「ビルドツール」を使うか)を上記
build-system
テーブルのbuild-backend
で指定すること
- インストール作業とは何を実行することか(=どの「ビルドツール」を使うか)を上記
いくつか例を見てみます。
例1. 従来方式
このように build-backend
を指定しなければ、従来同様 setup.py
が実行されます。
1
2
[build-system]
requires = ["setuptools", "wheel"]
なお PEP 518 では、pyproject.toml
ファイルがない場合のデフォルト値を上記とすることも定められています。
例2. Poetry
Poetry を使うとこのような例になります。
1
2
3
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
翻訳すると、このパッケージングをインストールするには
poetry
のバージョン 0.12 以降をインストールしてpoetry.masonry.api
を実行せよ
ということになります。
なお poetry
がインストールされるのはビルド用の一時的な環境なので、ユーザー環境は汚染されません。
どのツールを使うかという情報以上のことは、もうあとは標準では関知せず、各ツール任せになります。 たとえばパッケージの依存をどこで定義するか、lock ファイルを利用するかなどは、各ツールが自由に決めます。
Poetry の例では、依存の定義には setup.py
や setup.cfg
を使わず、pyproject.toml
内の tool.poetry
テーブルを利用しています(前述の通り、このテーブルを Poetry が自由に使えることは PEP 518 で定められています)。
1
2
3
4
5
6
[tool.poetry.dependencies]
python = "^3.7"
python-json-logger = "^0.1.11"
[tool.poetry.dev-dependencies]
pytest = "^3.0"
Lock ファイルは poetry.lock
を利用しています(当初は pyproject.lock
というファイルを利用していましたが、標準で定められていないのに標準かのようなファイル名を使うべきではないということでリネームされました)。
補足: TOML に対する依存について
Python の標準ライブラリには、JSON/ini などとは違い TOML 用ライブラリが含まれていません。
では pyproject.toml
パースのための TOML への依存はどうやって管理されているのでしょうか。
これは PEP では定まっておらずインストールツール任せになっています。
実は pip
は pytoml
という開発終了したライブラリを使っており、Poetry と同じ tomlkit
へ移行することが提案されています。
ライブラリ配布者として何をすべきか
ライブラリ配布者としては、sdist 形式に加えて必ず wheel 形式でも配布するようにしましょう。
その方が利用者にとっても使いやすく、配布者側も利用者の pip
のバージョンを気にせず pyproject.toml
が使えるというメリットがあります。
その理由は pip install
が下記のような挙動をするからです。
- wheel 形式で配布されている
- →
pip
が wheel ファイルを展開し配置する
- →
- sdist 形式で配布されている(=ビルドが必要)
- 2-a.
pip
のバージョンが v19.0 以降、かつpyproject.toml
ファイルがある(またはpip
に--use-pep517
を指定)- → PEP517 に従う
- 2-b.
wheel
ライブラリが実行環境にある- →
setup.py bdist_wheel
を実行して1へ
- →
- 2-c. それ以外
- →
setup.py egg_info && setup.py install
- →
- 2-a.
おわりに
以上、Python のパッケージングに関する2019年末時点での標準を整理してみました。 長らく混乱も多いトピックですが、それは先人たちの努力の現れなんですね。 こういった話は明日からすぐ使える知識ではないので疎かにしがちですが、知っておくと Python が向かう未来が見えてくる気がします。
さて、2020年はもうすぐそこです。 まもなく EOL を迎える Python 2 へ感謝をこめて、この記事は終わりにしたいと思います。
参考文献
本記事を書くにあたっては、下記の記事を参考にしました。
日本語ブログ
- 2019年
- 2018年
- 2017年
- 2016年
- 2015年
公式リソース
- PEP
- PEP 427 ―― The Wheel Binary Package Format 1.0 (status: final)
- PEP 425 ―― Compatibility Tags for Built Distributions (status: final)
- PEP 376 ―― Database of Installed Python Distributions (status: final)
- PEP 513 ―― A Platform Tag for Portable Linux Built Distributions (status: active)
- PEP 453 ―― Explicit bootstrapping of pip in Python installations (status: final)
- PEP 518 ―― Specifying Minimum Build System Requirements for Python Projects (status: provisional)
- PEP 517 ―― A build-system independent format for source trees (status: provisional)
- PyPA 公式ドキュメント
- Python 公式ドキュメント
アイキャッチ画像は WordArt.com によって生成されています。