Pythonパッケージの作り方

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


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


## 2. packageをつくる


### ファイル構成


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


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


### setup.py


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


```python: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


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


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


### \_\_init\_\_.py


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


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


### インストールする


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


```terminal:terminal
pip install -e .
```


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


```terminal:terminal
> pip list

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


### 使ってみる


```terminal:terminal
> python
```


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


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

func1() is executed
```


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


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

func1() is executed
```


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


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


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


```:変更箇所
myapp/
　　├ setup.py
　　└ myapp/
　　　　├ __init__.py　　← sub.pyについて書き足す
　　　　├ main.py
　　　　└ sub.py　　← 追加したファイル
```


```python:myapp/sub.py
def func2():
    print('func2() is executed')
```


```python:myapp/__init__.py
from .main import func1
from .sub import func2  # 追加
__all__ = ['func1', 'func2']  # 'func2'を追加
```


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


```terminal:terminal
pip install -e .
```

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


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

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


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


```python: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
```


```python:myapp/app1/main.py
def func3():
    print('func3() is executed')
```


```python:myapp/app1/__init__.py
from .main import func3
__all__ = ['func3']
```



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


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


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


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


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


区切りのいいところまで開発したらgithubに置くのが良いと思います
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/c555e502-2921-77cd-5afa-8c3398faa0f3.png" width=50%>
リポジトリURLが https://github.com/haneya-studio/test_package である場合は以下のようにインストールすることができます。
```terminal:terminal
pip install git+https://github.com/haneya-studio/test_package.git
```


## 6. wheelに固める


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


```terminal:terminal
python setup.py bdist_wheel
```


上記を実行するとdistフォルダ内に以下のように書き出されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/41ad79e0-359c-0f93-e84c-4deef8ae47b5.png)
以下のようにすればpipがインストールしてくれます


```terminal: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すればパッケージに含めてくれます


```python:myapp/MANIFEST.in
include myapp/txt/hoge.txt
```


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


```python:myapp/MANIFEST.in
recursive-include myapp/txt *
```


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


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


```python: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))
```


```python:結果
>>> myapp.fuga()
C:\Users\jjaka\マイドライブ\python\_github\test_package\myapp/txt/hoge.txt
True
```


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



## 参考にした記事

https://lightgauge.net/language/python/8582/

https://iroha.daizutabi.net/

https://blog.amedama.jp/entry/2015/12/26/012332

https://qiita.com/arc279/items/b70754378d1855e1a790