はじめに
Pythonを使っていると、様々なパッケージを使うかと思います。高度なデータ分析が簡単に出来るパッケージに頭が上がらない人たちも多いのではないでしょうか。私もその一人です。では、そんなパッケージはどのように作られているのでしょうか。そんな興味が湧いたそこのあなたにこの記事はぴったりです。自作パッケージを一緒に作ってPythonを一段深く理解していきましょう。それでは、本題に入りましょう。
結論
配布する予定の無い自作パッケージでしたら、比較的簡単に作成可能です。
- パッケージのファイル構成を考える。
- 任意のディレクトリに空のプロジェクトディレクトリを作成する。
- 空のプロジェクトディレクトリに空のパッケージディレクトリを作成する。
- 空のパッケージディレクトリに完成した複数のモジュールを入れる。
- パッケージディレクトリに
__init__.py
を作成し、初期化処理を行う。 - プロジェクトディレクトリに
setup.py
を作成し、pip
でのinstall
を可能にする。 - 完成!自分の実行環境にインストールしてみよう。
パッケージとは
概要
パッケージとは、複数のモジュールを束ねたディレクトリのことです。
また、複数のパッケージを束ねたディレクトリをライブラリと言います。
上記の図のようにディレクトリの入れ子状態をイメージしていただくとパッケージの立ち位置が分かりやすいと思います。
モチベーション
複数のモジュールをパッケージにまとめることで、どこにどんな機能を持ったモジュールがあるのかとても分かりやすくなり、新たにプログラムを書くときに簡単にimport
で呼び出すことが出来ます。
構成
パッケージディレクトリに直接モジュールを入れるのがスタンダードな形です。
パッケージのディレクトリにサブパッケージを用意し、サブパッケージの中にモジュールを入れる場合もあります。
また、モジュールの入ったパッケージディレクトリと、それ以外のパッケージに必要な情報を区別するために、両者を同レベルに格納するディレクトリを作ることもあります。
このディレクトリのことを、この記事の中ではプロジェクトディレクトリと呼称することにします。パッケージのインストールに欠かせないpipを開発した**PyPA(Python Packaging Authority)**は、プロジェクトでパッケージを作る際の例としてパッケージの中にサブパッケージを作る手法を取っています。詳しくは参考文献【1】からチェックしてみてください。
それらを踏まえた上で、この記事ではプロジェクトディレクトリの中にパッケージを作る手法を採用します。
手順
ここからは、実際に手順を追ってパッケージを作ってみましょう。
※OSはWindows10、実行環境はAnaconda3で作っていきますので、MacOSなど環境が異なる場合に正しく実行されない可能性があります。
【1】パッケージのファイル構成を考える。
fortune/
├───── setup.py
├───── fortune_package/
├───── __init__.py
├───── lottery_module.py
├───── horoscope_module.py
今回の例では、fortune_package
というパッケージにlottery_module.py
とhoroscope_module.py
を入れたファイル構成で占いが出来るパッケージを作っていきます。
【2】任意のディレクトリに空のプロジェクトディレクトリを作成する。
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を使って任意の場所に空のプロジェクトディレクトリを作成する方法が下記の参考文献【2】からご覧になれますのでご参考までにどうぞ。
【3】空のプロジェクトディレクトリに空のパッケージディレクトリを作成する。
C: Users\ユーザ名\Desktop\python files\fortune\fortune_package
【4】空のパッケージディレクトリに完成した複数のモジュールを入れる。
くじ引きが引けるモジュール:lottery_module
import random
def lottery():
lottery = ["大吉","中吉","小吉","末吉","凶","大凶"]
print(random.choice(lottery))
#実行結果
"""
大吉
"""
星座占いが出来るモジュール:horoscope_module
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位はおとめ座のあなた!
"""
これら2つのモジュールを空のパッケージディレクトリに入れます。
fortune/
├───── fortune_package/
├───── lottery_module.py
├───── horoscope_module.py
ここまで来るとファイル構成はこんな感じになります。ここからパッケージをパッケージたらしめるための作業に入ります。
【5】パッケージディレクトリに__init__.py
を作成し、初期化処理を行う。
__init__.py
は、ディレクトリをパッケージとして認識するために必要なファイルです。パッケージディレクトリとして作成したfortune_package/
の直下に__init__.py
を作成することで、fortune_package/
は正式にパッケージディレクトリとして認められます。
パッケージをimportする際は、パッケージディレクトリとして正式に認められたfortune_package/
の名前を使います。import fortune_package
ですね。
__init__.py
の中身は空でも成立します。ただ、あるコードを書くことでパッケージをimport
する際により便利になるので、説明していきます。
**__init__.py
中身が空の場合**
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位はおとめ座のあなた!
"""
関数を使いたい時、モジュール名をしっかり書く必要があります。パッケージの中に使いたい関数があることは分かっているけど、どのモジュールの中にあるか分からないといった際に、いちいちモジュール名を記入しないといけないのは意外と面倒です。試しに、モジュール名を記入せずに実行してみると以下の様にエラーが吐かれます。
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
中身を書いてモジュール名の記入をカットした場合**
from fortune_package.lottery_module import *
from fortune_package.horoscope_module import *
__init__.py
の中身で、fortune_package
パッケージからlottery_module
モジュールとhoroscope_module
モジュールのそれぞれ全ての関数を*
でimport
します。
そうすると、このようにモジュール名をカットして実行することが出来ます。
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
についての詳しい情報は参考文献【3】と【4】からどうぞ。
fortune/
├───── fortune_package/
├───── __init__.py
├───── lottery_module.py
├───── horoscope_module.py
ここまでくるとファイル構成はこんな感じ。残るはsetup.pyのみ!ラストスパート!
【6】パッケージディレクトリにsetup.py
を用意し、pip
でのinstall
を可能にする。
setup.py
には、パッケージをpip
でinstall
する際に必要なsetuptools
に関する設定をします。
setup.py
は、パッケージの取扱説明書の様なものでpip
を使ってインストールする際にはこのsetup.py
の記述に従ってインストールされます。そのため、パッケージを正しく使えるようにするためのたくさんのオプションがあり、それらオプションを使いこなしてsetup.py
を設定してく必要があります。
ただし、今回は配布無しで自分が使う自作パッケージについての解説であるため、最低限必要な記述3つだけでsetup.py
の設定を済ませます。
配布する際には、インストールする方がどんな環境であっても正しくパッケージが利用できるようにする必要がありますので、setup.py
に関する詳しい解説は今後投稿されるであろう**Pythonパッケージの作り方(配布有り編)**でします。お待ちください。待ちきれない方は、参考文献【1】からどうぞ。
それでは、実際に設定していきましょう。
今回setup.py
に記述するのは、name
とversion
とpackages
の3つです。
書きあがったもがこちらです。
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
パッケージディレクトリを自動的にパッケージとして扱ってくれます。
fortune/
├───── setup.py
├───── fortune_package/
├───── __init__.py
├───── lottery_module.py
├───── horoscope_module.py
これで、ファイル構成の全てを設定し終えることが出来ました。
最後は実際にインストールしてみましょう。
【7】完成!自分の実行環境にインストールしてみよう。
自作パッケージを自分のPCの実行環境にインストールして正しく利用できることを確認していきます。
まず、CMD、PowerShell、tarminalなどを立ち上げて、パスをプロジェクトディレクトリに移動してください。
C: Users\ユーザ名\Desktop\python files\fortune>
次に、pip install .
と入力してください。
C: Users\ユーザ名\Desktop\python files\fortune>pip install .
.
は現在のディレクトリの場所を指すので、fortune
プロジェクトディレクトリのsetup.py
の内容に従ってfortune_package
がパッケージとしてインストールされます。これで、以下の表示がされれば無事インストール完了です。
Successfully built fortune-package
Installing collected packages: fortune-package
Successfully installed fortune-package-1.0
インストールが完了したらPython
を起動して、パッケージをimport
して関数を実行してみて下さい。どのディレクトリでPython
を起動してもしっかりと利用できることでしょう。と、ここで最終チェックをします。
windows、anaconda、base(root)の実行環境でインストールした場合
C: Users\ユーザ名\anaconda3\Lib\site-packages
このパスを辿ってsite-packages
の中身を見て、パッケージディレクトリとパッケージ名-バージョン.dist-info
というディレクトリの二つがあることを確認してください。これで、他のサードパーティ製パッケージと同じく自作パッケージが使えることが確認できました。
windows、anaconda、任意の仮想環境の実行環境でインストールした場合
C: Users\ユーザ名\anaconda3\envs\仮想環境名\Lib\site-packages
このパスを辿ってsite-packages
の中身を見て、パッケージディレクトリとパッケージ名-バージョン.dist-info
というディレクトリの二つがあることを確認してください。これで、他のサードパーティ製パッケージと同じく自作パッケージが使えることが確認できました。
晴れて自作パッケージの完成です!お疲れさまでした。
おわりに
以上で自作パッケージの作り方の解説はおしまいです。Pythonのパッケージに対する解像度がアップして実際に作ることもできるようになりました。どんなパッケージを作ろうかワクワクしますね。是非あなたオリジナルのパッケージを作ってみて下さい。
さて、今回は配布無しのパッケージを作って参りました。これでも十分夢が広がるのですが、やはり配布してこそエンジニアの本望というところではないでしょうか。そんな配布有りのパッケージについては今後別の記事で改めて解説していきます。配布だけあってより厳格な設定が必要になっていきますが、私と一緒に勉強しましょう。
それではまた次回、お楽しみに!
参考文献
【1】
Packaging Python Projects - PyPA
【2】
【3】
Python の init.py とは何なのか - Qiita
【4】
【5】
【6】