5. インポートシステム

ある 1 つの module にある Python コードから他のモジュールをインポート (importing) することで、そこにあるコードへアクセスできるようになります。 import 文はインポート機構を動かす最も一般的な方法ですが、それが唯一の方法ではありません。 importlib.import_module() や組み込みの __import__() といった関数を使っても、インポート機構を動かすことができます。

import 文は 2 つの処理を連続して行っています; ある名前のモジュールを探し、その検索結果をローカルスコープの名前に束縛します。 import 文の検索処理は、適切な引数で __import__() 関数を呼び出すこととして定義されています。 __import__() の戻り値は import 文の名前束縛処理の実行で使われます。名前束縛処理の厳密な詳細は import 文を参照してください。

__import__() を直接呼び出すとモジュールの検索のみが行われ、見つかった場合、モジュールの作成処理が行われます。親パッケージのインポートや (sys.modules を含む) 様々なキャッシュの更新などの副作用は起きるかもしれませんが、 import 文のみが名前束縛処理を行います。

import 文の一部として __import__() を呼び出したときは標準の組み込みの __import__() が呼ばれます。インポートシステムを起動する他の機構 (たとえば importlib.import_module() など) は、 __import__() を覆すことを選んで固有のインポートセマンティクスを実装するための方法を用いることがあります。

モジュールが初めてインポートされるとき、 Python はそのモジュールを検索し、見付かった場合、モジュールオブジェクトを作成し、初期化します [1] 。その名前のモジュールが見付からなかった場合、 ModuleNotFoundError が送出されます。 Python には、インポート機構が実行されたときに名前からモジュールを検索する様々な戦略が実装されています。これらの戦略は、これ以降の節で解説される様々なフックを使って、修正したり拡張したりできます。

バージョン 3.3 で変更: インポートシステムが PEP 302 の第 2 フェーズの完全な実装へ更新されました。もはや暗黙的なインポート機構はありません - インポート機構全体は sys.meta_path を通して公開されています。加えて、ネイティブの名前空間パッケージのサポートは実装されています (PEP 420 を参照) 。

5.1. importlib

importlib モジュールはインポート機構とやり取りするための便利な API を提供します。例えば importlib.import_module() は、インポート機構を実行するための組み込みの __import__() よりもシンプルで推奨される API を提供します。より詳細なことは importlib ライブラリのドキュメントを参照してください。

5.2. パッケージ

Python にはモジュールオブジェクトの種類は 1 種類しかなく、 Python 、 C 、それ以外のもののどれで実装されているかに関係なく、すべてのモジュールはこの種類になります。モジュールの組織化を助け、名前階層を提供するために、 Python には パッケージ という概念があります。

パッケージはファイルシステムのディレクトリ、モジュールはディレクトリにあるファイルと考えることができますが、パッケージやモジュールはファイルシステムから生まれる必要はないので、この比喩を額面通りに受け取ってはいけません。この文書の目的のために、ディレクトリとファイルという便利な比喩を使うことにします。ファイルシステムのディレクトリのように、パッケージは階層構造を成し、通常のモジュールだけでなく、サブパッケージを含むこともあります。

すべてのパッケージはモジュールですが、すべてのモジュールがパッケージとは限らないことを心に留めておくのが重要です。もしくは他の言い方をすると、パッケージは単なる特別な種類のモジュールであると言えます。特に、__path__ 属性を持つ任意のモジュールはパッケージと見なされます。

すべてのモジュールには名前があります。サブパッケージ名は、 Python の標準の属性アクセスの構文に似て、親パッケージ名とドットで区切られています。したがって、 sys と呼ばれるモジュールや email と呼ばれるパッケージを見掛けることがあるでしょう。その中には email.mime と呼ばれるサブパッケージと、そのサブパッケージの中に email.mime.text と呼ばれるモジュールがあります。

5.2.1. 通常のパッケージ

Python では、 通常のパッケージ名前空間パッケージ の 2 種類のパッケージが定義されています。通常のパッケージは Python 3.2 以前から存在する伝統的なパッケージです。典型的な通常のパッケージは __init__.py ファイルを含むディレクトリとして実装されます。通常のパッケージがインポートされたとき、この __init__.py ファイルが暗黙的に実行され、それで定義しているオブジェクトがパッケージ名前空間にある名前に束縛されます。 __init__.py ファイルは、他のモジュールに書ける Python コードと同じものを含むことができ、モジュールがインポートされたときに Python はモジュールに属性を追加したりします。

例えば、以下のようなファイルシステム配置は、3 つのサブパッケージを持つ最上位の parent パッケージを定義します:

parent/
    __init__.py
    one/
        __init__.py
    two/
        __init__.py
    three/
        __init__.py

parent.one をインポートすると暗黙的に parent/__init__.pyparent/one/__init__.py が実行されます。その後に parent.two もしくは parent.three をインポートすると、それぞれ parent/two/__init__.pyparent/three/__init__.py が実行されます。

5.2.2. 名前空間パッケージ

