【今更聞きたい】Pythonパッケージの作り方(配布無し編)【Python】

# はじめに

Pythonを使っていると、様々なパッケージを使うかと思います。高度なデータ分析が簡単に出来るパッケージに頭が上がらない人たちも多いのではないでしょうか。私もその一人です。では、そんなパッケージはどのように作られているのでしょうか。そんな興味が湧いたそこのあなたにこの記事はぴったりです。自作パッケージを一緒に作ってPythonを一段深く理解していきましょう。それでは、本題に入りましょう。

# 結論

配布する予定の無い自作パッケージでしたら、比較的簡単に作成可能です。

1. パッケージのファイル構成を考える。
2. 任意のディレクトリに空のプロジェクトディレクトリを作成する。
3. 空のプロジェクトディレクトリに空のパッケージディレクトリを作成する。
4. 空のパッケージディレクトリに完成した複数のモジュールを入れる。
5. パッケージディレクトリに`__init__.py`を作成し、初期化処理を行う。
6. プロジェクトディレクトリに`setup.py`を作成し、`pip`での`install`を可能にする。
7. 完成！自分の実行環境にインストールしてみよう。

# パッケージとは

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f313636383434382f38376166326665322d393263622d666635312d393365332d6634633636346335643331392e706e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668448/f1cdf9a6-8090-8c96-83ea-009ef74128e7.png)


## 概要

パッケージとは、複数のモジュールを束ねたディレクトリのことです。
また、複数のパッケージを束ねたディレクトリをライブラリと言います。
上記の図のようにディレクトリの入れ子状態をイメージしていただくとパッケージの立ち位置が分かりやすいと思います。

## モチベーション

複数のモジュールをパッケージにまとめることで、どこにどんな機能を持ったモジュールがあるのかとても分かりやすくなり、新たにプログラムを書くときに簡単に`import`で呼び出すことが出来ます。

## 構成

パッケージディレクトリに直接モジュールを入れるのがスタンダードな形です。

![パッケージの作り方_1.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668448/b460db41-10e1-c45d-b8f4-abf73dde19ae.png)

パッケージのディレクトリにサブパッケージを用意し、サブパッケージの中にモジュールを入れる場合もあります。

![パッケージの作り方_2.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668448/5924a291-51cc-a8f5-c13a-4c8adf3de7c0.png)

また、モジュールの入ったパッケージディレクトリと、それ以外のパッケージに必要な情報を区別するために、両者を同レベルに格納するディレクトリを作ることもあります。

![パッケージの作り方_3.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668448/a4fcf9fc-d0f8-325a-ebe5-f9aee3b4e576.png)

このディレクトリのことを、この記事の中では**プロジェクトディレクトリ**と呼称することにします。パッケージのインストールに欠かせないpipを開発した**PyPA(Python Packaging Authority)**は、プロジェクトでパッケージを作る際の例としてパッケージの中にサブパッケージを作る手法を取っています。詳しくは参考文献【１】からチェックしてみてください。

それらを踏まえた上で、この記事ではプロジェクトディレクトリの中にパッケージを作る手法を採用します。

# 手順

ここからは、実際に手順を追ってパッケージを作ってみましょう。

※OSはWindows10、実行環境はAnaconda3で作っていきますので、MacOSなど環境が異なる場合に正しく実行されない可能性があります。

## 【１】**パッケージのファイル構成を考える。**

```python
fortune/
 ├───── setup.py
 ├───── fortune_package/
         ├───── __init__.py
         ├───── lottery_module.py
         ├───── horoscope_module.py
```

今回の例では、`fortune_package`というパッケージに`lottery_module.py`と`horoscope_module.py`を入れたファイル構成で占いが出来るパッケージを作っていきます。

## 【２】**任意のディレクトリに空のプロジェクトディレクトリを作成する。**

```python
C: Users\ユーザ名\Desktop\python files\fortune\
```

空のプロジェクトディレクトリを作成するディレクトリは任意なので、基本的にはどのディレクトリでも構いませんが、避けた方が無難なディレクトリもあるので説明します。

**PythonとAnacondaをインストール済みの場合**

この場合、サードパーティ製のパッケージをインストールした際に保存される場所は以下のバスになります。

`C: Users\ユーザ名\anaconda3\Lib\site-packages`

