Pythonのモジュールについてまとめてみたよ
2018-09-04

理解が曖昧だと感じていたためPythonのモジュールについて復習&まとめてみました。

当たり前のことも多いですが、中には知らないこともあるかもしれませんよ!

備考

この記事ではバージョンの指定がなければ2系のことを指します

Basic

Pythonでは拡張子が「py,pyc,pyo」のファイルをモジュールとして読み込むことができます。

備考

バイトコンパイルについて

pyc(pyo)は初回import時にバイトコンパイルされ、同じディレクトリに生成されます。

モジュールファイルの更新時刻に変更がなければ次回以降はpyc(pyo)が読み込まれます。

勘違いされることが多いのですが、バイトコンパイルにより高速化されるのは読込速度であり実行速度ではありません。

バイトコンパイルされたファイル が残っていることにより意図しない挙動を引き起こすことがあるので、 リポジトリには入れないほうがいいです。

-B オプションを指定して python を実行するか、環境変数 PYTHONDONTWRITEBYTECODE=1 が指定されていると pyc ファイルが作られません。

.pycファイルを作成させない方法メモ

モジュールはimportによって読み込まれた時点で実行され、モジュールオブジェクトとしてアクセスできるようになります。 ここが他言語のincludeとは違いますね。

Pythonにおけるグローバルスコープはモジュールに限定されており、 意図的に書き換えない限り実行されたコードが他のモジュールの値に影響を及ぼすことはありません。

また、グローバルスコープに宣言されたオブジェクトはモジュールオブジェクトの属性としてアクセスすることができます。 これをモジュール変数といいます。

このあたりについて詳しく知りたい方は スコープの記事 を参照ください。

Pythonでは名前空間という言葉はあまり聞きませんが、モジュールがその役割果たしていると言えるでしょう。

__name__ という変数にモジュールパスが入り、名前空間を識別できます。

備考

対話モードなどでこの変数を参照すると '__main__' が入っていますよね。 if __name__ == '__main__': という記述をよく見ますが、 これは「スクリプトファイルとして実行した場合」つまり「モジュールとして実行しなかった場合」を判定します。

主に、テストコードやコマンドを書いたりするのに使われます。

この記事で __main__ としている箇所はPythonコンソールで実行していることを表しています。

mod1 mod2
test = 1
test = 2
raise Exception('some exception')
__main__
>>> test = 0
>>> import mod1

>>> # 他のモジュール変数には影響されない
>>> print(test)
0
>>> # mod1のモジュール変数testはmod1の属性となる
>>> print(mod1.test)
1
>>> import mod2
Traceback (most recent call last):
  File "", line 1, in
  File "mod2.py", line 2, in
    raise Exception('some exception')
Exception: some exception

>>> # 最後まで無事に実行されなければ変数として宣言されない
>>> mod2
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'mod2' is not defined

Package

パッケージはディレクトリを使ってモジュールを構造化する仕組みです。

パッケージはモジュールの入れ物であると同時に自身もモジュールなのです。 しかしディレクトリに処理を書くことはできないため代わりに__init__.pyに処理を記述します。

単純に構造化するだけなら内容は空でも構いませんが__init__.pyはパッケージモジュールの実体であるため、 存在しないとそのディレクトリはパッケージとして認識されません(Python2系以下)。

valid_package.py valid_package/__init__.py valid_package/mod.py
print('valid_package.py file')
print(__name__)
print(__name__)
__main__
>>> # モジュールパスに記述したすべてのモジュールが実行される
>>> import valid_package.mod
valid_package
valid_package.mod

>>> # 単一のモジュールよりもパッケージが優先される
>>> import valid_package

>>> # __init__.pyが存在しないディレクトリ(invalid_package)はimportできない
>>> import invalid_package
Traceback (most recent call last):
  File "", line 1, in
ImportError: No module named invalid_package

Singleton

importはモジュールサーチパスから該当のモジュールを探し出し、実行するという比較的重い処理です。 そのためimportしたモジュールはキャッシュされプロセス内で共有します。

2回目以降のimportではキャッシュされたモジュールオブジェクトが参照されるというわけです。

どこから参照しても同じ値が得られるということは書き換えると同一プロセスのすべてが影響を受けるということになります。

callee.py caller.py
print(__name__)
test = 1
import callee
__main__
>>> import callee
callee
>>> # 2回目以降はキャッシュが使用される
>>> import callee

>>> # 別モジュールからの読み込みでもキャッシュが使用される
>>> import caller

reload

警告

Python3 では ビルトインスコープの reload がなくなり、 importlib ライブラリに移動しました。 (imp ライブラリは3.3まで)

この記事を試す場合、 Python3 以上をお使いの方は from importlib import reload とした上で利用ください。