名前空間パッケージは様々な ポーション を寄せ集めたもので、それぞれのポーションはサブパッケージを親パッケージに提供します。ポーションはファイルシステムの別々の場所にあることもあります。ポーションは、 zip ファイルの中やネットワーク上や、それ以外のインポート時に Python が探すどこかの場所で見つかることもあります。名前空間パッケージはファイルシステム上のオブジェクトに対応することもあるし、そうでないこともあります; それらは実際の実体のない仮想モジュールです。

名前空間パッケージは、 __path__ 属性に普通のリストは使いません。その代わりに独自の iterable 型を使っていて、ポーションの親パッケージのパス (もしくは最上位パッケージのための sys.path) が変わった場合、そのパッケージでの次のインポートの際に、新たに自動でパッケージポーションを検索します。

名前空間パッケージには parent/__init__.py ファイルはありません。それどころか、異なるポーションがそれぞれ提供する複数の parent ディレクトリがインポート検索の際に見つかることもあります。したがって parent/one は物理的に parent/two の隣りにあるとは限りません。その場合、そのパッケージかサブパッケージのうち 1 つがインポートされたとき、Python は最上位の parent パッケージのための名前空間パッケージを作成します。

名前空間パッケージの仕様については PEP 420 も参照してください。

5.3. 検索

検索を始めるためには、 Python はインポートされるモジュール (もしくはパッケージですが、ここでの議論の目的においてはささいな違いです) の 完全修飾 名を必要とします。この名前は、 import 文の様々な引数や importlib.import_module() および __import__() 関数のパラメータから得られます。

この名前はインポート検索の様々なフェーズで使われ、これは例えば foo.bar.baz のようなドットで区切られたサブモジュールへのパスだったりします。この場合、 Python は最初に foo を、次に foo.bar 、そして最後に foo.bar.baz をインポートしようとします。中間のいずれかのインポートに失敗した場合は、 ModuleNotFoundError が送出されます。

5.3.1. モジュールキャッシュ

インポート検索で最初に調べる場所は sys.modules です。このマッピングは、中間のパスを含む、これまでにインポートされたすべてのモジュールのキャッシュを提供します。なので foo.bar.baz がインポート済みの場合、 sys.modulesfoofoo.barfoo.bar.baz のエントリーを含みます。それぞれのキーはその値として対応するモジュールオブジェクトを持ちます。

インポートではモジュール名は sys.modules から探され、存在した場合は、対応する値がインポートされるべきモジュールであり、この処理は完了します。しかし値が None だった場合、 ModuleNotFoundError が送出されます。モジュール名が見付からなかった場合は、 Python はモジュールの検索を続けます。

sys.modules は書き込み可能です。キーの削除は対応するモジュールを破壊しない (他のモジュールがそのモジュールへの参照を持っている) かもしれませんが、指定されたモジュールのキャッシュされたエントリーを無効にし、それが次にインポートされたとき Python にそのモジュールを改めて検索させることになります。キーを None に対応付けることもできますが、次にそのモジュールがインポートされるときに ModuleNotFoundError となってしまいます。

たとえモジュールオブジェクトへの参照を保持しておいて、 sys.modules にキャッシュされたエントリーを無効にし、その指定したモジュールを再インポートしたとしても、 2 つのモジュールオブジェクトは同じでは ない ことに注意してください。それとは対照的に、 importlib.reload()同じ モジュールオブジェクトを再利用し、モジュールのコードを再実行することで単にモジュールの内容を再初期化するだけです。

5.3.2. ファインダーとローダー

sys.modules に指定されたモジュールが見つからなかった場合は、 Python のインポートプロトコルが起動され、モジュールを見つけロードします。このプロトコルは 2 つの概念的なオブジェクト、 ファインダーローダー から成ります。ファインダーの仕事は、知っている戦略を使って指定されたモジュールを見つけられるかどうか判断することです。両方のインターフェースを実装しているオブジェクトは インポーター と呼ばれます - インポーターは要求されたモジュールがロードできると分かったとき、自分自身を返します。

Python にはデフォルトのファインダーとインポーターがいくつかあります。 1 つ目のものは組み込みモジュールの見つけ方を知っていて、 2 つ目のものは凍結されたモジュール (訳注: freeze ツールで処理されたモジュールのこと。 プログラミング FAQ の「どうしたら Python スクリプトからスタンドアロンバイナリを作れますか?」の項目を参照) の見つけ方を知っています。 3 つ目のものは インポートパス からモジュールを探します。 インポートパス はファイルシステムのパスや zip ファイルの位置を示すリストです。このリストは、 URL で特定できるもののような、位置を示すことのできる任意のリソースの検索にまで拡張することもできます。

インポート機構は拡張可能なので、モジュール検索の範囲とスコープを拡張するために新しいファインダーを付け加えることができます。

ファインダーは実際にはモジュールをロードしません。指定されたモジュールが見つかった場合、ファインダーは module spec (モジュール仕様)、すなわちモジュールのインポート関連の情報をカプセル化したものを返します。モジュールのロード時にインポート機構はそれを利用します。

