5. インポートシステム—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.9/reference/import
移動先:案内検索

5.5。 輸入システム

1つのモジュール内のPythonコードは、インポートのプロセスによって別のモジュール内のコードにアクセスします。 import ステートメントは、インポート機構を呼び出す最も一般的な方法ですが、それが唯一の方法ではありません。 importlib.import_module()や組み込みの __ import __()などの関数を使用して、インポート機構を呼び出すこともできます。

import ステートメントは、2つの操作を組み合わせたものです。 名前付きモジュールを検索し、その検索結果をローカルスコープ内の名前にバインドします。 importステートメントの検索操作は、適切な引数を使用した __ import __()関数の呼び出しとして定義されます。 __ import __()の戻り値は、importステートメントの名前バインディング操作を実行するために使用されます。 その名前バインディング操作の正確な詳細については、importステートメントを参照してください。

__ import __()を直接呼び出すと、モジュール検索のみが実行され、見つかった場合はモジュール作成操作が実行されます。 親パッケージのインポートやさまざまなキャッシュ( sys.modules を含む)の更新など、特定の副作用が発生する可能性がありますが、名前バインディング操作を実行するのは 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 というサブパッケージと[X13X]というサブパッケージがあります。 X187X] 。

5.2.1。 通常のパッケージ

Pythonは、通常パッケージ名前空間パッケージの2種類のパッケージを定義しています。 通常のパッケージは、Python3.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__.pyおよびparent/three/__init__.pyが実行されます。


5.2.2。 名前空間パッケージ

名前空間パッケージは、さまざまな部分の複合体であり、各部分が親パッケージにサブパッケージを提供します。 一部は、ファイルシステムのさまざまな場所に存在する場合があります。 一部は、zipファイル、ネットワーク、またはPythonがインポート中に検索するその他の場所にもあります。 名前空間パッケージは、ファイルシステム上のオブジェクトに直接対応する場合と対応しない場合があります。 それらは具体的な表現を持たない仮想モジュールである可能性があります。

名前空間パッケージは、__path__属性に通常のリストを使用しません。 代わりに、親パッケージ(または最上位パッケージの場合は sys.path )のパスが変更された場合に、そのパッケージ内の次のインポート試行でパッケージ部分の新しい検索を自動的に実行するカスタム反復可能タイプを使用します。 。

名前空間パッケージでは、parent/__init__.pyファイルはありません。 実際、インポート検索中に複数のparentディレクトリが見つかる場合があり、それぞれが異なる部分によって提供されます。 したがって、parent/oneparent/twoの隣に物理的に配置されていない可能性があります。 この場合、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.modules には、foofoo.bar、およびfoo.bar.bazのエントリが含まれます。 各キーは、その値として対応するモジュールオブジェクトを持ちます。

インポート中に、モジュール名が sys.modules で検索され、存在する場合、関連付けられた値はインポートを満たすモジュールであり、プロセスが完了します。 ただし、値がNoneの場合、 ModuleNotFoundError が発生します。 モジュール名が欠落している場合、Pythonはモジュールの検索を続行します。

sys.modules は書き込み可能です。 キーを削除しても、関連するモジュールが破壊されることはありませんが(他のモジュールがそのキーへの参照を保持している可能性があるため)、名前付きモジュールのキャッシュエントリが無効になり、Pythonは次回のインポート時に名前付きモジュールを新たに検索します。 キーをNoneに割り当てることもでき、モジュールの次のインポートで ModuleNotFoundError が発生します。

ただし、モジュールオブジェクトへの参照を保持し、 sys.modules のキャッシュエントリを無効にしてから、名前付きモジュールを再インポートする場合と同様に、2つのモジュールオブジェクトはではなくになることに注意してください。同じであること。 対照的に、 importlib.reload()は、同じモジュールオブジェクトを再利用し、モジュールのコードを再実行してモジュールの内容を再初期化します。


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

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

Pythonには、いくつかのデフォルトのファインダーとインポーターが含まれています。 1つ目は組み込みモジュールを見つける方法を知っており、2つ目はフリーズしたモジュールを見つける方法を知っています。 3番目のデフォルトファインダーは、インポートパスでモジュールを検索します。 インポートパスは、ファイルシステムパスまたはzipファイルに名前を付けることができる場所のリストです。 また、URLで識別されるリソースなど、検索可能なリソースを検索するように拡張することもできます。

