40
56

More than 1 year has passed since last update.

Pythonパッケージの作り方

Last updated at Posted at 2022-07-13

1. packageにすると何が良いのか

  • pipでインストールできる
  • githubに置けるのでバージョン管理がやりやすい
  • CIがやりやすい
  • PyPIに置けば誰かの役に立つこともできちゃう

2. packageをつくる

ファイル構成

最小構成は以下のようになります

myapp/   ← プロジェクトのフォルダ(フォルダ名は何でも良いです)
  ├ setup.py    ← packageの情報を書くファイル
  └ myapp/     ← このフォルダ名でimportするようになります
    ├ __init__.py  ← フォルダ内のクラスやメソッドを定義するファイル
    └ main.py    ← 実際にコードを書くファイル(ファイル名は自由)

setup.py

packageのメタ情報を書くファイルでpipはこれに従ってインストールします

setup.py
from setuptools import setup, find_packages

setup(
    name='myapp',  # パッケージ名(pip listで表示される)
    version="0.0.1",  # バージョン
    description="sample of minimum package",  # 説明
    author='haneya',  # 作者名
    packages=find_packages(),  # 使うモジュール一覧を指定する
    license='MIT'  # ライセンス
)

main.py

実際のコードを書くファイルで、ファイル名は任意です
複数ファイルに分けて書く事もできます

myapp/main.py
def func1():
    print('func1() is executed')

__init__.py

packageで使う関数、クラス、定数などが、どのファイルにあるか書いておくファイルです

myapp/__init__.py
from .main import func1  # 同じフォルダのmain.pyにあるfunc1をimport
__all__ = ['func1']  # func1をpackageから呼べるようにする

インストールする

ここまで出来たら、setup.pyがあるフォルダから以下コマンドを実行します

terminal
pip install -e .

インストール出来たか確認しましょう

terminal
> pip list

Package      Version             Location
------------ ------------------- -----------------------------------------------------------------------------
myapp        0.0.1               c:\users\hoge\myapp

使ってみる

terminal
> python

関数はmyappフォルダから順にたどって呼ぶ関数にたどり着けば呼ぶことが出来ます

pythonコンソール
>>> import myapp
>>> myapp.main.func1()

func1() is executed

また、myappフォルダ内の__init__.pyで__all__に割り当てているのでmyapp直下から呼ぶことも出来ます

pythonコンソール
>>> import myapp
>>> myapp.func1()

func1() is executed

上手くインストールできたようです

3. コードを複数ファイルに分ける

コード量が増えてくるとファイルを分けたくなりますが、その場合も__init__.pyに書いてやれば1つのパッケージのモジュールとして扱ってくれます

変更箇所
myapp/
  ├ setup.py
  └ myapp/
    ├ __init__.py  ← sub.pyについて書き足す
    ├ main.py
    └ sub.py  ← 追加したファイル
myapp/sub.py
def func2():
    print('func2() is executed')
myapp/__init__.py
from .main import func1
from .sub import func2  # 追加
__all__ = ['func1', 'func2']  # 'func2'を追加

先程と同様にsetup.pyがあるフォルダから以下コマンドを実行すればインストールされます

terminal
pip install -e .

普通にたどって行って呼ぶ場合は以下のようになります

pythonコンソール
>>> import myapp
>>> myapp.main.func1()
func1() is executed

>>> myapp.sub.func2()
func2() is executed

また、myappフォルダ内の__init__.pyで__all__に割り当てているのでmyapp直下から呼ぶことも出来ます

pythonコンソール
>>> import myapp
>>> myapp.func1()
func1() is executed

>>> myapp.func2()
func2() is executed

4. 階層構造をつくる

階層をつくって役割ごとに関数を分けて整理したい場合も、普通にフォルダで階層を作ってやればそのように読み込んでくれます

変更箇所
myapp/
  ├ setup.py
  └ myapp/
    ├ app1/           ← 追加
    |  ├ __init__.py  ← 追加
    |  └ main.py      ← 追加
    ├ __init__.py
    ├ main.py
    └ sub.py
myapp/app1/main.py
def func3():
    print('func3() is executed')
myapp/app1/__init__.py
from .main import func3
__all__ = ['func3']

同様にフォルダをたどって行って呼ぶ場合は以下のようになります

pythonコンソール
>>> import myapp
>>> myapp.main.app1.main.func3()
func3() is executed

また、myapp/app1フォルダ内の__init__.pyで__all__に割り当てているのでmyapp.app1から呼ぶことも出来ます

pythonコンソール
>>> import myapp
>>> myapp.app1.func3()

5. githubに置いたプロジェクトからインストールする

区切りのいいところまで開発したらgithubに置くのが良いと思います

リポジトリURLが https://github.com/haneya-studio/test_package である場合は以下のようにインストールすることができます。

terminal
pip install git+https://github.com/haneya-studio/test_package.git

6. wheelに固める

以下のようにしてwhlファイルに固めることが出来ます

terminal
python setup.py bdist_wheel

上記を実行するとdistフォルダ内に以下のように書き出されます
image.png
以下のようにすればpipがインストールしてくれます

terminal
pip install myapp-0.0.2-py3-none-any.whl

7. データファイルをパッケージに含める

setuptools.find_packages()はPythonソースコードしか含めてくれないので、機械学習モデルやGUIのアイコンなどのファイルをパッケージに含めたい場合は別途MANIFEST.inに書く必要があります

myapp/
  ├ setup.py
  ├ MANIFEST.in      ← 含めるファイルの指定を書く
  └ myapp/
    ├ txt/
    |  └ hoge.txt  ← 含めたいファイル
    ├ __init__.py
    └ fuga.py      ← ここからhoge.txtを使います

7-1. MANIFEST.in

MANIFEST.inに以下のようにPATHを書いてincludeすればパッケージに含めてくれます

myapp/MANIFEST.in
include myapp/txt/hoge.txt

フォルダ内のファイルをまとめてincludeしたい場合は以下のようにも書けます

myapp/MANIFEST.in
recursive-include myapp/txt *

7-2. 組み込んだファイルの使い方

fuga.pyからhoge.txtへの相対PATHは './txt/hoge.txt' ですが、パッケージとしてimportして使う際には呼び出した側のワーキングディレクトリからの相対PATHと扱われてしまうので当然ながら上手く読み込めません。__file__で当該ファイルの場所を調べて、そこを基準に読みにいくのが良さげです。

myapp/fuga.py
import os

def fuga():
    path = os.path.dirname(os.path.abspath(__file__)) + "/txt/hoge.txt"
    print(path)
    print(os.path.isfile(path))
結果
>>> myapp.fuga()
C:\Users\jjaka\マイドライブ\python\_github\test_package\myapp/txt/hoge.txt
True

ちゃんと見つけることが出来ました。os.listdir()なんかも出来ますので扱いは普通のファイルと同じです。

参考にした記事

40
56
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
56