次の節では、インポート機構を拡張するための新しいファインダーやローダーの作成と登録を含め、ファインダーとローダーのプロトコルについてより詳しく解説します。

バージョン 3.4 で変更: Python の以前のバージョンでは、ファインダーは直接 ローダー を返していましたが、現在はローダーを 含む モジュール仕様を返します。ローダーはインポート中はまだ使われていますが、責任は減りました。

5.3.3. インポートフック

インポート機構は拡張可能なように設計されています; その主となる仕組みは インポートフック です。インポートフックには 2 種類あります: メタフックインポートパスフック です。

メタフックはインポート処理の最初、 sys.modules キャッシュの検索以外のインポート処理より前に呼び出されます。これにより、 sys.path の処理や凍結されたモジュールや組み込みのモジュールでさえも、メタフックで上書きすることができます。メタフックは以下で解説するように、 sys.meta_path に新しいファインダーオブジェクトを追加することで登録されます。

インポートパスフックは、 sys.path (もしくは package.__path__) の処理の一部として、対応するパス要素を取り扱うところで呼び出されます。インポートパスフックは以下で解説するように、新しい呼び出し可能オブジェクトを sys.path_hooks に追加することで登録されます。

5.3.4. メタパス

指定されたモジュールが sys.modules に見つからなかったとき、 Python は次にメタパス・ファインダー・オブジェクトが格納されている sys.meta_path を検索します。指定されたモジュールを扱うことができるかどうかを調べるために、各ファインダーに問い合わせを行います。メタパス・ファインダーには、名前とインポートパスと (オプションの) ターゲットモジュールの 3 つの引数を取る find_spec() という名前のメソッドが実装されていなければいけません。メタパス・ファインダーでは、指定されたモジュールを扱えるかどうかを判定するための戦略は任意のものを使って構いません。

meta path finder が指定されたモジュールの扱い方を知っている場合は、ファインダは spec オブジェクトを返します。指定されたモジュールを扱えない場合は None を返します。 sys.meta_path に対する処理が spec を返さずにリストの末尾に到達してしまった場合は、 ModuleNotFoundError を送出します。その他の送出された例外はそのまま呼び出し元に伝播され、インポート処理を異常終了させます。

メタパス・ファインダーの find_spec() メソッドは 2 つまたは 3 つの引数を渡して呼び出します。1 つ目の引数はインポートされるモジュールの完全修飾名で、例えば foo.bar.baz などです。2 つ目の引数はモジュールの検索で使われるパスです。最上位のモジュールでは 2 つ目の引数は None にしますが、サブモジュールやサブパッケージでは 2 つ目の引数は親パッケージの __path__ 属性の値です。適切な __path__ 属性にアクセスできなかった場合は、 ModuleNotFoundError が送出されます。3 つ目の引数は、あとでロードされるターゲットとなる既存のモジュールオブジェクトです。インポートシステムはリロードの間だけターゲットモジュール をセットします。

メタパスは、1 回のインポート要求で複数回走査される可能性があります。例えば、関係するモジュールがどれもまだキャッシュされていないとしたときに foo.bar.baz をインポートすると、最初は各メタパス・ファインダー (mpf) に対して mpf.find_spec("foo", None, None) を呼び出して、最上位のインポート処理を行います。foo がインポートされた後に、mpf.find_spec("foo.bar", foo.__path__, None) を呼び出していく 2 回目のメタパスの走査が行われ、foo.bar がインポートされます。foo.bar のインポートまで行われたら、最後の走査で mpf.find_spec("foo.bar.baz", foo.bar.__path__, None) を呼び出していきます。

あるメタパス・ファインダーは最上位のインポートのみサポートしています。これらのインポーターは、2 つ目の引数に None 以外のものが渡されたとき、常に None を返します。

Python のデフォルトの sys.meta_path は 3 つのパスファインダーを持っています。組み込みモジュールのインポートの方法を知っているもの、凍結されたモジュールのインポートの方法を知っているもの、 インポートパス からのモジュールのインポートの方法を知っているもの (つまり パスベース・ファインダー) があります。

バージョン 3.4 で変更: メタパス・ファインダーの find_spec() メソッドは find_module() を置き換えました。 find_module() メソッドは deprecated です。それは今でも変更なしに動きますが、インポート機構はファインダーが find_spec() を実装していない場合にのみそれを試します。

5.4. ロード

モジュール仕様が見つかった場合、インポート機構はモジュールをロードする時にそれ (およびそれに含まれるローダー) を使います。これは、インポートのロード部分で起こることの近似です:

module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
    # It is assumed 'exec_module' will also be defined on the loader.
    module = spec.loader.create_module(spec)
if module is None:
    module = ModuleType(spec.name)
# The import-related module attributes get set here:
_init_module_attrs(spec, module)

if spec.loader is None:
    if spec.submodule_search_locations is not None:
        # namespace package
        sys.modules[spec.name] = module
    else:
        # unsupported
        raise ImportError
elif not hasattr(spec.loader, 'exec_module'):
    module = spec.loader.load_module(spec.name)
    # Set __loader__ and __package__ if missing.