インポート機構は拡張可能であるため、新しいファインダーを追加して、モジュール検索の範囲と範囲を拡張できます。

ファインダーは実際にはモジュールをロードしません。 名前付きモジュールが見つかった場合は、モジュール仕様を返します。これは、モジュールのインポート関連情報のカプセル化であり、インポート機構がモジュールのロード時に使用します。

次のセクションでは、新しいプロトコルを作成および登録してインポート機構を拡張する方法など、ファインダーとローダーのプロトコルについて詳しく説明します。

バージョン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 を検索します。これにはメタパスファインダーオブジェクトのリストが含まれています。 これらのファインダーは、指定されたモジュールの処理方法を知っているかどうかを確認するために照会されます。 メタパスファインダーは、 find_spec()というメソッドを実装する必要があります。このメソッドは、名前、インポートパス、および(オプションで)ターゲットモジュールの3つの引数を取ります。 メタパスファインダーは、指定されたモジュールを処理できるかどうかを判断するために、任意の戦略を使用できます。

メタパスファインダーが名前付きモジュールの処理方法を知っている場合、スペックオブジェクトを返します。 指定されたモジュールを処理できない場合は、Noneを返します。 sys.meta_path 処理が仕様を返さずにリストの最後に到達すると、 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がインポートされた後、[X135X] は、mpf.find_spec("foo.bar", foo.__path__, None)を呼び出して、メタパスをもう一度トラバースすることによってインポートされます。 foo.barがインポートされると、最後のトラバーサルはmpf.find_spec("foo.bar.baz", foo.bar.__path__, None)を呼び出します。

一部のメタパスファインダーは、トップレベルのインポートのみをサポートします。 これらのインポーターは、None以外のものが2番目の引数として渡された場合、常にNoneを返します。

Pythonのデフォルトの sys.meta_path には、3つのメタパスファインダーがあります。1つは組み込みモジュールのインポート方法、もう1つはフリーズモジュールのインポート方法、もう1つはからモジュールをインポートする方法を知っています。インポートパス(つまり パスベースのファインダー)。

バージョン3.4で変更:メタパスファインダーの find_spec()メソッドが find_module()に置き換わり、現在は非推奨になっています。 変更なしで動作し続けますが、インポートマシンは、ファインダーが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:
    # unsupported
    raise ImportError
if spec.origin is None and spec.submodule_search_locations is not None:
    # namespace package
    sys.modules[spec.name] = module
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 に指定された名前の既存のモジュールオブジェクトがある場合、インポートはすでにそれを返しています。
  • モジュールは、ローダーがモジュールコードを実行する前に、 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()メソッドを呼び出します。 exec_module()から返された値はすべて無視されます。

ローダーは、次の要件を満たす必要があります。

  • モジュールがPythonモジュールの場合(組み込みモジュールや動的にロードされる拡張機能ではなく)、ローダーはモジュールのグローバル名前空間(module.__dict__)でモジュールのコードを実行する必要があります。
  • ローダーがモジュールを実行できない場合は、 ImportError を発生させる必要がありますが、 exec_module()中に発生したその他の例外は伝播されます。


多くの場合、ファインダーとローダーは同じオブジェクトにすることができます。 このような場合、 find_spec()メソッドは、ローダーがselfに設定された仕様を返すだけです。

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

バージョン3.4の新機能:ローダーの create_module()メソッド。


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

既存のローダーとの互換性のために、インポート機構はローダーのload_module()メソッドが存在し、ローダーがexec_module()も実装していない場合はそれを使用します。 ただし、load_module()は非推奨になり、ローダーは代わりにexec_module()を実装する必要があります。

load_module()メソッドは、モジュールの実行に加えて、上記のすべてのボイラープレート読み込み機能を実装する必要があります。 すべて同じ制約が適用されますが、いくつかの追加の説明があります。

  • sys.modules に指定された名前の既存のモジュールオブジェクトがある場合、ローダーはその既存のモジュールを使用する必要があります。 (そうでない場合、 importlib.reload()は正しく機能しません。)指定されたモジュールが sys.modules に存在しない場合、ローダーは新しいモジュールオブジェクトを作成してに追加する必要があります。 sys.modules
  • モジュールは、ローダーがモジュールコードを実行する前に、 sys.modules に存在する必要があります。これにより、無制限の再帰や複数のロードを防ぐことができます。
  • ロードが失敗した場合、ローダーは sys.modules に挿入したモジュールをすべて削除する必要がありますが、ローダー自体がロードした場合にのみ、失敗したモジュールをのみ削除する必要があります。モジュールを明示的に。


