【Python】パッケージ配布 ~ setup.pyによる作成方法 ~

■ はじめに

https://dk521123.hatenablog.com/entry/2020/02/09/234350

で egg / wheel ファイルを作成したが、
今回は、Pythonのパッケージ配布で、ちょっと古い方法だが
setup.pyによる作成方法について扱う

ちなみに、この手の話は、結構、古いやり方とかあるので
ちょっと混乱する。

別の方法は、以下の関連記事を参照のこと

パッケージ配布 ~ pyproject.tomlによる作成方法 ~
https://dk521123.hatenablog.com/entry/2024/03/28/000000

目次

【1】用語整理
 1)setuptools
 2)setup.py
【2】setup.pyを使った作成方法
 1)ファイル構成
 2)コマンドによるパッケージ作成
【3】使い方
 1)インストール方法1:ファイルから pip install
 2)インストール方法2:GitHubから pip install
 3)アンインストール
【4】補足1:__init__.py の役割
 1)「[2] インポートの制御のため」について
 2)解決策「__all__」

【1】用語整理

1)setuptools

* Pythonパッケージするためのツール
 => 作成したモジュールを pip install でインストール可能になる

https://packaging.python.org/ja/latest/guides/distributing-packages-using-setuptools/
https://packaging.python.org/ja/latest/key_projects/#setuptools

2)setup.py

* パッケージのメタデータを定義

https://packaging.python.org/ja/latest/discussions/setup-py-deprecated/

より抜粋
~~~~
setup.py は非推奨になりましたか?
いいえ、 setup.py および Setuptools は非推奨にはなっていません。
・・・略・・・
しかしながら、 python setup.py および setup.py を
コマンドラインツールとして使うことは非推奨になりました。
・・・略・・・
python setup.py install
python setup.py develop
python setup.py sdist
python setup.py bdist_wheel

sdist

* ソースコードをtar.gz形式で配布

bdist_wheel

* ビルド済みのファイルをwheel形式で配布
 => 以下の関連記事でも使用した

https://dk521123.hatenablog.com/entry/2020/02/09/234350

【2】setup.pyを使った作成方法

* 以下の「1)ファイル構成」で行う

1)ファイル構成

├─ hello_package # パッケージ
│   ├─ world_sub_package # サブパッケージ(Options)
│   │   ├─ __init__.py # ★今回は空ファイルでもいい。詳細は「補足1:__init__.py の役割」を参照
│   │   └─ world.py # サブモジュール
│   ├─ __init__.py # ★今回は空ファイルでもいい。詳細は「補足1:__init__.py の役割」を参照
│   └─ hello.py # モジュール
├─ main.py # モジュール動作確認用プログラム(Options)
└─ setup.py # プロジェクトの一番上(ルート)にある必要がある

setup.py

from setuptools import setup

setup(
  # Package name
  name="hello_package",
  # Package version
  version="1.0.0",
  packages=[
    "hello_package",
    "hello_package.world_sub_package"
  ],
  url="https://dk521123.hatenablog.com/",
  license="MIT",
  author="Your-name",
  author_email="your-email@xxx.com",
  description="This is just a sample"
)

hello_package.hello.py

def say_hello(name):
  return f"Hello, {name}!!"

hello_package.world_sub_package.world.py

def say_world():
  return "The World!!?"

main.py

import hello_package.hello
from hello_package.world_sub_package import world

result1 = hello_package.hello.say_hello("Mike")
# The result from hello - Hello, {name}!!
print(f"The result from hello - {result1}")

result2 = world.say_world()
# The result from world - The World!!?
print(f"The result from world - {result2}")

2)コマンドによるパッケージ作成

https://packaging.python.org/ja/latest/guides/distributing-packages-using-setuptools/#packaging-your-project

に従って行うと、以下の成果物(パッケージ配布物)ができる
~~~~~
* dist/hello_package-1.0.0.tar.gz
* dist/hello_package-1.0.0-py3-none-any.whl
~~~~~

コマンド例

# Step1: build パッケージをインストールする
python -m pip install build

# Step2: パッケージ作成
python -m build

# 個別で作成したい場合
# python -m build --sdist
# python -m build --wheel

# 以下は、もう古いらしい。。。
# python setup.py sdist
# python setup.py bdist_wheel

【3】使い方

1)インストール方法1:ファイルから pip install

# インストール
pip install dist/hello_package-1.0.0.tar.gz
# pip install dist/hello_package-1.0.0-py3-none-any.whl

・・・略・・・
Successfully built hello-package
Successfully installed hello-package-1.0.0
・・・略・・・

# 確認
pip list
・・・略・・・
hello_package                 1.0.0
・・・略・・・

2)インストール方法2:GitHubから pip install

* 「pip install git+<GithubRepositoryURL>」でインストール可能

コマンド例

# リポジトリ名とパッケージ名が同じ場合
pip install git+https://github.com/acc_name/pkg_name.git

# リポジトリ名とパッケージ名が異なる場合
#  =>「パッケージ名@」を「git+https://…….git」の前に付加
pip install pkg_name@git+https://github.com/acc_name/repo_name.git

# 「@v1.0」のように末尾にバージョンやタグを付加することも可能
pip install git+https://github.com/acc_name/pkg_name.git@v1.0

3)アンインストール

pip uninstall --yes hello_package

【4】補足1:init.py の役割

* __init__.py の主な役割は、以下の通り。

[1] Pythonパッケージとして認識させるため
[2] インポートの制御のため
[3] パッケージの初期化 / パッケージ全体の設定

 => 詳細は、以下のサイトを参照。

https://ya6mablog.com/how-to-use-init-py/

1)「[2] インポートの制御のため」について

例えば、以下のように変更すると、エラーになる
* 同じパッケージ内にファイルを追加
* main.pyの import を「*」に変更

エラーになるフォルダ構成

hello_package.world_sub_package
 + world.py
 + world2.py << 追加し、同じメソッドを追加
main.py << 変更(『[エラー] main.py』を参照)

[エラー] main.py

import hello_package.hello
# 「*」に変更するとエラーになる
#from hello_package.world_sub_package import world
from hello_package.world_sub_package import *

result1 = hello_package.hello.say_hello("Mike")
print(f"The result from hello {result1}")

result2 = world.say_world()
print(f"The result from world {result2}")

2)解決策「all

* 以下のように

hello_package.world_sub_package.init.py

__all__ = ['hello', 'hello2']

参考文献

bdist/sdist
https://blog.n-t.jp/post/tech/python-wheel-bdist-sdist-pip/
setup.py
https://ya6mablog.com/how-to-use-setup-py/

関連記事

パッケージ配布 ~ setuptoolsあれこれ ~
https://dk521123.hatenablog.com/entry/2024/04/17/132415
パッケージ配布 ~ pyproject.tomlによる作成方法 ~
https://dk521123.hatenablog.com/entry/2024/03/28/000000
egg / wheel ファイルを作成する
https://dk521123.hatenablog.com/entry/2020/02/09/234350
パッケージ管理 ~ pip ~
https://dk521123.hatenablog.com/entry/2021/07/02/000000
オフライン環境下で pip install するには
https://dk521123.hatenablog.com/entry/2021/07/10/164833