else:
    sys.modules[spec.name] = module
    try:
        spec.loader.exec_module(module)
    except BaseException:
        try:
            del sys.modules[spec.name]
        except KeyError:
            pass
        raise
return sys.modules[spec.name]

以下の詳細に注意してください:

  • sys.modules の中に与えられた名前を持つ既存のモジュールオブジェクトがあるなら、 import は既にそれを返しているでしょう。
  • モジュールは、ローダーがモジュールコードを実行する前に sys.modules に存在しています。 モジュールコードが (直接的または間接的に) 自分自身をインポートする可能性があるので、これは重要です; モジュールを sys.modules に追加することで、最悪のケースでは無限の再帰が、そして最良のケースでは複数回のロードが、前もって防止されます。
  • ロード処理に失敗した場合、その失敗したモジュールは -- そして、そのモジュールだけが -- sys.modules から取り除かれます。 sys.modules キャッシュに既に含まれていたすべてのモジュールと、副作用としてロードに成功したすべてのモジュールは、常にキャッシュに残されます。これはリロードとは対照的で、リロードの場合は失敗したモジュールも sys.modules に残されます。
  • 後のセクション で要約されるように、モジュールが作られてから実行されるまでの間にインポート機構はインポート関連のモジュール属性を設定します (上記擬似コード例の "_init_module_attrs")。
  • モジュール実行はモジュールの名前空間が構築されるロードの重要な瞬間です。実行はローダーに完全に委任され、ローダーは何をどのように構築するかを決定することになります。
  • ロードの間に作成されて exec_module() に渡されたモジュールは、インポートの終わりに返されるものとは異なるかもしれません [2]

バージョン 3.4 で変更: インポートシステムはローダーの定型的な責任を引き継ぎました。これらは以前は importlib.abc.Loader.load_module() メソッドによって実行されました。

5.4.1. ローダー

モジュールローダーは、ロードの重要な機能であるモジュール実行機能を提供します。インポート機構は、実行しようとするモジュールオブジェクトを単一の引数として importlib.abc.Loader.exec_module() メソッドを呼び出します。 importlib.abc.Loader.exec_module() から返された任意の値は無視されます。

ローダーは以下の仕様を満たしていなければいけません:

  • モジュールが (組み込みモジュールや動的に読み込まれる拡張モジュールではなくて) Python モジュールだった場合、ローダーはモジュールのグローバル名前空間 (module.__dict__) で、モジュールのコードを実行すべきです。
  • exec_module() の呼び出し中に ImportError 以外の例外が送出され、伝播されてきたとしても、モジュールをロードできない場合は ImportError を送出すべきです。

多くの場合、ファインダーとローダーは同じオブジェクトで構いません; そのような場合では find_spec() メソッドは単に self (訳注: オブジェクト自身) を返すだけです。

モジュールローダーは、 create_module() メソッドを実装することでロード中にモジュールオブジェクトを作成することを選択できます。このメソッドは、モジュール仕様を引数に取って、ロード中に使う新しいモジュールオブジェクトを返します。 create_module() はモジュールオブジェクトに属性を設定する必要はありません。もしこのメソッドが None を返すなら、インポート機構は新しいモジュールを自身で作成します。

バージョン 3.4 で追加: ローダーの create_module() メソッド。

バージョン 3.4 で変更: load_module() メソッドは exec_module() によって置き換えられ、インポート機構がロードのすべての定型責任を引き受けました。

既存のローダーとの互換性のため、もしローダーに load_module() メソッドが存在し、かつローダーが exec_module() を実装していなければ、インポート機構はローダーの load_module() メソッドを使います。しかし、 load_module() は deprecated であり、ローダーは代わりに exec_module() を実装すべきです。

load_module() メソッドは、モジュールを実行することに加えて上記で説明されたすべての定型的なロード機能を実施しなければなりません。同じ制約が適用されます。以下は追加の明確化です:

  • sys.modules に与えられた名前のモジュールが存在している場合、ローダーはその既存のモジュールを使わなければいけません。 (そうしないと importlib.reload() は正しく動かないでしょう。) 指定されたモジュールが sys.modules に存在しない場合、ローダーは新しいモジュールオブジェクトを作成し、 sys.modules に追加しなければいけません。
  • 無限の再帰または複数回のロードを防止するために、ローダーがモジュールコードを実行する前にモジュールは sys.modules に存在しなければなりません (must)。
  • ロード処理に失敗した場合、ローダーは sys.modules に追加したモジュールを取り除かなければいけませんが、それはロードに失敗したモジュール のみ を、そのモジュールがローダー自身に明示的にロードされた場合に限り、除去しなければなりません。

バージョン 3.5 で変更: exec_module() が定義されていて create_module() が定義されていない場合、 DeprecationWarning が送出されるようになりました。

バージョン 3.6 で変更: exec_module() が定義されていて create_module() が定義されていない場合、 ImportError が送出されるようになりました。

5.4.2. サブモジュール