reload はキャッシュ済みのモジュールを再読込します。

import は文ですが reload は関数のため引数にはモジュールオブジェクトを指定する必要があります。 変数名は関係ないということです。

キャッシュを管理しているのは sys.modules というモジュールパス(文字列)をキーとする辞書オブジェクトです。

お察しの通り、 sys.modules から該当モジュールパスを削除することで擬似的なreloadを実現できます。

しかし importreload と違いオブジェクトを新たに生成する(アドレスが変わる)ため、 シングルトンの仕組みが崩れます。

複数箇所からモジュールが参照されている場合には注意が必要です。

>>> # 現状では以下のモジュールが読み込まれている(callee.pyとcaller.pyも)
>>> import sys
>>> sys.modules.keys()
['copy_reg', 'encodings', 'site', '__builtin__', '__main__', 'encodings.encodings', 'abc', 'posixpath', '_weakrefset', 'errno', 'encodings.codecs', '_abcoll', 'types', '_codecs', '_warnings', 'genericpath', 'stat', 'zipimport', 'encodings.__builtin__', 'warnings', 'UserDict', 'encodings.utf_8', 'sys', 'codecs', 'readline', 'os.path', 'sitecustomize', 'callee', 'signal', 'caller', 'linecache', 'posix', 'encodings.aliases', 'exceptions', 'os', '_weakref']

>>> print(callee.test)
1

>>> # このタイミングでcallee.pyファイルのtestの値を「2」に書き換える
>>> reload(callee)
callee


>>> # リロードすると属性値が更新される
>>> print(callee.test)
2
>>> print(caller.callee.test)
2
>>> # アドレスが同じためモジュール変数の値も等しい
>>> callee is caller.callee
True


>>> # 再度testの値を更新(3)
>>> # 擬似的なreload
>>> del sys.modules['callee']
>>> import callee
callee
>>> print(callee.test)
3
>>> print(caller.callee.test)
2
>>> # アドレスが更新されてしまうためモジュール変数の値に不整合が生じる
>>> callee is caller.callee
False

Search path

モジュールはカレントディレクトリ、PYTHONPATHの順に検索されます。

PYTHONPATH

カレントディレクトリ以外に参照されるディレクトリを列挙したのが PYTHONPATH という環境変数です。

ビルトインモジュールやサードパーティモジュールはPYTHONPATHに登録されたディレクトリに配置されています。

PYTHONPATHは sys.path で参照できます。