バージョン3.5で変更:exec_module()が定義されているが、create_module()が定義されていない場合、 A 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

次に、以下を実行すると、fooおよびbarにバインドされた名前がspamモジュールに配置されます。

>>> 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__は文字列の反復可能である必要がありますが、空の場合があります。 sys.path に使用されるのと同じルールが、パッケージの__path__にも適用され、パッケージの [をトラバースするときに、 sys.path_hooks (以下で説明)が参照されます。 X171X]。

パッケージの__init__.pyファイルは、パッケージの__path__属性を設定または変更する場合があります。これは通常、 PEP 420 より前の名前空間パッケージの実装方法でした。 PEP 420 の採用により、名前空間パッケージは、__path__操作コードのみを含む__init__.pyファイルを提供する必要がなくなりました。 インポート機構は、名前空間パッケージに対して__path__を自動的に正しく設定します。


5.4.6。 モジュールreprs

デフォルトでは、すべてのモジュールに使用可能なreprがありますが、上記で設定した属性に応じて、モジュールの仕様では、モジュールオブジェクトのreprをより明示的に制御できます。

モジュールに仕様(__spec__)がある場合、インポート機構はモジュールからreprを生成しようとします。 それが失敗した場合、または仕様がない場合、インポートシステムは、モジュールで利用可能な情報を使用してデフォルトのreprを作成します。 module.__name__module.__file__、およびmodule.__loader__をreprへの入力として使用しようとしますが、欠落している情報はデフォルトで使用されます。

使用される正確なルールは次のとおりです。

  • モジュールに__spec__属性がある場合、仕様の情報を使用してreprが生成されます。 「name」、「loader」、「origin」、および「has_location」属性が参照されます。
  • モジュールに__file__属性がある場合、これはモジュールのreprの一部として使用されます。
  • モジュールに__file__がないが、Noneではない__loader__がある場合、ローダーのreprはモジュールのreprの一部として使用されます。
  • それ以外の場合は、reprでモジュールの__name__を使用してください。


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

Python 3.3との下位互換性のために、モジュールreprは、上記のいずれかのアプローチを試す前に、ローダーの module_repr()メソッド(定義されている場合)を呼び出すことによって生成されます。 ただし、このメソッドは非推奨です。


5.4.7。 キャッシュされたバイトコードの無効化

Pythonは、キャッシュされたバイトコードを.pycファイルからロードする前に、キャッシュがソース.pyファイルで最新であるかどうかを確認します。 デフォルトでは、Pythonは、ソースの書き込み時にソースの最終変更されたタイムスタンプとサイズをキャッシュファイルに保存することでこれを行います。 実行時に、インポートシステムは、キャッシュファイルに保存されているメタデータをソースのメタデータと照合することにより、キャッシュファイルを検証します。

Pythonは、メタデータではなくソースファイルのコンテンツのハッシュを格納する「ハッシュベース」のキャッシュファイルもサポートしています。 ハッシュベースの.pycファイルには、チェック済みとオフの2つのバリエーションがあります。 チェックされたハッシュベースの.pycファイルの場合、Pythonは、ソースファイルをハッシュし、結果のハッシュをキャッシュファイル内のハッシュと比較することにより、キャッシュファイルを検証します。 チェックされたハッシュベースのキャッシュファイルが無効であることが判明した場合、Pythonはそれを再生成し、新しいチェックされたハッシュベースのキャッシュファイルを書き込みます。 チェックされていないハッシュベースの.pycファイルの場合、Pythonは、キャッシュファイルが存在する場合は有効であると単純に想定します。 ハッシュベースの.pycファイルの検証動作は、 -check-hash-based-pycs フラグでオーバーライドされる場合があります。

バージョン3.7で変更:ハッシュベースの.pycファイルを追加しました。 以前は、Pythonはバイトコードキャッシュのタイムスタンプベースの無効化のみをサポートしていました。


5.5。 パスベースのファインダー

前述のように、Pythonにはいくつかのデフォルトのメタパスファインダーが付属しています。 これらの1つは、パスベースのファインダーPathFinder )と呼ばれ、パスエントリのリストを含むインポートパスを検索します。 各パスエントリは、モジュールを検索する場所を指定します。

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