サブモジュールをロードするのにどのようなメカニズム (例えば、 importlib API 、 import または import-from ステートメント、またはビルトイン関数の __import__) が使われた場合でも、バインディングはサブモジュールオブジェクトを親モジュールの名前空間に配置します。例えば、もしパッケージ spam がサブモジュール foo を持っていた場合、 spam.foo をインポートした後は spam は値がサブモジュールに束縛された属性 foo を持ちます。以下のディレクトリ構造を持っているとしましょう:

spam/
    __init__.py
    foo.py
    bar.py

そして spam/__init__.py は以下のようになっているとします:

from .foo import Foo
from .bar import Bar

このとき、以下を実行することにより spam モジュールの中に foobar に束縛された名前が置かれます:

>>> import spam
>>> spam.foo
<module 'spam.foo' from '/tmp/imports/spam/foo.py'>
>>> spam.bar
<module 'spam.bar' from '/tmp/imports/spam/bar.py'>

Python の慣れ親しんだ名前束縛ルールからするとこれは驚きかもしれませんが、それは実際インポートシステムの基本的な機能です。不変に保たなければならないのは (上記のインポートの後などで) sys.modules['spam']sys.modules['spam.foo'] が存在する場合、後者が前者の foo 属性として存在しなければならないということです。

5.4.3. モジュール仕様

インポート機構は、インポートの間 (特にロードの前) に、個々のモジュールについてのさまざまな情報を扱います。情報のほとんどはすべてのモジュールで共通です。モジュール仕様の目的は、このインポート関連の情報をモジュールの単位でカプセル化することです。

インポートの際にモジュール仕様を使うことは、インポートシステムコンポーネント間、例えばモジュール仕様を作成するファインダーとそれを実行するローダーの間で状態を転送することを可能にします。最も重要なのは、それによってインポート機構がロードの定型的な作業を実行できるようになるということです。これに対して、モジュール仕様なしではローダがその責任を担っていました。

モジュール仕様は、モジュールオブジェクトの __spec__ 属性として公開されます。 モジュール仕様の内容の詳細については ModuleSpec を参照してください。

バージョン 3.4 で追加.

5.4.5. module.__path__

定義より、モジュールに __path__ 属性があれば、そのモジュールはパッケージとなります。

パッケージの __path__ 属性は、そのサブパッケージのインポート中に使われます。インポート機構の内部では、それは sys.path とほとんど同じように機能します。つまり、インポート中にモジュールを探す場所のリストを提供します。しかし、一般的に __path__sys.path よりも制約が強いです。

__path__ は文字列の iterable でなければいけませんが、空でも構いません。 sys.path と同じ規則がパッケージの __path__ にも適用され、パッケージの __path__ を走査するときに (後で解説する) sys.path_hooks が考慮に入れられます。

パッケージの __init__.py ファイルは、パッケージの __path__ 属性を設定もしくは変更することがあり、これが PEP 420 以前の名前空間パッケージの典型的な実装方法でした。 PEP 420 の採択により、もはや名前空間パッケージは、 __path__ を操作するコードだけを含む __init__.py ファイルを提供する必要がなくなりました;インポート機構は、名前空間パッケージに対し自動的に適切な __path__ をセットします。

5.4.6. モジュールの repr

デフォルトでは、すべてのモジュールは利用可能な repr を持っています。ただしこれは上位で設定された属性に依存しており、モジュール仕様によってモジュールオブジェクトの repr をより明示的に制御することができます。

もしモジュールが仕様 (__spec__) を持っていれば、インポート機構はそこから repr を生成しようとします。もしそれが失敗するか、または仕様が存在しなければ、インポートシステムはモジュールで入手可能なあらゆる情報を使ってデフォルトの repr を構築します。それは module.__name__, module.__file__, module.__loader__ を (足りない情報についてはデフォルト値を使って補いながら) repr への入力として使おうと試みます。

これが使われている正確な規則です:

  • モジュールが __spec__ 属性を持っていれば、仕様に含まれる情報が repr を生成するために使われます。 "name", "loader", "origin", "has_location" 属性が参照されます。
  • モジュールに __file__ 属性がある場合は、モジュールの repr の一部として使われます。
  • モジュールに __file__ はないが __loader__ があり、その値が None ではない場合は、ローダーの repr がモジュールの repr の一部として使われます。
  • そうでなければ、単にモジュールの __name__ を repr の中で使います。

バージョン 3.4 で変更: loader.module_repr() の使用は deprecated です。インポート機構によりモジュール仕様がモジュール repr を生成するために使用されるようになりました。

Python 3.3 との後方互換性のために、ローダーの module_repr() メソッドが定義されていたら、モジュール repr を生成するために上記のいずれかのアプローチを試す前にそのメソッドが呼ばれます。ただし、このメソッドは deprecated です。

5.5. パスベース・ファインダー

上で触れた通り、 Python にはいくつかのデフォルトのメタパス・ファインダーが備わっています。そのうちの 1 つは パスベース・ファインダー (PathFinder) と呼ばれ、 パスエントリ のリストである インポートパス を検索します。それぞれのパスエントリは、モジュールを探す場所を指しています。