自作パッケージが完成した後、利用するためにAnacondaにインストールする必要があるのですが、無事インストール出来た際に自作パッケージが保存されるのも上記の`site-packages`になります。ここにプロジェクトディレクトリを作成してしまうと、`site-packages`で作成したパッケージを`site-packages`にインストールして保存するというややこしいことになるので止めておきましょう。

普段作成したPythonモジュールを保存しているディレクトリがあればそこに、無ければDesktopに新たに自作パッケージ用のディレクトリを作成し、そこに空のプロジェクトディレクトリを作成することをおすすめします。

jupyter notebookを使って任意の場所に空のプロジェクトディレクトリを作成する方法が下記の参考文献【２】からご覧になれますのでご参考までにどうぞ。

## 【３】**空のプロジェクトディレクトリに空のパッケージディレクトリを作成する。**

```python
C: Users\ユーザ名\Desktop\python files\fortune\fortune_package
```

## 【４】**空のパッケージディレクトリに完成した複数のモジュールを入れる。**

くじ引きが引けるモジュール：`lottery_module`

```python
import random

def lottery():
    lottery = ["大吉","中吉","小吉","末吉","凶","大凶"]
    print(random.choice(lottery))

#実行結果
"""
大吉
"""
```

星座占いが出来るモジュール：`horoscope_module`

```python
import random 

def horoscope():
    horoscope = [
        "牡羊座","おうし座","ふたご座","かに座","しし座","おとめ座","てんびん座","さそり座","いて座","やぎ座","みずがめ座","うお座"
    ]
    random.shuffle(horoscope)
    for n,i in enumerate(horoscope, 1):
        print(f"第{n}位は{i}のあなた！")

#実行結果
"""
第1位はいて座のあなた！
第2位はてんびん座のあなた！
第3位は牡羊座のあなた！
第4位はみずがめ座のあなた！
第5位はしし座のあなた！
第6位はかに座のあなた！
第7位はやぎ座のあなた！
第8位はさそり座のあなた！
第9位はおうし座のあなた！
第10位はうお座のあなた！
第11位はふたご座のあなた！
第12位はおとめ座のあなた！
"""
```

これら２つのモジュールを空のパッケージディレクトリに入れます。

```python
fortune/
 ├───── fortune_package/
         ├───── lottery_module.py
         ├───── horoscope_module.py
```

ここまで来るとファイル構成はこんな感じになります。ここからパッケージをパッケージたらしめるための作業に入ります。

## 【５】**パッケージディレクトリに`__init__.py`を作成し、初期化処理を行う。**

`__init__.py`は、ディレクトリをパッケージとして認識するために必要なファイルです。パッケージディレクトリとして作成した`fortune_package/`の直下に`__init__.py`を作成することで、`fortune_package/`は正式にパッケージディレクトリとして認められます。
パッケージをimportする際は、パッケージディレクトリとして正式に認められた`fortune_package/`の名前を使います。`import fortune_package`ですね。
`__init__.py`の中身は空でも成立します。ただ、あるコードを書くことでパッケージを`import`する際により便利になるので、説明していきます。

`**__init__.py`中身が空の場合**

```python
import fortune_package as fp
#くじ引きがしたい場合
fp.lottery＿module.lottery()
#星座占いがしたい場合
fp.horoscope_module.horoscope()

#実行結果
"""
大吉
"""

"""
第1位はいて座のあなた！
第2位はてんびん座のあなた！
第3位は牡羊座のあなた！
第4位はみずがめ座のあなた！
第5位はしし座のあなた！
第6位はかに座のあなた！
第7位はやぎ座のあなた！
第8位はさそり座のあなた！
第9位はおうし座のあなた！
第10位はうお座のあなた！
第11位はふたご座のあなた！
第12位はおとめ座のあなた！
"""
```

関数を使いたい時、モジュール名をしっかり書く必要があります。パッケージの中に使いたい関数があることは分かっているけど、どのモジュールの中にあるか分からないといった際に、いちいちモジュール名を記入しないといけないのは意外と面倒です。試しに、モジュール名を記入せずに実行してみると以下の様にエラーが吐かれます。

```python
import fortune_package as fp
fp.lottery()

#実行結果
#lottery_module.py, line 2, in <module>
#    fp.lottery()
#AttributeError: module 'fortune_package' has no attribute 'lottery'
```

「`fortune_package`には`lottery()`という関数はない」というエラーが出ました。
モジュール名を記入しないことでパスが途切れて`fortune_package`が`lottery()`を見失ってしまったんですね。

