(この記事の情報は古いです。最近は pip が myproject.toml を理解できるようになりました。Python でパッケージを開発して配布する標準的な方法 2023 年編 に更新版を書きましたので参考にしてください。)
今更ながら Python でパッケージを開発したり配布する標準の方法を知らなかったので調べました。Poetry の方が簡単ですが、人の作ったプロジェクトで仕事するのに覚えておくと便利です。
仮想環境の作成
Python 3.7.7 等の最新の Python が入っていて python コマンドで実行出来るようになっている状態から始めます。これからパッケージを開発するために、他の Python プログラムと利用ライブラリが混ざらないように最初に仮想環境を作ります。仮想環境を作る標準の方法は venv モジュール https://docs.python.org/ja/3/tutorial/venv.html を使う事です。仮想環境の場所に決まりは無いですが、よく .venv
フォルダが使われます。
mkdir packaging_tutorial
cd packaging_tutorial
python -m venv .venv --prompt tutorial
source .venv/bin/activate
最後の source .venv/bin/activate
が環境に入るコマンドです。抜けるには単に deactivate
と入力します。上記 --prompt
オプションは無くても良いですが、あるとプロンプトにその文字が出るので、今自分が何を使っているかわかりやすいです。
開発に使うライブラリのインストール (無くても良いかも)
開発に使うライブラリは requirements.txt
というファイルに書きます。ここでは requests というパッケージを使います。
requests ~= 2.23.0
requirements.txt に書いたパッケージをインストールするには、以下のコマンドを使います。
pip install -r requirements.txt
こうすると、有効な仮想環境 .venv
に必要なライブラリがインストールされます。すでに色々 pip install
してしまって何を入れたか忘れていたら、
pip freeze > requirements.txt
のように現在の環境から requirements.txt を作れます。
パッケージを実装する
今回次のようなディレクトリ構成を使います。
packaging_tutorial/
corona/
__init__.py
__main__.py
tests/
requirements.txt
setup.py
LICENSE
README.md
corona/__init__.py
の例を示します。最近流行りの新型コロナ情報を取得する get()
関数を実装してみました。コマンドラインから簡単に実行出来るよう main()
も用意しておきます。
import requests
import sys
def get(country: str) -> str:
url = f"https://corona-stats.online/{country}?minimal=true"
response = requests.get(url, headers={'user-agent': 'curl'})
return response.text
def main() -> None:
country = sys.argv[1] if len(sys.argv) > 1 else ""
print(get(country))
pythonm -m
で main()
を呼ぶために corona/__main__.py
を書いておきます。
from . import main
main()
試しに実行。
% python -m corona jp
Rank Country Total Cases New Cases ▲ Total Deaths New Deaths ▲ Recovered Active Critical Cases / 1M pop
1 Japan (JP) 2,495 62 472 1,961 60 20
World 1,015,466 523 ▲ 53,190 24 ▲ 212,229 750,047 37,696 130.29
setup.py を書いてパッケージを完成させる。
setup.py というファイルを作って、パッケージの情報を書きます。細かい仕様は https://packaging.python.org/guides/distributing-packages-using-setuptools/ に書いてあります。ポイントとしては、
-
install_requires
にパッケージが依存するパッケージを書くと pip が自動インストールしてくれます。 -
entry_points
のconsole_scripts
にコマンド名=モジュール:関数
を書くと、このパッケージを使ったコマンドが生成されます。
import setuptools
with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(
name="corona-propella", # Replace with your own username
version="0.0.1",
install_requires=[
"requests",
],
entry_points={
'console_scripts': [
'corona=corona:main',
],
},
author="Propella",
author_email="propella@example.com",
description="A covoid-19 tracker",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/pypa/sampleproject",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.7',
)
これでパッケージの完成なので、この環境にインストールして実行してみます。
pip install -e .
rehash
corona
この pip install -e
コマンドは開発モードでパッケージをインストールするので、ソースコードを変更するとすぐ反映されて便利です。
配布物を作る
作ったパッケージを配布出来るようまとめます。標準は wheel というフォーマットです。
必要なコマンドをインストールして dist に whl ファイルを作ります。
pip install --upgrade pip setuptools wheel
python setup.py bdist_wheel
dist/corona_propella-0.0.1-py3-none-any.whl
のようなファイルが配布物です。これを pip でインストール出来ます。
配布物のテスト
別の仮想環境を作って配布物をテストしてみます。
deactivate
cd ..
mkdir packaging_test
cd packaging_test
python -m venv .venv
source .venv/bin/activate
pip install ../packaging_tutorial/dist/corona_propella-0.0.1-py3-none-any.whl
python -m corona jp
これで依存パッケージと共に先程作ったパッケージが仮想環境にインストールされます。スムーズ。。。
Egg の作成
Wheel の代わりに昔使われていたらしい Egg というフォーマットを作る事も出来ます。
python setup.py bdist_egg
Egg のインストールには pip ではなく easy_install を使います。
easy_install ../packaging_tutorial/dist/corona_propella-0.0.1-py3.7.egg
様々な後片付け
setup.py が作ったファイルを消す
python setup.py clean --all
仮想環境を消す
rm -r .venv
requirements.txt と setup.py の関係
ご紹介した方法では、必要なパッケージを requirements.txt と setup.py の install_requires の両方に書くという事になってしまい冗長です。https://caremad.io/posts/2013/07/setup-vs-requirement/ によると作法としては、次のような違いがあるとなっています。
- requirements.txt
- Python アプリに必要な依存を具体的に書く。
- ダウンロードする場所や、具体的なバージョンをはっきり書く。
- 開発時と同じ環境をデプロイ時に構築出来るようにする。
- setup.py の install_requires
- Python ライブラリに必要な依存を抽象的に書く。
- 出来るだけ幅広い環境に対応出来るように多くのバージョンに対応させる。
ライブラリを作るときは pip install -e .
で依存パッケージもインストールされるので、requirements.txt 不要だと思います。
extras_require
たまに setup.py に extras_require
が指定されている事があります。
extras_require={
"dev": ["pytest", "mypy", "pylint", "boto3", "pandas"],
"jupyter": ["jupyter", "pandas"],
},
これは開発向けなど、必要な時だけインストールするパッケージを表しています。これをインストールするには、パッケージ名の後ろに []
を書いてオプション名を指定します。例えばローカルに "jupyter", "pandas" をインストールするには、
pip install -e .[jupyter]
のようにします。
参考
- 仮想環境とパッケージ:
- Packaging Python Projects (setup.py を書いて配布物を作る方法)
- Packaging and distributing projects (setup.py の仕様)
- Requirements file format (requirements.txt の仕様):
- setup.py vs requirements.txt: (setup.py と requirements.txt の違い):
- Building and Distributing Packages with Setuptools (setup.py で Wheel や Egg を作る):
- ソースコード配布物を作成する (sdist の説明)
- extras_require の解説