パスベース・ファインダー自体は何かのインポート方法を知っているわけではありません。その代わりに、個々のパスエントリを走査し、それぞれに特定の種類のパスの扱いを知っているパスエントリ・ファインダーを関連付けます。

デフォルトのパスエントリ・ファインダーは、ファイルシステム上のモジュールを見つけるためのすべてのセマンティクスを実装しています。それは Python ソースコード (.py ファイル) 、Python バイトコード (.pyc ファイル) 、共有ライブラリ (例えば .so ファイル) などの特別なファイルタイプを処理します。標準ライブラリの zipimport モジュールによってサポートされる場合は、デフォルトのパスエントリ・ファインダーは (共有ライブラリ以外の) すべてのファイルタイプの zip ファイルからのロードも扱います。

パスエントリはファイルシステム上の場所に限定される必要はありません。URL やデータベースクエリやその他文字列で指定できる場所を参照することも可能です。

パスベース・ファインダーにはフックやプロトコルを追加することができ、それによって検索可能なパスエントリの種類を拡張し、カスタマイズすることができます。例えば、ネットワーク上の URL をパスエントリとしてサポートしたい場合、 web 上のモジュールを見つけるために HTTP の取り扱い方を実装したフックを書くことができます。この (呼び出し可能オブジェクトである) フックは、下で解説するプロトコルをサポートする パスエントリ・ファインダー を返します。このプロトコルは web からモジュールのローダーを取得するのに使われます。

警告の言葉: この節と前の節の両方で ファインダー という言葉が、 メタパス・ファインダーパスエントリ・ファインダー という用語で区別されて使われています。これら 2 種類のファインダーは非常に似ており、似たプロトコルをサポートし、インポート処理で同じように機能しますが、微妙に異なっているのを心に留めておくのは重要です。特に、メタパス・ファインダーはインポート処理の開始時、 sys.meta_path の走査が動くときに動作します。

それとは対照的に、パスエントリ・ファインダーはある意味でパスベース・ファインダーの実装詳細であり、実際 sys.meta_path からパスベース・ファインダーが取り除かれた場合、パスエントリ・ファインダーの実装は何も実行されないでしょう。

5.5.1. パスエントリ・ファインダー

パスベース・ファインダー には、文字列 パスエントリ で指定された場所の Python モジュールや Python パッケージを見つけ、ロードする責任があります。ほとんどのパスエントリはファイルシステム上の場所を指定していますが、そこに制限される必要はありません。

メタパス・ファインダーとして、 パスベース・ファインダー には前に解説した find_spec() プロトコルが実装されていますが、これに加えて インポートパス からモジュールを見つけ、ロードする方法をカスタマイズするために使えるフックを提供しています。

パスベース・ファインダーsys.pathsys.path_hookssys.path_importer_cache という 3 つの変数を使います。さらにパッケージオブジェクトの __path__ 属性も使います。これらによって、インポート処理をカスタマイズする方法が提供されます。

sys.path には、モジュールとパッケージを探す場所文字列の一覧があります。これは PYTHONPATH 環境変数とその他様々なインストール方法や実装に依存するデフォルト値で初期化されます。 sys.path 内の要素は、ファイルシステム上のディレクトリや zip ファイルやその他モジュールを探すべき "場所" となりうるもの (site モジュールを参照) を指すことができます。文字列およびバイト列のみを sys.path に入れるべきです; 他のデータ型は無視されます。バイト列の要素のエンコーディングは、各 パスエントリ・ファインダー によって判別されます。

パスベース・ファインダーメタパス・ファインダー なので、インポート機構は、前で解説したパスベース・ファインダーの find_spec() メソッドを呼び出すことで インポートパス の検索を始めます。 path 引数が find_spec() に渡されたときは、それは走査するパス文字列のリスト - 典型的にはそのパッケージの中でインポートしているパッケージの __path__ 属性になります。 path 引数が None だった場合、それは最上位のインポートであることを示していて、 sys.path が使われます。

パスベース・ファインダーは検索パスのすべての要素について反復処理をし、それぞれのパスに対して適切な パスエントリ・ファインダー (PathEntryFinder) を探します。これは時間のかかる処理 (例えば、この検索のための stat() 呼び出しのオーバーヘッド) になり得るので、パスベース・ファインダーはパス要素からパスエントリ・ファインダーへの対応付けをキャッシュとして持っておきます。このキャッシュは sys.path_importer_cache に持っています (名前に反して、このキャッシュは実際には インポーター には制限されておらず、ファインダーオブジェクトを保持します) 。このようにして、時間のかかる特定の パスエントリ の場所のための パスエントリ・ファインダー の検索を一度だけ検索すれば良くなります。パスベース・ファインダーにパスエントリの検索を再度行わせるために、ユーザコードでは自由に sys.path_importer_cache からキャッシュを取り除いて構いません [3]