さて、どうにかして**関数を実行する際にモジュール名をカットする方法**はないのでしょうか。あるんです。ということで`__init()__.py`の中身を書いていきましょう。

`**__init__.py`中身を書いてモジュール名の記入をカットした場合**

```python
from fortune_package.lottery_module import *
from fortune_package.horoscope_module import *
```

`__init__.py`の中身で、`fortune_package`パッケージから`lottery_module`モジュールと`horoscope_module`モジュールのそれぞれ全ての関数を`＊`で`import`します。
そうすると、このようにモジュール名をカットして実行することが出来ます。

```python
import fortune_package as fp
#くじ引きがしたい場合
fp.lottery()
#星座占いがしたい場合
fp.horoscope()

#実行結果
"""
大吉
"""

"""
第1位はいて座のあなた！
第2位はてんびん座のあなた！
第3位は牡羊座のあなた！
第4位はみずがめ座のあなた！
第5位はしし座のあなた！
第6位はかに座のあなた！
第7位はやぎ座のあなた！
第8位はさそり座のあなた！
第9位はおうし座のあなた！
第10位はうお座のあなた！
第11位はふたご座のあなた！
第12位はおとめ座のあなた！
"""
```

無事カットできましたが、エンジニアとしてはやはり仕組みが知りたくなります。

**仕組み**

パッケージを`import`した際に、初期化処理として`__init__.py`の中身のコードが裏で処理されるために表で書かなくて済むという仕組みです。
上記の例では、初期化処理で二つのモジュールの全ての関数をあらかじめ`import`していたため、パッケージを`import`した時点で二つのモジュールを使うことが織り込み済みとなり、関数を使う際にモジュール名を書かなくてもパスを見失わず理解してくれたわけです。
快適なホテル体験のための優秀なホテルスタッフを育てる教育マニュアルを作る様に、快適にパッケージを使うための便利な初期化処理を司る`__init__.py`を作るのです。
普段自分がよく使うパッケージの`__init__.py`を覗いてみてください。たくさんの初期化処理によって使いやすくなる様に工夫がなされてます。作り手のホスピタリティを強く感じられます。
仕組みがわかれば応用が可能になります。
例えば、`__init__.py`の中に関数を書くと、そのパッケージを`import`した際にモジュール名無しで使うことが出来ます。

`__init__.py`についての詳しい情報は参考文献【３】と【４】からどうぞ。

```python
fortune/
 ├───── fortune_package/
         ├───── __init__.py
         ├───── lottery_module.py
         ├───── horoscope_module.py
```

ここまでくるとファイル構成はこんな感じ。残るはsetup.pyのみ！ラストスパート！

## 【６】**パッケージディレクトリに`setup.py`を用意し、`pip`での`install`を可能にする。**

`setup.py`には、パッケージを`pip`で`install`する際に必要な`setuptools`に関する設定をします。
`setup.py`は、パッケージの取扱説明書の様なもので`pip`を使ってインストールする際にはこの`setup.py`の記述に従ってインストールされます。そのため、パッケージを正しく使えるようにするためのたくさんのオプションがあり、それらオプションを使いこなして`setup.py`を設定してく必要があります。
ただし、今回は配布無しで自分が使う自作パッケージについての解説であるため、最低限必要な記述３つだけで`setup.py`の設定を済ませます。
配布する際には、インストールする方がどんな環境であっても正しくパッケージが利用できるようにする必要がありますので、`setup.py`に関する詳しい解説は今後投稿されるであろう**Pythonパッケージの作り方（配布有り編）**でします。お待ちください。待ちきれない方は、参考文献【１】からどうぞ。

それでは、実際に設定していきましょう。
今回`setup.py`に記述するのは、`name`と`version`と`packages`の３つです。
書きあがったもがこちらです。

```python
from setuptools import setup, find_packages

setup(
    name="fortune_package",
    version="1.0",
    packages=find_packages()
)
```

まず、`setup.py`で必要な関数である`setup`を`import`します。
インストールする際に必要な情報は全てこの`setup`関数の中に記述していきます。
まずは`name`ですが、これは単純にパッケージの名前を入力してください。
次に`version`ですが、これはパッケージのバージョンを入力してください。
最後に`packages`ですが、ここでは`find_package`関数を使います。`find_package()`は、パッケージをインストールする際に引数に指定した名前のディレクトリをプロジェクトディレクトリの中から探してパッケージとして扱います。引数が無い場合は、`pip`でインストールする際のディレクトリ（プロジェクトディレクトリ）直下のパッケージディレクトリを自動的にパッケージとして扱います。
今回の場合、`fortune`プロジェクトディレクトリでインストールを行えば、直下の`fortune_package`パッケージディレクトリを自動的にパッケージとして扱ってくれます。