datetime.py /tmp/datetime.py
print('[current] dummy %s' % __name__)
print('[tmp] dummy %s' % __name__)
__main__
>>> import sys
>>> sys.path
['', '/usr/local/lib/python2.7/dist-packages/pip-1.5.6-py2.7.egg', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-x86_64-linux-gnu', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PILcompat', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']

>>> # 先頭「''(カレントディレクトリ)」のモジュールが優先して読み込まれる
>>> import datetime
[current] dummy datetime

>>> del sys.path[0]
>>> # 通常のdatetimeが読まれるようになった
>>> reload(datetime)


>>> # /tmp/のモジュールを最優先にしてみる
>>> sys.path.insert(0, '/tmp/')
>>> reload(datetime)
[tmp] dummy datetime


>>> # PYTHONPATHを空にすると
>>> sys.path = []
>>> reload(datetime)
Traceback (most recent call last):
  File "", line 1, in
ImportError: No module named datetime

from

from は import の起点を指定する予約語です。 import文には基点からのモジュールパスを指定すれば良いため余計なキータイプを減らすことができます。

as でも同じようなことは可能ですが、fromで指定した基点モジュールパスにはアクセスできません(でも実行はされる)。

a/__init__.py a/aa.py
print(__name__)
print(__name__)
__main__
>>> from a import aa
a
a.aa
# fromに指定したモジュールにはアクセスできない
>>> a
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'a' is not defined

ほかにもfromを指定したimportは単体のimportとは異なる点がいくつかあります。

Module variable

単体の(fromを使わない) importによって指定できるのはモジュールのみですが、 from を指定することで モジュール変数 (グローバルスコープに属する変数) を import することができます。

属性名と同じ名前のモジュールが存在する場合は属性名が優先されます。 import というよりは定義と言ったほうが正しいかもしれません。

定義された属性は元のモジュールとの関係が断ち切られるため reload によって値の更新がされません。

a/b/__init__.py a/b/c/__init__.py a/b/c/e.py
print(__name__)
test = 3
_test = 4
print(__name__)
d = 'd'
e = 'e'
print(__name__)
__main__
>>> from a.b.c import e
a
a.b
a.b.c
>>> # e.pyよりもc.e属性が優先される
>>> print(e)
e
>>> import a.b.c as c
>>> print(c.e)
e
>>> # ここでc.eの値を更新(ee)
>>> reload(c)
>>> print(c.e)
ee
>>> # eはcを更新しても変更されない
>>> print(e)
e

fromによる属性のimportは、モジュールの属性を代入文によって再定義していると考えるとわかりやすいかもしれません。

はじぱい にも同じような説明がありますが from mod import name1, name2 は以下と等価です。

>>> import mod
>>> name1 = mod.name1
>>> name2 = mod.name2
>>> del mod

また、 import * とすることで変数名が _ で始まらないすべての属性を取り込むこともできます。

ただ、どの属性が取り込まれたかわかりにくいのでむやみに利用すべきではありません。

>>> from a.b import *
>>> print(test)
3
>>> print(_test)
Traceback (most recent call last):
  File "", line 1, in
NameError: name '_test' is not defined

Relative import

相対インポート は同一パッケージを基点としたモジュールのインポートです。

Python2系では同一パッケージのモジュールが最優先されるため、 同一パッケージを対象としたインポートではメリットがありませんが、3系では同一パッケージのモジュールは読み込まれないため相対インポートを利用する必要があります。

互換性を意識するのであれば2系でも明示的に相対インポート指定をするべきでしょう。

callee.py a/b/callee.py a/b/caller.py
test = __name__
test = __name__
from callee import test
print(test)

from .callee import test
print(test)
python2 console __main__
>>> import a.b.caller
a
a.b
a.b.callee
a.b.callee
python3 console __main__
>>> import a.b.caller
a
a.b
callee
a.b.callee

上記の結果を見てわかるとおり、同一パッケージに重複した名前のモジュールが存在すると 意図したモジュールをimportできないという問題があります。

Python3系ではデフォルトで同一パッケージは参照されないため問題は発生しません。

この参照ルールをPython2系に適用したいとき absolute_import を使います。

下記をa/b/caller.pyの先頭に記述

from __future__ import absolute_import

再度Python2系にてimportしてみる

>>> # python2 console
>>> import a.b.caller
a
a.b
callee
a.b.callee

3系と同じ結果となりましたね。

相対インポートの2,3系に共通するメリットとして上位パッケージを参照できるという点です。

1つ上のパッケージに遡るときは .. 、2つ上の時は ... と言った具合に . の数を増やすことで複数のパッケージを遡ることができます。

ただし、カレントディレクトリ以上を参照することはできません。

a/b/back.py
# coding: utf-8
# a.__init__.testを参照する(1)
from .. import test
print(test)
# カレントディレクトリ以上は参照できないためエラーとなる
from ... import test
__main__
>>> import a.b.back
a
a.b
1
Traceback (most recent call last):
  File "", line 1, in
  File "a/b/back.py", line 4, in
    from ... import test
ValueError: Attempted relative import beyond toplevel package

相対インポートが使用できるのはパッケージ内のモジュールのみです。

単体のスクリプトファイルとして実行したり、 インタラクティブモードにて使用するとパッケージに所属していることにならないためエラーが発生します。

>>> from . import *
Traceback (most recent call last):
  File "", line 1, in
ValueError: Attempted relative import in non-package

from import syntax restriction

from を使った import には モジュールパス を指定できません。

簡単に言うとimport以降には . を含めることができないということです。

>>> from a.b import c.d
  File "", line 1
    from a.b import c.d
                     ^
SyntaxError: invalid syntax

__import__

通常モジュールはimport文によって読み込まれますが、読み込むモジュールを動的に変えたいこともあります。 対象のモジュールが少ないうちはif文だけで何とかなりますがあまり現実的ではありません。

__import__ 関数はインポートしたいモジュールのパスを文字列として受け取るため、柔軟な指定ができます。

読み込んだモジュールオブジェクトを返却します。自動的に宣言されないため注意しましょう。

>>> a = 'sys'
# モジュールオブジェクトが返却される
>>> sys1 = __import__(a)
# importしたのと同じ
>>> import sys as sys2
>>> print(sys1 is sys2)
True

備考

python3 では importlib.import_module を使うことが推奨されています。

Circulation import

複数のモジュールがお互いのモジュールを必要とし合っている状態を 循環インポート と呼びます。

読み込んだタイミングで必要としている属性が宣言されていなければエラーが発生します。

x.py y.py
print(0)

test1 = 1

import y

test2 = 2
import x

print(x.test1) # test1は宣言されているが
print(x.test2) # test2は未宣言のためAttributeError
__main__
>>> import x
0
1
Traceback (most recent call last):
  File "", line 1, in
  File "x.py", line 5, in
    import y
  File "y.py", line 4, in
    print(x.test2)
AttributeError: 'module' object has no attribute 'test2'