path entry がキャッシュの中に無かった場合、 path based finder は sys.path_hooks の中の呼び出し可能オブジェクトを全て辿ります。 このリストのそれぞれの path entry フック は、検索する path entry という引数 1 つを渡して呼び出されます。 その呼び出し可能オブジェクトは path entry を扱える path entry finder を返すか、 ImportError を送出します。 ImportError は、フックが path entry のための path entry finder を探せないことを報せるために path based finder が使います。 この例外は処理されず、 import path を辿っていく処理が続けられます。 フックは引数として文字列またはバイト列オブジェクトを期待します; バイト列オブジェクトのエンコーディングはフックに任されていて (例えば、ファイルシステムのエンコーディングの UTF-8 やそれ以外などです) 、フックが引数をデコードできなかった場合は ImportError を送出すべきです。

sys.path_hooks を辿る処理が パスエントリ・ファインダー を何も返さずに終わった場合、パスベース・ファインダーの find_spec() メソッドは、 sys.path_importer_cache に (このパスエントリに対するファインダーが存在しないことを示すために) None を保存し、 メタパス・ファインダー はモジュールが見つからなかったことを伝えるために None を返します。

sys.path_hooks 上の パスエントリフック 呼び出し可能オブジェクトの戻り値のいずれかが パスエントリ・ファインダー であった 場合、後で出てくるモジュール仕様を探すためのプロトコルが使われ、それがモジュールをロードするために使われます。

(空の文字列によって表される)現在のディレクトリは、 sys.path の他のエントリとは多少異なる方法で処理されます。まず、現在のディレクトリが存在しないことが判明した場合、 sys.path_importer_cache には何も追加されません。次に、現在のディレクトリに対する値は個々のモジュールのルックアップで毎回新たに検索されます。 3番目に、 sys.path_importer_cache に使われ、 importlib.machinery.PathFinder.find_spec() が返すパスは、実際のディレクトリであって空の文字列ではありません。

5.5.2. パスエントリ・ファインダー・プロトコル

モジュールと初期化されたパッケージのインポートをサポートするため、および名前空間パッケージのポーションとして提供するために、パスエントリ・ファインダーは find_spec() メソッドを実装しなければいけません。

find_spec() は 2 つの引数を取ります。インポートしようとしているモジュールの完全修飾名と、 (オプションの) 対象モジュールです。 find_spec() はモジュールに対応する完全に初期化 (populated) された仕様を返します。この仕様は (1つの例外を除いて) 常に "loader" セットを持っています。

モジュール仕様が名前空間 ポーション を表していることをインポート機構に示すために、パスエントリ・ファインダーはモジュール仕様の "loader" を None に、 "submodule_search_locations" を名前空間ポーションを含むリストに設定します。

バージョン 3.4 で変更: find_spec()find_loader()find_module() を置き換えました。両者は deprecated ですが、 find_spec() が定義されていなければ使われます。

古いパスエントリ・ファインダーの中には、 find_spec() の代わりにこれら 2 つの deperecated なメソッドのうちのいずれかを実装しているものがあるかもしれません。これらのメソッドは後方互換性のためにまだ考慮されています。しかし、パスエントリ・ファインダーに find_spec() が実装されていれば、古いメソッドは無視されます。

find_loader() はインポートされるモジュールの完全修飾名を引数に取ります。 find_loader() は、第 1 要素がローダで第 2要素が名前空間 ポーション である 2 要素のタプルを返します。第 1 要素 (つまりローダー) が None の場合、その意味は、パスエントリ・ファインダー自身は指定されたモジュールのためのローダーを持っていないものの、パスエントリがモジュールの名前空間ポーションに関係している (contribute) のをパスエントリ・ファインダーが知っているということです。これは、ほとんど常にファイルシステム上に物理的な実体のない名前空間パッケージをインポートしようとした場合です。パスエントリ・ファインダーがローダーとして None を返す場合には、 2 要素タプルである戻り値の第 2 要素はシーケンスでなければなりません。ただし、シーケンスは空でも構いません。

find_loader()None でないローダー値を返した場合、ポーションは無視され、パスベース・ファインダーからローダーが返され、パスエントリ上の検索が終了します。

他のインポート機構の実装に対する後方互換性のために、多くのパスエントリ・ファインダーは、メタパス・ファインダーがサポートするのと同じ伝統的な find_module() メソッドもサポートしています。しかし、パスエントリ・ファインダーの find_module() メソッドは、決して path 引数では呼び出されません (このメソッドは、パスフックの最初の呼び出しから適切なパス情報を記録する動作が期待されています)。

パスエントリ・ファインダーの find_module() メソッドは deprecated です。なぜなら、その方法ではパスエントリ・ファインダーが名前空間パッケージに対してポーションを提供することができないからです。もし find_loader()find_module() の両方がパスエントリ・ファインダーに存在したら、インポートシステムは常に find_module() よりも find_loader() を優先して呼び出します。

5.6. 標準のインポートシステムを置き換える

インポートシステム全体を置き換えるための最も信頼性のある仕組みは、 sys.meta_path のデフォルトの内容を削除し、全部をカスタムのメタパスフックで置き換えるものです。

