Pythonで自作モジュールをインストールする手順

# 執筆の経緯
現在フリーランスで活動しています。この度新しい案件に参画することが決まったので、現在の現場で使っていた便利な小ネタなどをメモしておきたいと思い、この記事を書きます。

# やったこと
開発したコードをシステムに組み込むとき、モジュール化して結合します。モジュール化するとき、`setup.py`を作成し、`pip install`でインストールするのが最も簡単に感じたので、そのやり方をメモします。

小ネタとして、モジュールで使うライブラリを`pip install`を実行するときに`requirements.txt`からインストールする方法も記載します。

具体的には、開発している環境で以下のコマンドを実行することで、自作したモジュールと必要なライブラリをインストールする方法を書きます。
```shell
pip install .
```

# 実行環境
- MacBook Pro, Apple M1
- Python 3.11.1
- pip 22.3.1
- setuptools 65.5.0

# やったこと
以下の順に書きます。
1. モジュールの作成
1. requirements.txtの用意
1. setup.pyの作成
1. インストール
1. モジュールに変更を加えた時の挙動

## 1. モジュールの作成
今回は適当に作ります。とりあえずコードを格納するディレクトリを作ります。
```shell
mkdir check_setup
cd check_setup
```
<br>
この記事用に環境を分けているので、ライブラリは何もインストールされていません。

```shell
$ pip list
Package    Version
---------- -------
pip        22.3.1
setuptools 65.5.0
```
  
今回モジュールの内容は重要ではないので、まずは足し算をするだけのモジュールを作りたいと思います。ディレクトリ構成は以下のようになります。
```shell
$ tree check_setup
check_setup
└── src
    ├── __init__.py
    └── calculation.py
```

`calculation.py`には、下のような関数のみ実装しています。
```calculation.py
"""計算をするだけのモジュール"""

import numpy as np


def add(a, b):
    """足し算"""
    return np.array(a) + np.array(b)
```

モジュールの作成は以上です。

## 2. requirements.txtの用意
今回使う予定のライブラリはnumpyのみなので、numpyのみ書いておきます。
```requirements.txt
numpy==1.26.0
```

## 3. setup.pyの作成
以下のファイルを用意します。
```setup.py
from setuptools import setup


def get_requirements_from_file():
    with open("./requirements.txt") as f_in:
        requirements = f_in.read().splitlines()
    return requirements


setup(
    name="my_module",
    version="0.0.1",
    author="sekine345",
    author_email="sekine345@example.com",
    description="only calculation",
    package_dir={"": "src"},
    install_requires=get_requirements_from_file()
)
```

`setup`関数の引数は表のようになります。

| args | 説明 |
| :- | :- |
| name | モジュールの名前 |
| version | バージョン |
| author | 作成者 |
| author_email | 作成者のメールアドレス |
| description | モジュールの説明 |
| package_dir | モジュールのディレクトリ |
| install_requires | 必要なライブラリのリスト |

※より詳しい説明や他の引数については[こちら](https://docs.python.org/ja/3/distutils/setupscript.html#additional-meta-data)を参照してください。

`get_requirements_from_file`関数は、設定したファイルパスのrequirements.txtを読み込み、記載されたライブラリをリストで返す間数です。
この関数を用意したことにより、`sepup`関数の`install_requires`でライブラリを列挙する必要がなくなります。

## 4. インストール
準備ができたので、インストールしていこうと思います。ディレクトリ構造は以下のようになっているはずです。
```shell
$ tree check_setup
check_setup
├── requirements.txt
├── setup.py
└── src
    ├── __init__.py
    └── calculation.py
```

次のコマンドで、自作したモジュールをインストールします。
```shell
pip install .
```

これで、自作したモジュールを`import`できるようになりました。Pythonのインタプリタモードで以下のように`add`関数を実行できるようになりました。

```python 
from calculation import add
add(1, 2)
# 3
add([1, 2], [3, 4])
# array([4, 6])
```

## 5. モジュールに変更を加えた時の挙動
例えば引き算を行う関数`subtract`を追加したとします。

```calculation.py
"""計算をするだけのモジュール"""

import numpy as np


def add(a, b):
    """足し算"""
    return np.array(a) + np.array(b)

def subtract(a, b):
    """引き算, a - b"""
    return np.array(a) - np.array(b)
```

ソースコードに変更を加えましたが、これだけでは`subtract`を利用することはできません。

```python
from calculation import subtract
""" Import Error
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'subtract' from 'calculation'
"""
```

利用するためには再度`pip install .`を実行しなければいけません。

```shell
pip install .
```
```python
from calculation import subtract
subtract(1, 2)
# -1
subtract([1, 2], [3, 4])
# array([-2, -2])
```


ソースコードを変更するたびに`pip install .`するのは手間なので、開発中のモジュールの動作を確認する時は、変更するたびに自動でインストールされているモジュールに反映されて欲しいです。
  
モジュールへの変更を自動で反映するには、`pip install -e .`と`-e`をつけて実行します。
```shell
pip install -e .
```

ソースコードに掛け算を行う関数を追加します。
```calculation.py
"""計算をするだけのモジュール"""

import numpy as np


def add(a, b):
    """足し算"""
    return np.array(a) + np.array(b)

def subtract(a, b):
    """引き算, a - b"""
    return np.array(a) - np.array(b)

def product(a, b):
    """掛け算"""
    return np.array(a) * np.array(b)
```

`pip install .`を実行せずにPythonで`import`してみますが、`Import Error`を起こさずに`product`関数を実行できました。

```python
from calculation import product
product(1, 2)
# 2
product([1, 2], [3, 4])
# array([3, 8])
```

以上で、自作モジュールをインストールし、開発や動作確認ができるようになりました。

# 備考
`setup.py`や`pip install`でもっと便利な使い方があるよ！って方がいましたらご教授いただけると嬉しいです！　お気軽にコメントください。