```python
fortune/
 ├───── setup.py
 ├───── fortune_package/
         ├───── __init__.py
         ├───── lottery_module.py
         ├───── horoscope_module.py
```

これで、ファイル構成の全てを設定し終えることが出来ました。
最後は実際にインストールしてみましょう。

## 【７】完成！自分の実行環境にインストールしてみよう。

自作パッケージを自分のPCの実行環境にインストールして正しく利用できることを確認していきます。

まず、CMD、PowerShell、tarminalなどを立ち上げて、パスをプロジェクトディレクトリに移動してください。

```python
C: Users\ユーザ名\Desktop\python files\fortune>
```

次に、`pip install .` と入力してください。

```python
C: Users\ユーザ名\Desktop\python files\fortune>pip install .
```

`.`は現在のディレクトリの場所を指すので、`fortune`プロジェクトディレクトリの`setup.py`の内容に従って`fortune_package`がパッケージとしてインストールされます。これで、以下の表示がされれば無事インストール完了です。

```python
Successfully built fortune-package
Installing collected packages: fortune-package
Successfully installed fortune-package-1.0
```

インストールが完了したら`Python`を起動して、パッケージを`import`して関数を実行してみて下さい。どのディレクトリで`Python`を起動してもしっかりと利用できることでしょう。と、ここで最終チェックをします。

**windows、anaconda、base(root)の実行環境でインストールした場合**

```python
C: Users\ユーザ名\anaconda3\Lib\site-packages
```

このパスを辿って`site-packages`の中身を見て、パッケージディレクトリと`パッケージ名-バージョン.dist-info`というディレクトリの二つがあることを確認してください。これで、他のサードパーティ製パッケージと同じく自作パッケージが使えることが確認できました。

**windows、anaconda、任意の仮想環境の実行環境でインストールした場合**

```python
C: Users\ユーザ名\anaconda3\envs\仮想環境名\Lib\site-packages
```

このパスを辿って`site-packages`の中身を見て、パッケージディレクトリと`パッケージ名-バージョン.dist-info`というディレクトリの二つがあることを確認してください。これで、他のサードパーティ製パッケージと同じく自作パッケージが使えることが確認できました。

晴れて自作パッケージの完成です！お疲れさまでした。:thumbsup:

# おわりに

以上で自作パッケージの作り方の解説はおしまいです。Pythonのパッケージに対する解像度がアップして実際に作ることもできるようになりました。どんなパッケージを作ろうかワクワクしますね。是非あなたオリジナルのパッケージを作ってみて下さい。

さて、今回は配布無しのパッケージを作って参りました。これでも十分夢が広がるのですが、やはり配布してこそエンジニアの本望というところではないでしょうか。そんな配布有りのパッケージについては今後別の記事で改めて解説していきます。配布だけあってより厳格な設定が必要になっていきますが、私と一緒に勉強しましょう。

それではまた次回、お楽しみに！

# 参考文献

【１】

[Packaging Python Projects - PyPA](https://packaging.python.org/en/latest/tutorials/packaging-projects/#packaging-python-projects)

【２】

[［Python入門］パッケージ](https://atmarkit.itmedia.co.jp/ait/articles/1907/09/news010.html#:~:text=%E7%9B%B8%E5%AF%BE%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88-,%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%A8%E3%81%AF,%E3%82%82%E3%81%AE%E3%81%A0%E3%81%A8%E8%80%83%E3%81%88%E3%82%89%E3%82%8C%E3%82%8B%E3%80%82)

【３】

[Python の __init__.py とは何なのか - Qiita](https://qiita.com/msi/items/d91ea3900373ff8b09d7)

【４】

[Python __init__.pyの書き方 - Qiita](https://qiita.com/FN_Programming/items/2dcabc93365a62397afe)

【５】

[Pythonで自分だけのクソライブラリを作る方法](https://zenn.dev/karaage0703/articles/db8c663640c68b)

【６】

[Anaconda 環境に独自にモジュールを追加する - datalove's diary](https://datalove.hatenadiary.jp/entry/python/anaconda/how-to-install-3rd-party-module-into-anaconda)