Help us understand the problem. What is going on with this article?

Python でパッケージを開発して配布する標準的な方法

今更ながら 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
source .venv/bin/activate

最後の source .venv/bin/activate が環境に入るコマンドです。抜けるには単に deactivate と入力します。

開発に使うライブラリのインストール (無くても良いかも)

開発に使うライブラリは 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 -mmain() を呼ぶために 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_pointsconsole_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]

のようにします。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away