VSCodeを使ったPythonパッケージの作り方

# 動機

プログラミングをしているとプログラムを使い回したいであったり、そのプログラムを他のメンバーに使ってもらうという場面が出てくると思います。  

そんなときは、コードを機能ごとにモジュール化、パッケージ化し、きちんとドキュメントを整備しておくと、他の人が使いやすくなるとなると思います。

またPythonのパッケージを作る際はVSCodeが強力なツールとなります。そこでVSCodeを使ったPythonパッケージの作り方について説明します。

またデータ分析プログラムをパッケージ化する時に役立つ情報も載せておきます。

# 環境
|||備考|
|:----:|:------:|:------:|
|OS|Windows10||
|conda|4.8.3|Anaconda Promptで`conda -V`|
|Anaconda|2020.02|Anaconda Promptで`conda list anaconda`|
|Python|3.8.2||
|VSCode|1.43.2||



# 環境のセットアップ

この記事を参考にVSCodeでのPython実行環境を整えてください。  
[VSCodeでのPython、Jupyter実行環境の構築方法](https://qiita.com/SolKul/items/f078877acd23bb1ea5b5)


# ディレクトリ構成

VSCodeでの実行環境ができたら、そこにPythonパッケージを開発するためのフォルダやファイルを作っていきます。

```
.
├── 好きなパッケージ名
│   ├── __init__.py
│   └── 好きなファイル名.py
├── setup.py
└── script.py
```

`好きなパッケージ名`フォルダ以下がパッケージ本体となります。

  

![01VSCodeDirectory.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466677/b8e081eb-0993-1bec-e650-e8ccb6d5a385.png)

例として、VSCode上で上図のように作ってみました。例としてパッケージ名を`mypackage`としてみます。  

# setup.pyを書く
`setup.py`はパッケージにはこれから作るパッケージの依存情報、バージョン情報、パッケージ名を設定するファイルです。

`setup.py`

```python
from setuptools import setup, find_packages

setup(
    name='mypackage',
    install_requires=['pandas','scikit-learn'],
    packages=find_packages()
)
```
`setup`という関数の引数に設定を書いていきます。
例えば、`install_requires`にはパッケージに必要なモジュールを書きます。  

ほかにも様々な項目があるので、[公式ドキュメント(setup スクリプトを書く)](https://docs.python.org/ja/3/distutils/setupscript.html)などで適宜調べてください。



# プログラムを書く
早速プログラムを書いて行きましょう。例として今回はKaggleのタイタニック号のデータを分析するパッケージを作ろうと思います。

パッケージ内のファイル、`preprocessing.py`に次のようなプログラムを書いたとします。
これはデータを前処理するプログラムです。

`preprocessing.py`

```python
class Preprocesser:
"""
前処理をするクラス
"""
    def process(self,data):
        """
        前処理をするメソッド
        """
        processed_data=data.copy()

        # Ageの欠損値を中央値に
        age_m=processed_data['Age'].median()
        processed_data['Age']=processed_data['Age'].fillna(age_m)

        #-----省略-----
        # 前処理を書いていく

        return processed_data
```

# プログラムを実行する

プログラムを実行してみましょう。パッケージ`mypackage`フォルダ直下に`script.py`というプログラム実行用の`.py`スクリプトを作ります。例として、訓練データを前処理し、表示するプログラムを書きます。

`script.py`

```python
def main():
    from mypackage import preprocessing
    import pandas as pd

    train=pd.read_csv('train.csv')

    # 前処理インスタンスを初期化し、前処理をする
    preprocesser=preprocessing.Preprocesser()
    train_processed=preprocesser.process(train)

    print(train_processed.head())

if __name__=='__main__':
    main()
```

自作パッケージのインポート方法についてですが、自作パッケージのフォルダ直下であれば、

```python
from mypackage import preprocessing
```

のように

```python
from 自作パッケージ名 import 個々のpythonコードのファイル名
```

でパッケージをインポートできます。

![02Run.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466677/47005840-356f-4d5f-e4e2-88058eaa586d.png)
 
この`script.py`を開いた状態で、VSCode上で`F5`キーを押すと上図のようにプログラムが実行でき、実行結果が`Terminal`上に表示されます。

# パッケージをデバッグする
[VSCodeでのPython、Jupyter実行環境の構築方法 #デバッグの活用](https://qiita.com/SolKul/items/f078877acd23bb1ea5b5#%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E3%81%AE%E6%B4%BB%E7%94%A8)  
と同様にパッケージのプログラミングでもVSCodeのデバッグ機能が使えます。  

![07DebugPackage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466677/a29fd923-0813-d16f-d44c-e06b05e8de6a.png)

例えば、上図のようにパッケージ内のコード`preprocessing.py`の7行目で`F9`キーを押します。そうすると、この行の左端に赤い点が表示されます。これを**ブレークポイント**といいます。そしてこの状態で`script.py`に戻り、F5キーを押して実行すると、

![08BreakRun.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466677/8817f3b1-86f7-92be-befd-5f9970439b75.png)

上図のようにパッケージ内の7行目で実行が一時停止し、その時宣言されている変数(ここでは`preprocessing.py`内の変数)が左のサイドバーに表示されています。このようにブレークポイントを用いることで、プログラムのバグ取り(=**デバッグ**)が捗ると思います。

# パッケージをインストールする

この自作パッケージを別環境にインストールしてみます。そしてその別環境でも動くかどうか試してみます。

Anaconda Promptを開き、新しく環境を作ります。

```bash
conda create -n 好きな環境名 python=Pythonバージョン
```

![03CreateEnv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466677/b365a88f-59f8-7433-62b2-7436e0bfe438.png)

今回は例として`setup_test`という環境を作りました。

そしてこの環境を起動します。

```bash
conda activate setup_test
```

そして上で編集した`setup.py`があるフォルダまで移動します。

```bash
cd setup.pyのあるディレクトリ
```

そしてこの自作パッケージをインストールします。

```bash
python setup.py install
```

インストールできたら、この状態で上の`script.py`を実行してみます。`script.py`と`train.csv`を適当な別のフォルダにコピーし、そこで実行してみます。


![05OnlyScript.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466677/2834d3d5-9eff-a1c5-e4b8-3a548eb937ac.png)

```bash
python script.py
```

![06RunIndependent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466677/01d907a0-0d36-8e56-c7ae-907044b9845d.png)

上図のように実行でき、前処理された訓練データが表示されました。このフォルダにはスクリプトとデータがあるだけで、自作パッケージのフォルダはありません。つまり、このフォルダで`script.py`で実行できたということは、この自作パッケージがこの環境にインストールできたということになります。

# 自作パッケージにデモ用データを用意する
自作パッケージを作る際に、ソースコード以外のデータファイルを含めたくなる場合があると思います。

例えばデータ分析用のパッケージを作って配布したとします。そして、他のメンバーがそのパッケージを使いたいと思った時に、データはすぐに用意できないけど分析の挙動を知りたい、というニーズがあると思います。そんなときに、あなたがデモ用のデータをパッケージの中に用意しておけば、その人にスムーズに説明できます。

例として、タイタニック号の訓練データをパッケージ内に用意する場合を説明します。ディレクトリにいくつかフォルダとファイルを加えます。


```
.
├── mymodule
│   ├── __init__.py
│   ├── preprocessing.py
│   ├── load_date.py *
│   └── resources *
│        └── train.csv *
├── setup.py
└── script.py

*:新しく追加したファイル・フォルダ
```


まず、自作パッケージ内にデータ用のフォルダを作ります。ここでは`resources`とします。そしてその中に訓練データ(`train.csv`)を入れます。

## パッケージ内のデータを読み込む

次のようなデモデータを読み込むコードを書き、パッケージに加えます。

`load_date.py`

```python
import pkgutil,io
import pandas as pd

class DataLoader:
    def load_demo(self):
        train_b=pkgutil.get_data('mypackage','resources/train.csv')
        train_f=io.BytesIO(train_b)
        train=pd.read_csv(train_f)
        return train
```

ここで`pkgutil`というPythonに標準で含まれているモジュールを使います。`pkgutil.get_data()`という関数は、パッケージ名とファイル名を指定するとその内容がバイナリで取得できます。

また`io`は読み込んだバイナリデータをファイルっぽく(`file-like object`)扱えるようにするために使っています。

デモデータが読み込めるかテストします。`script.py`の`main()`を次のように書き換え、VSCode上で`F5`で実行します。

`script.py`

```python
def main():
    from mypackage import load_data

    data_loader=load_data.DataLoader()
    train=data_loader.load_demo()
    print(train.head())
```

![09LoadTest.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466677/a58117c5-856d-8b03-49b6-4dd6d8f1c654.png)

上図のようにデモデータが読み込めました。

## パッケージインストール時にデータも同時にインストールされるようにする

ただ、これだけだとこのパッケージをインストールしてもデータも一緒にはインストールされません。パッケージをインストールした際にデータも同時にインストールされるようにするために、`setup.py`に一行加えます。

`setup.py`

```python
from setuptools import setup, find_packages

setup(
    name='mypackage',
    install_requires=['pandas','scikit-learn'],
    packages=find_packages(),
    package_data={'mypackage': ['resources/*']}
)
```

`package_data`にパッケージ名、フォルダ名を指定することで、パッケージインストールと同時にインストールするデータを指定することができます。

詳しくは[公式ドキュメント(2.6. パッケージデータをインストールする)](https://docs.python.org/ja/3/distutils/setupscript.html#installing-package-data)などを参考にしてください。

そうして上で説明したように、また新しく環境を作って、`setup.py`を使って自作パッケージをインストールすると、インストールした環境でデモデータが使えるようになってることが確認できると思います。

# おわりに

自分の作ったプログラムを他のメンバーに説明しやすいように、そして使いやすいようにするにはまだまだこれだけでは不十分で、本来であれば、`unittest`や`pytest`でテストを書いたり、`docstring`でプログラムの入出力について説明したりと、すべきことは他にもあります。

ただ、パッケージ化することはそうしたことの第一歩だと思います。

ここまで来た人はぜひ、テストを書いたり、`docstring`を書いたり、`docstring`を下のようなプログラムの仕様書に変換したりと自分のプログラムをわかりやすいものにしてください。

![10Sphinx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466677/29b23ac1-3daa-3b7a-99ca-40b449aa7aaa.png)

# 追加情報
Pythonコードのパッケージングについてはこの記事が大いに参考になりました。  
また`unittest`を使ったテストの書き方についても書いてあるので参考にしてください。      
[（インターン向けに書いた）Pythonパッケージを作る方法](https://qiita.com/Kensuke-Mitsuzawa/items/7717f823df5a30c27077)

ただ、テストについては`pytest`の方が使いやすいです。`unittest`に慣れた人は是非`pytest`を使ってみてください。  
[pytest(公式ドキュメント)](https://docs.pytest.org/en/latest/)

また自分のプログラムについて説明するドキュメントについてです。`docstring`を仕様書としてドキュメント化する事ができます。  
[Sphinxの使い方．docstringを読み込んで仕様書を生成](https://qiita.com/futakuchi0117/items/4d3997c1ca1323259844)

また、プログラムの使い方や理論を説明するのに図や式を用いて説明したい場合もあると思います。

そんなときは`mkdocs`というmarkdown 形式でドキュメントが作成できるモジュールを使うといいでしょう。  
[MkDocsによるドキュメント作成](https://qiita.com/mebiusbox2/items/a61d42878266af969e3c)

この`sphinx`と`mkdocs`でドキュメントを作成し、AWSのS3などでホスティングすれば、メンバーからこのプログラムどうやって使うんだ？と聞かれた場合、忙しいときはそのURLを送ればいいので非常に便利です。


タイタニック号のデータ分析についてはここを参考にしました。  
[【Kaggle初心者入門編】タイタニック号で生き残るのは誰？](https://www.codexa.net/kaggle-titanic-beginner/)