パスエントリファインダーのデフォルトセットは、ファイルシステム上でモジュールを検索するためのすべてのセマンティクスを実装し、Pythonソースコード(.pyファイル)、Pythonバイトコード(.pycファイル)などの特別なファイルタイプを処理します。および共有ライブラリ(例: .soファイル)。 標準ライブラリの zipimport モジュールでサポートされている場合、デフォルトのパスエントリファインダーは、zipファイルからのこれらすべてのファイルタイプ(共有ライブラリを除く)の読み込みも処理します。

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

パスベースのファインダーは、追加のフックとプロトコルを提供するため、検索可能なパスエントリのタイプを拡張およびカスタマイズできます。 たとえば、パスエントリをネットワークURLとしてサポートする場合は、HTTPセマンティクスを実装してWeb上のモジュールを検索するフックを作成できます。 このフック(呼び出し可能)は、以下で説明するプロトコルをサポートするパスエントリファインダーを返します。これは、Webからモジュールのローダーを取得するために使用されました。

警告:このセクションと前のセクションはどちらもファインダーという用語を使用しており、メタパスファインダーパスエントリファインダーという用語を使用して区別しています。 これらの2種類のファインダーは非常に似ており、同様のプロトコルをサポートし、インポートプロセス中に同様の方法で機能しますが、微妙に異なることに注意することが重要です。 特に、メタパスファインダーは、 sys.meta_path トラバーサルをキーオフするため、インポートプロセスの開始時に動作します。

対照的に、パスエントリファインダーは、ある意味でパスベースのファインダーの実装の詳細であり、実際、パスベースのファインダーが sys.meta_path から削除された場合、パスエントリファインダーのセマンティクスはありません。呼び出されます。

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

パスベースのファインダーは、文字列パスエントリで場所が指定されているPythonモジュールとパッケージを検索してロードする役割を果たします。 ほとんどのパスエントリはファイルシステム内の場所に名前を付けますが、これに限定する必要はありません。

メタパスファインダーとして、パスベースのファインダーは前述の find_spec()プロトコルを実装しますが、モジュールの検索方法とロード方法をカスタマイズするために使用できる追加のフックを公開します。 インポートパス

パスベースのファインダーsys.pathsys.path_hookssys.path_importer_cache では3つの変数が使用されます。 パッケージオブジェクトの__path__属性も使用されます。 これらは、輸入機械をカスタマイズできる追加の方法を提供します。

sys.path には、モジュールとパッケージの検索場所を提供する文字列のリストが含まれています。 これは、PYTHONPATH環境変数、およびその他のさまざまなインストールおよび実装固有のデフォルトから初期化されます。 sys.path のエントリは、ファイルシステム上のディレクトリ、zipファイル、およびURLなどのモジュールを検索する必要があるその他の「場所」(サイトモジュールを参照)に名前を付けることができます。 、またはデータベースクエリ。 sys.path には文字列とバイトのみが存在する必要があります。 他のすべてのデータ型は無視されます。 バイトエントリのエンコーディングは、個々のパスエントリファインダーによって決定されます。

パスベースのファインダーメタパスファインダーであるため、インポート機構はパスベースのファインダーの find_spec()[を呼び出すことによってインポートパス検索を開始します。 X176X]前述の方法。 find_spec()へのpath引数を指定すると、トラバースする文字列パスのリストになります。通常は、そのパッケージ内のインポートに対するパッケージの__path__属性です。 path引数がNoneの場合、これは最上位のインポートを示し、 sys.path が使用されます。

パスベースのファインダーは、検索パス内のすべてのエントリを反復処理し、これらのそれぞれについて、パスエントリに適切なパスエントリファインダーPathEntryFinder )を探します。 これは費用のかかる操作になる可能性があるため(例: この検索には stat()呼び出しのオーバーヘッドが発生する可能性があります)、パスベースのファインダーはパスエントリをパスエントリファインダーにマッピングするキャッシュを維持します。 このキャッシュは sys.path_importer_cache で維持されます(名前にもかかわらず、このキャッシュは importer オブジェクトに限定されるのではなく、実際にはファインダーオブジェクトを格納します)。 このように、特定のパスエントリの場所のパスエントリファインダーの高価な検索は1回だけ実行する必要があります。 ユーザーコードは sys.path_importer_cache からキャッシュエントリを自由に削除し、パスベースのファインダーにパスエントリ検索を再度実行するように強制します 3