もし、 import 文の動作だけを変更し、インポートシステムにアクセスする他の API には影響を与えなくてもよければ、組み込みの __import__() 関数を置き換えるだけで十分です。この手法は、ある 1 つのモジュール内だけで import 文の動作を変更するのにも用いられます。

(標準のインポートシステム全体を停止するのではなく) すでにメタパスにいるフックからあるモジュールのインポートを選択的に防ぐためには、 find_spec() から None を返す代わりに、直接 ModuleNotFoundError を送出するだけで十分です。 None を返すのはメタパスの走査を続けるべきであることを意味しますが、例外を送出するとすぐに走査を打ち切ります。

5.7. __main__ に対する特別な考慮

__main__ モジュールは、 Python のインポートシステムに関連する特別なケースです。 他の場所 で言及されているように、 __main__ モジュールは sysbuiltins などと同様にインタプリタースタートアップで直接初期化されます。しかし、前者 2 つのモジュールと違って、 __main__ は厳密にはビルトインのモジュールとしての資格を持っていません。これは、 __main__ が初期化される方法がインタプリタが起動されるときのフラグやその他のオプションに依存するためです。

5.7.1. __main__.__spec__

__main__ がどのように初期化されるかに依存して、 __main__.__spec__ は適切に設定されることもあれば None になることもあります。

Python が -m オプションを付けて実行された場合には、 __spec__ は対応するモジュールまたはパッケージのモジュール仕様に設定されます。また、ディレクトリや zip ファイル、または他の sys.path エントリを実行する処理の一部として __main__ モジュールがロードされる場合にも __spec__ が生成 (populate) されます。

それ以外のケース では、 __main__.__spec__None に設定されます。これは、 __main__ を生成 (populate) するために使われたコードがインポート可能なモジュールと直接一致していないためです:

  • 対話プロンプト
  • -c スイッチ
  • stdin から起動された場合
  • ソースファイルやバイトコードファイルから直接起動された場合

最後のケースでは、たとえ技術的にはファイルがモジュールとして直接インポートできた としても __main__.__spec__ は常に None になることに注意してください。もし __main__ において有効なモジュールメタデータが必要なら -m スイッチを使ってください。

__main__ がインポート可能なモジュールと一致し、 __main__.__spec__ がそれに応じて設定されていたとしても、それでもなお、この 2 つのモジュールは別物とみなされることに注意してください。これは、 if __name__ == "__main__": チェックによって保証されるブロックは、 __main__ 名前空間を生成 (populate) するためにモジュールが使用される時にだけ実行され、通常のインポート時には実行されない、という事実に起因しています。

5.8. 取り掛かり中の問題

XXX 図があるととても良い。

XXX * (import_machinery.rst) モジュールとパッケージの属性のみに紙面を割いた節を設けるのは何如でしょうか? もしかしたらデータモデルについての言語リファレンスのページにある関係する内容を拡張したり、置き換えるようなものになるかもしれません。

XXX ライブラリマニュアルの runpy や pkgutil の解説の先頭すべてに、"こちらも参照 (See Also)" という、この新しいインポートシステムの節へのリンクを置くべき。

XXX __main__ が初期化される様々な方法についてより多くの説明を追加する?

XXX __main__ の特異性/落とし穴についてより多くの情報を追加する (つまり PEP 395 からコピーする)

5.9. 参考資料

Python の初期の頃からすると、インポート機構は目覚ましい発展を遂げました。 一部細かいところがドキュメントが書かれたときから変わってはいますが、最初期の パッケージの仕様 はまだ読むことができます。

オリジナルの sys.meta_path の仕様は PEP 302 で、その後継となる拡張が PEP 420 です。

PEP 420 は Python 3.3 に 名前空間パッケージ を導入しています。 PEP 420 はまた find_module() に代わるものとして find_loader() プロトコルを導入しています。

PEP 366 は、メインモジュールでの明示的な相対インポートのために追加した __package__ 属性の解説をしています。

PEP 328 は絶対インポート、明示的な相対インポート、および、当初 __name__ で提案し、後に PEP 366__package__ で定めた仕様を導入しました。

PEP 338 はモジュールをスクリプトとして実行するときの仕様を定めています。

PEP 451 は、モジュール仕様オブジェクトにおけるモジュール毎のインポート状態のカプセル化を追加しています。また、ローダーの定型的な責任のほとんどをインポート機構に肩代わりさせています。これらの変更により、インポートシステムのいくつかの API が deprecate され、またファインダーとローダーには新しいメソッドが追加されました。

脚注

[1]types.ModuleType を参照してください。
[2]importlib の実装は、戻り値を直接使うことは避けています。その代わりに、モジュール名を調べて sys.modules からモジュールオブジェクトを得ます。こうすることの間接的な効果は、インポートされたモジュールが sys.modules にいる自分自身を置き換えることがあるということです。これは実装依存の動作であり、他の Python 実装では保証されていない動作です。
[3]レガシーなコードでは、 sys.path_importer_cacheimp.NullImporter のインスタンスがいることがあります。それの代わりに None を使うようにコードを変更することが推奨されます。より詳しいことは Python コードの移植 を参照してください。