モジュールはPythonの最高レベルの組織単位です。
もしあなたが少しでもPythonに慣れているなら、おそらく準備されたモジュールを使うだけでなく、自分でいくつか作成したことがあるでしょう。
では、モジュールとはいったい何なのでしょうか?モジュールはコードとデータを格納する単位で、Pythonプロジェクトにコードの再利用を提供し、また、システムの名前空間を自己完結型のパッケージに分割するのに便利なものです。
モジュールの属性にアクセスできるのはインポート後だけなので、自己充足的です。
また、名前のパッケージとして理解することもでき、インポートされるとインポートモジュールオブジェクトの属性となります。
実際、拡張子が.pyのPythonファイルはすべてモジュールに相当します。
この記事では、モジュールの作成とインポートの基本から、より高度なモジュールの使用例、そしてパッケージングと “公式 “Pythonソフトウェアリポジトリへのモジュール提出まで、それぞれ3つのパートで構成されています。
モジュールの作成、モジュールの使用、そしてPyPIへのパッケージの提出です。
モジュールの作成
基本的なこと
Pythonのモジュールは、接尾辞が.pyのファイルがモジュールを表すので、作るのにあまり哲学的なことはありません。
しかし、すべてのPythonファイルがモジュールとしてインポートされるように設計されているわけではありません。
スタンドアロンのPythonアプリとして実行するために使われるPythonファイル(トップレベルファイル)は、通常スクリプトとして実行するように設計されており、それらをインポートすると実際にスクリプト内のコマンドが実行されることになります。
他のコードからインポートされるように設計されたモジュールは、コードを実行せず、そのトップレベル名をインポートオブジェクトの属性として公開するだけです。
また、インポートとトップレベルスクリプトの両方に使用できるデュアルモードコードのPythonモジュールを設計することも可能です。
モジュールの作成規則はかなり緩やかですが、モジュールの命名規則が1つあります。
モジュールのファイル名はインポートされると Python の変数名になるので、Python の予約語を使ってモジュール名を付けることは許されません。
例えば、for.pyというモジュールは作成できますが、forは予約語なのでインポートできません。
ここまでの内容を、“Hello world!”の例で説明しましょう。
# Module file: my_module.py
def hello_printer():
print("Hello world!")
name = "John"
# Script file: my_script.py
import my_module
my_module.hello_printer()
print("Creator:", my_module.name)
my_module.pyは、他のPythonファイルからインポートして再利用できるモジュールとして設計されています。
その内容は、関数と変数を定義するだけで、何もアクションを呼び出していないことから分かります。
Let’s run the ‘my_script.py’ file in the terminal:
$ python my_script.py
Hello world!
Creator: John
As noted before, an important takeaway from this first basic example is that module filenames are important. Once imported they become variables/objects in the importer module. All top-level code definitions within a module become attributes of that variable.
By ‘top-level’ I mean any function or variable which is not nested inside another function or class. These attributes can then be accessed using the standard <object>.<attribute>
statement in Python.
In the following section we first look at the “big picture” of multi-file Python programs, and then in “dual mode” Python files.
プログラム・アーキテクチャ
Pythonのプログラムは、インポートを使って互いに接続された複数のファイルから構成されます。
Pythonは、他の多くのプログラミング言語と同様に、このモジュール式のプログラム構造を採用しており、機能性は再利用可能なユニットにグループ化されています。
一般的に、複数ファイルのPythonアプリケーションでは、3つのタイプのファイルを区別することができます。
- トップレベルファイル。トップレベルファイル:プログラムの主要なエントリポイントであるPythonファイル、またはスクリプト。このファイルは、アプリケーションを起動するために実行されます。
- ユーザー定義モジュール。ユーザー定義モジュール:トップレベルファイルにインポートされる、または互いにインポートしあって、別々の機能を提供するPythonファイル。これらのファイルは通常、コマンドプロンプトから直接起動されることはなく、プロジェクトの目的のためにカスタムメイドされます。
- 標準ライブラリモジュール。システムインターフェイス、インターネットスクリプト、GUI構築などのためのプラットフォームに依存しないツールなど、Pythonのインストールパッケージに組み込まれているコード化済みのモジュールです。これらのモジュールはPythonの実行ファイル自体には含まれず、Pythonの標準ライブラリの一部となっています。
図1に、3つのファイルタイプによるプログラム構成の例を示します。
図1:トップレベルスクリプト、カスタムモジュール、標準ライブラリモジュールを含むプログラム構造例。
この図では、モジュール ‘top_module.py’ はトップレベルのPythonファイルで、モジュール ‘module1′ で定義されたツールをインポートし、’module1’ を通して ‘module2’ のツールにもアクセスすることができる。
2つのカスタムモジュールは、標準のPythonライブラリから他のモジュールと同様に、互いのリソースを使用します。
インポートチェーンは好きなだけ深くすることができます。
インポートされるファイルの数に制限はなく、互いにインポートすることができます。
コードの例で説明しましょう。
# top_module.py
import module1
module1.print_parameters()
print(module1.combinations(5, 2))
# module1.py
from module2 import k, print_parameters
from math import factorial
n = 5.0
def combinations(n, k):
return factorial(n) / factorial(k) / factorial(n-k)
# module2.py
import module1
k = 2.0
def print_parameters():
print('k = %.f n = %.f' % (k, module1.n))
上記の例では、top_module.pyがユーザーによって実行されるトップレベルモジュールで、module1.pyを介して他のモジュールからツールをインポートしています。
module1と
module2` はユーザー定義モジュールで、’math’ モジュールは Python の標準ライブラリからインポートされています。
トップレベルのスクリプトを実行すると、次のようになる。
$ python top_module.py
k = 2 n = 5
10.0
トップレベルのPythonファイルを実行すると、そのソースコードの文とインポートされたモジュール内の文は、バイトコードというプラットフォームに依存しない中間形式でコンパイルされます。
インポートされたモジュールのバイトコードファイルは、Python 3.2までは.pyファイルと同じディレクトリに.pycの拡張子で、Python 3.2以降ではプログラムのホームディレクトリの中の _pycache_dictories に格納されます。
$ ls __pycache__/
module1.cpython-36.pyc module2.cpython-36.pyc
デュアルモードコード
A
モジュールの使用
インポートステートメント
プログラムアーキテクチャのセクションの例は、2つのインポート文の違いを見るのに便利でした。
importと
fromです。
主な違いは、importはモジュール全体を1つのオブジェクトとしてロードするのに対して、
fromはモジュールから特定のプロパティや関数をロードすることです。
from ステートメントでインポートした名前は、インポートしたオブジェクト名を呼び出すことなく、インポーターモジュールで直接使用することができます。
Python 3.x では、 from
ステートメントを使用できるのはモジュールファイルのトップレベルのみで、関数内では使用できません。
Python 2.x では、関数内で使用することは可能ですが、警告が表示されます。
パフォーマンス的には、 from
文は import
よりも遅いです。
なぜなら、 import
が行うすべての作業 – インポートされたモジュールのすべての内容を調べて、インポートするために適切な名前を選択する余分なステップを行うからです。
3つ目のimportステートメントである from *
は、インポートモジュールからすべてのトップレベル名をインポートして、インポータークラスで直接使用するために使用されます。
例えば、私たちは使うことができました。
# hiprinter.py
# Name definitions part
multiply = 3
def print_hi():
print("Hi!" * multiply)
# Stand-alone script part
if __name__ == '__main__':
print_hi()
これは、module2.pyファイルからすべての名前(変数と関数)をインポートします。
この方法は、名前が重複する可能性があるため、推奨されません – インポートされた名前は、インポーターモジュールに既に存在する名前を上書きしてしまう可能性があります。
モジュール検索パス
モジュール化されたPythonアプリを書くときに重要な点の1つは、インポートする必要があるモジュールを見つけることです。
標準のPythonライブラリのモジュールはグローバルにアクセスできるように設定されていますが、ディレクトリの境界を越えてユーザー定義モジュールをインポートすると、より複雑になる可能性があります。
Pythonはモジュールを探すために、サーチパスと呼ばれるディレクトリのリストを使用します。
サーチパスは、次のように見つかるディレクトリで構成されています。
- プログラムのホームディレクトリ。トップレベルのスクリプトの場所。ただし、ホームディレクトリは現在の作業ディレクトリと同じとは限りません。
-
-
PYTHONPATH
ディレクトリ。環境変数PYTHONPATH
が設定されている場合、Pythonインタプリタがモジュールを探すためのユーザ定義のディレクトリを連結して定義する。
-
-
- 標準ライブラリのディレクトリ。これらのディレクトリは、Pythonのインストール時に自動的に設定され、常に検索されます。
-
- .pth ファイルにリストされるディレクトリ。このオプションは
PYTHONPATH
の代替手段で、1行ごとにディレクトリを追加していきます。
- .pth ファイルにリストされるディレクトリ。このオプションは
-
- site-packagesディレクトリ。このディレクトリには、すべてのサードパーティ製拡張機能が自動的に追加されます。
PYTHONPATH`は、開発者がカスタムモジュールを検索パスに含めるための最も適切な方法でしょう。
この変数が自分のコンピュータに設定されているかどうか、簡単に確認することができます。
私の場合は次のような結果になりました。
# Terminal window
$ python hiprinter.py
Hi!Hi!Hi!
Windows マシンでこの変数を作成するには、「コントロールパネル -> システム -> 詳細」を使用します。
MacOS やその他の Unix システムでは、 ~/.bashrc または ~/.bash_profile ファイルに次の行を追加するのが最も簡単です(ディレクトリはコロン (“:”) 記号で連結されています)。
# Python interpreter
>> import hiprinter
>> hiprinter.print_hi()
Hi!Hi!Hi!
この方法は、Unixの$PATHにディレクトリを追加するのと非常によく似ています。
プログラムの起動時に検索パスですべてのディレクトリが見つかると、それらはリストに保存され、Pythonの sys.path
で探索することができるようになります。
もちろん、 sys.path
にディレクトリを追加して、モジュールをインポートすれば、プログラムの実行中に検索パスを変更するだけでよい。
いずれにせよ、 PYTHONPATH
と .pth オプションは、より永続的に検索パスを変更することができます。
Python は検索パスの文字列を左から右にスキャンするので、左端のディレクトリにあるモジュールは右端の同じ名前のディレクトリを上書きすることができることを知っておくことは重要です。
モジュールの検索パスは、異なるディレクトリにあるモジュールをインポートするためにのみ必要であることに注意してください。
次の例に示すように、リストの先頭にある空の文字列は、カレントディレクトリのものです。
from module2 import *
結論として、Pythonのプログラムを複数の相互に接続されたモジュールで構成することは、プログラムがよく構造化されている場合、つまり自己完結し、自然にグループ化されたコード部分である場合、かなり簡単です。
より複雑な、あるいはあまりよく構造化されていないプログラムでは、インポートが負担になることがあり、より高度なインポートの話題に取り組む必要があります。
モジュールリロード
キャッシュのおかげで、モジュールはプロセスごとに一度だけインポートすることができます。
Python はインタプリタ型言語なので、 import
または from
ステートメントに到達すると、インポートされたモジュールのコードが実行されます。
同じプロセス(例えば同じPythonインタプリタ)内で後からインポートしても、インポートされたモジュールのコードは再び実行されません。
キャッシュからモジュールを取得するだけです。
以下はその例です。
上記のコードを ‘my_module.py’ で再利用し、Python インタープリターでインポートして、ファイルを修正して、再度インポートしてみましょう。
$ echo $PYTHONPATH
/Users/Code/Projects/:
キャッシュを無効にし、モジュールの再インポートを可能にするために、Pythonは reload
という関数を提供しています。
先ほどと同じPythonのウィンドウで試してみましょう。
export PYTHONPATH=<directory1:directory2:...:directoryn>:$PYTHONPATH".
reload` 関数はモジュールをインプレースで変更します。
つまり、インポートされたモジュールを参照している他のオブジェクトに影響を与えることなく、モジュールを変更します。
この関数は、モジュールの名前とファイルパスも返すことにお気づきでしょう。
この機能は、開発段階において特に有用ですが、大規模なプロジェクトにおいても有用です。
例えば、サーバーとの常時接続を必要とするプログラムでは、動的リロードや開発中のホットロードよりも、アプリケーション全体の再起動の方がはるかにコストがかかるのです。
モジュールパッケージ
モジュール名をインポートするとき、実際にはファイルシステムのどこかに保存されている Python ファイルを読み込みます。
先に述べたように、インポートされたモジュールは、モジュール検索パス (sys.path
) にリストされるディレクトリに存在する必要があります。
Pythonでは、これらの「名前のインポート」以外にも、Pythonファイルを含むディレクトリ全体をモジュールパッケージとして実際にインポートすることができます。
これらのインポートはパッケージインポートとして知られています。
では、どのようにしてモジュールパッケージをインポートするのでしょうか?mod0.py’ モジュールを含む ‘mydir’ という名前のディレクトリと、それぞれ ‘mod1.py’ と ‘mod2.py’ モジュールを含む ‘subdir1’ と ‘subdir2’ という2つのサブディレクトリを作成してみましょう。
ディレクトリ構成は以下のようになります。
import sys
sys.path
['',
'/Users/Code/Projects',
'/Users/Code/Projects/Blogs',
'/Users/Code/anaconda3/lib/python36.zip',
'/Users/Code/anaconda3/lib/python3.6',
'/Users/Code/anaconda3/lib/python3.6/site-packages',
'/Users/Code/anaconda3/lib/python3.6/site-packages/IPython/extensions',
'/Users/Code/.ipython']
これまで説明してきた通常のアプローチは、’mod0.py’, ‘mod1.py’, ‘mod2.py’ をインポートできるように、モジュール検索パス (sys.path
) に ‘mydir’, ‘subdir1’, ‘subdir2’ パスを追加することでした。
これは、モジュールが多くの異なるサブディレクトリにまたがっている場合、大きなオーバーヘッドになることがあります。
とにかく、パッケージのインポートが役に立ちます。
彼らは、フォルダ自体の名前をインポートすることで動作します。
例えば、このコマンドは許可されておらず、InvalidSyntaxエラーになります。
>> import my_module
>> print(my_module.name)
John
# Now modify the 'name' variable in 'my_module.py' into name = 'Jack' and reimport the module
>> import my_module
>> print(my_module.name)
John
正しい方法は、コンテナディレクトリ ‘/Users/Code/Projects/’ のみをモジュール検索パスに設定し(環境変数 PYTHONPATH
に追加するか .pth ファイルにリストする)、ドット構文でモジュールをインポートすることです。
以下は有効なインポートの例です。
>> from imp import reload # Python3.x
>> reload(my_module)
<module '="" 'my_module'="" code="" from="" my_module.py'="" projects="" small_example="" users="">
>> print(my_module.name)
Jack
以前、いくつかのPythonディレクトリには、 _init_JPY.py ファイルがあることに気づかれたかと思います。
これはPython2.xで、ディレクトリがモジュールパッケージであることをPythonに伝えるために必要なものです。
このファイルは通常のPythonファイルでもあり、そのディレクトリがインポートされるたびに実行され、データベースへの接続など、値の初期化に向いています。
とにかく、ほとんどの場合、これらのファイルは空のままです。
Python3.xでは、これらのファイルはオプションであり、必要であれば使用することができます。
次の数行で、Importされたオブジェクトの属性(そのオブジェクトが含まれるディレクトリの名前)が、 _init ____pyで定義された名前になることを説明します。
$ ls -R mydir/
mod0.py subdir1 subdir2
my_dir//subdir1:
mod1.py
my_dir//subdir2:
mod2.py
モジュールパッケージを語る上でもう一つ重要なトピックが相対インポートです。
相対インポートは、パッケージ自体の中でモジュールをインポートするときに便利です。
この場合、Pythonはモジュール検索パスではなく、パッケージのスコープ内でインポートされたモジュールを探します。
便利なケースの1つを例で説明します。
>> import /Users/Code/Projects/mydir/
File "<stdin>", line 1
import /Users/Code/Projects/mydir/
^
SyntaxError: invalid syntax
import mod2` 行は、モジュール検索パスからモジュール ‘mod2’ を探すように Python に指示し、そのため失敗しています。
代わりに、相対インポートがうまく機能します。
次の相対 import 文は、現在のパッケージ (‘mydir/’) の親を示すダブルドット (“..”) を使用しています。
mod2モジュールへの完全な相対パスを作成するには、次のsubdir2が含まれなければなりません。
>> import mydir.mod0
>> import mydir.subdir1.mod1 as mod1
>> from mydir.subdir2.mod2 import print_name # print_name is a name defined within mod2.py
相対インポートは大きなトピックで、本の章全体を占めることができます。
また、Python2.xと3.xのバージョン間でも大きく異なります。
今のところ、便利なケースを1つだけ紹介しましたが、別のブログ記事でもっと紹介する予定です。
また、Python2.xについては2020年にサポートが終了するため、相対インポートのようにPythonのバージョンによって大きな差がある場合は、3.xを中心に考える方がよいでしょう。
まとめ
この記事は、Pythonモジュールの基本(最初のインポートモジュールの作成とインポート)から、もう少し進んだトピック(検索パスの修正、モジュールパッケージ、リロード、いくつかの基本的な相対インポート)、そしてPythonソフトウェアリポジトリPyPIにあなたのPythonパッケージを提出するまでのガイドを意図したものでした。
このトピックにはたくさんの情報があり、この1つの記事ですべてをカバーすることができなかったので、この記事の読了時間内にすべてのステップに取り組んで公式パッケージを提出することはできないかもしれません。
しかし、各ステップは、あなたの学習の道しるべとなる簡単な紹介になるはずです。
参考文献
- Python Documentation
- Mark Lutz, David Ascher, Learning Python, O’Reilly Media, 5 edition, July 2013
- Python Package Index (PyPI)