パスエントリがキャッシュに存在しない場合、パスベースのファインダーは sys.path_hooks 内のすべての呼び出し可能オブジェクトを反復処理します。 このリストの各パスエントリフックは、検索対象のパスエントリという単一の引数で呼び出されます。 この呼び出し可能オブジェクトは、パスエントリを処理できるパスエントリファインダーを返すか、 ImportError を発生させる可能性があります。 ImportError は、パスベースのファインダーによって使用され、フックがそのパスエントリパスエントリファインダーを見つけられないことを通知します。 例外は無視され、インポートパスの反復が続行されます。 フックは、文字列またはバイトオブジェクトのいずれかを予期する必要があります。 バイトオブジェクトのエンコーディングはフック次第です(例: ファイルシステムエンコーディング、UTF-8、またはその他のものである可能性があり、フックが引数をデコードできない場合は、 ImportError を発生させる必要があります。

sys.path_hooks の反復が終了し、パスエントリファインダーが返されない場合、パスベースのファインダーの find_spec()メソッドはNoneをに格納します。 sys.path_importer_cache (このパスエントリのファインダーがないことを示すため)そしてNoneを返し、このメタパスファインダーがモジュールを見つけられなかったことを示します。

パスエントリファインダーsys.path_hooks 上のパスエントリフック呼び出し可能オブジェクトの1つによって返される場合、次のプロトコルが使用されますモジュールの仕様をファインダーに要求します。モジュールの仕様は、モジュールをロードするときに使用されます。

空の文字列で示される現在の作業ディレクトリは、 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()は、モジュールの完全に入力された仕様を返します。 この仕様には常に「ローダー」が設定されています(1つの例外を除く)。

仕様が名前空間部分を表すことをインポート機構に示すために、パスエントリファインダーは「submodule_search_locations」をその部分を含むリストに設定します。

バージョン3.4で変更: find_spec()find_loader()find_module()を置き換えました。どちらも非推奨になりましたが、 find_spec()が定義されていない場合に使用されます。

古いパスエントリファインダーは、find_spec()の代わりに、これら2つの非推奨のメソッドのいずれかを実装する場合があります。 これらのメソッドは、下位互換性のために引き続き尊重されます。 ただし、find_spec()がパスエントリファインダーに実装されている場合、従来のメソッドは無視されます。

find_loader()は1つの引数を取り、インポートされるモジュールの完全修飾名です。 find_loader()は、最初の項目がローダーで、2番目の項目が名前空間部分である2タプルを返します。

インポートプロトコルの他の実装との下位互換性のために、多くのパスエントリファインダーは、メタパスファインダーがサポートするのと同じ従来のfind_module()メソッドもサポートします。 ただし、パスエントリファインダーfind_module()メソッドは、path引数で呼び出されることはありません(パスフックへの最初の呼び出しから適切なパス情報を記録することが期待されます)。

パスエントリファインダーのfind_module()メソッドは、パスエントリファインダーが名前空間パッケージに部分を提供できないため、非推奨になりました。 find_loader()find_module()の両方がパスエントリファインダーに存在する場合、インポートシステムは常にfind_module()よりもfind_loader()を呼び出します。


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

インポートシステム全体を置き換えるための最も信頼できるメカニズムは、 sys.meta_path のデフォルトの内容を削除し、それらを完全にカスタムメタパスフックに置き換えることです。

インポートシステムにアクセスする他のAPIに影響を与えずに、インポートステートメントの動作のみを変更することが許容される場合は、組み込みの __ import __()関数を置き換えるだけで十分な場合があります。 この手法は、モジュールレベルで使用して、そのモジュール内のインポートステートメントの動作のみを変更することもできます。

(標準のインポートシステムを完全に無効にするのではなく)メタパスの早い段階でフックから一部のモジュールのインポートを選択的に防ぐには、 ModuleNotFoundErrorfind_spec()から直接発生させるだけで十分です。 Noneを返す代わりに。 後者は、メタパス検索を続行する必要があることを示していますが、例外を発生させるとすぐに終了します。


5.7。 パッケージの相対インポート

相対的なインポートでは、先頭のドットが使用されます。 単一の先頭のドットは、現在のパッケージから始まる相対的なインポートを示します。 2つ以上の先頭のドットは、現在のパッケージの親への相対的なインポートを示します。最初のドットの後のドットごとに1つのレベルです。 たとえば、次のパッケージレイアウトがあるとします。

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

subpackage1/moduleX.pyまたはsubpackage1/__init__.pyのいずれにおいても、有効な相対インポートは次のとおりです。

from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo

絶対インポートではimport <>またはfrom <> import <>構文を使用できますが、相対インポートでは2番目の形式のみを使用できます。 この理由は次のとおりです。

import XXX.YYY.ZZZ

XXX.YYY.ZZZを使用可能な式として公開する必要がありますが、.moduleYは有効な式ではありません。


5.8。 __main__に関する特別な考慮事項

__ main __ モジュールは、Pythonのインポートシステムに関連する特殊なケースです。 他の場所で述べたように、__main__モジュールは、 sysbuiltins と同様に、インタープリターの起動時に直接初期化されます。 ただし、これら2つとは異なり、組み込みモジュールとして厳密に認定されているわけではありません。 これは、__main__が初期化される方法が、インタープリターが呼び出されるフラグやその他のオプションに依存するためです。

5.8.1。 __main __.__ spec__

__ main __ の初期化方法に応じて、__main__.__spec__は適切に設定されるかNoneに設定されます。

-m オプションを指定してPythonを起動すると、__spec__が対応するモジュールまたはパッケージのモジュール仕様に設定されます。 __spec__は、ディレクトリ、zipファイル、またはその他の sys.path エントリの実行の一部として__main__モジュールがロードされたときにも入力されます。

では、 __ main __ に入力するために使用されるコードがインポート可能なモジュールに直接対応していないため、残りのケース __main__.__spec__Noneに設定されます。

  • インタラクティブプロンプト
  • -c オプション
  • stdinから実行
  • ソースファイルまたはバイトコードファイルから直接実行

最後のケースでは、__main__.__spec__は常にNoneであり、が技術的には代わりにモジュールとして直接インポートできる場合でも、であることに注意してください。 __ main __ で有効なモジュールメタデータが必要な場合は、 -m スイッチを使用します。

__main__がインポート可能なモジュールに対応し、それに応じて__main__.__spec__が設定されている場合でも、個別のモジュールと見なされることにも注意してください。 これは、if __name__ == "__main__":チェックによって保護されたブロックが、モジュールが__main__名前空間への入力に使用された場合にのみ実行され、通常のインポート中には実行されないためです。


5.9。 未解決の問題

XXX図があれば本当にいいです。

XXX *(import_machinery.rst)モジュールとパッケージの属性だけに専念するセクションはどうですか?おそらく、データモデルリファレンスページの関連エントリを拡張したり、置き換えたりしますか?

ライブラリマニュアルのXXXrunpy、pkgutilなどはすべて、新しいインポートシステムセクションを指す上部の「関連項目」リンクを取得する必要があります。

XXX __main__が初期化されるさまざまな方法に関する説明を追加しますか?

XXX __main__の癖/落とし穴に関する情報を追加します(つまり、 PEP 395 からコピーします)。


5.10。 参考文献

インポート機構は、Pythonの初期からかなり進化してきました。 パッケージの元の仕様は引き続き読むことができますが、そのドキュメントの作成以降、一部の詳細が変更されています。

sys.meta_path の元の仕様は PEP 302 であり、その後 PEP 420 で拡張されました。

PEP 420 は、Python3.3用の名前空間パッケージを導入しました。 PEP 420 は、find_module()の代わりにfind_loader()プロトコルも導入しました。

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

PEP 328 は、絶対的および明示的な相対インポートを導入し、最初にセマンティクス用に__name__を提案しました PEP 366 は最終的に[ X178X] 。

PEP 338 は、実行中のモジュールをスクリプトとして定義します。

PEP 451 は、仕様オブジェクトにモジュールごとのインポート状態のカプセル化を追加します。 また、ローダーの定型的な責任のほとんどを輸入機械にオフロードします。 これらの変更により、インポートシステムでのいくつかのAPIの非推奨と、ファインダーとローダーへの新しいメソッドの追加が可能になります。

脚注

1
types.ModuleType を参照してください。
2
importlib実装は、戻り値を直接使用することを回避します。 代わりに、 sys.modules でモジュール名を検索してモジュールオブジェクトを取得します。 これの間接的な影響は、インポートされたモジュールが sys.modules でそれ自体を置き換える可能性があることです。 これは実装固有の動作であり、他のPython実装での動作が保証されていません。
3
レガシーコードでは、 sys.path_importer_cacheimp.NullImporter のインスタンスを見つけることができます。 代わりにNoneを使用するようにコードを変更することをお勧めします。 詳細については、 Pythonコードの移植を参照してください。