PythonのImportメカニズム

4566 ワード

Pythonプログラムでは、import文が現在のプログラムで使用するための新しいモジュールをインポートします.importの基本文法はimport module_nameです.import文を実行すると、Python解釈器はまずmodule_を検索します.nameが指すソースファイルは、メモリにロードされ、新しくインポートしたモジュールを変数にバインドします.これにより、後続の操作で、このバインドされた変数から新しくインポートされたモジュールにアクセスできます.
sys.path import module_nameの場合、Pythonはsys.pathで指定されたフォルダリストの順に検索します.sys.pathは、オペレーティングシステムやコンパイルパラメータに応じて、デフォルトでは異なるフォルダリストが格納されるリストタイプです.また、プログラム実行中に、動的にsys.pathにコンテンツを追加または削除し、必要なロードモジュールのニーズを実現することもできる.現在のArch Linux(kernel 4.19.21)マシンで、Python 3.7のsys.pathを以下のように表示します.
$ python
Python 3.7.1 (default, Oct 22 2018, 10:41:28) 
[GCC 8.2.1 20180831] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/home/hong/.local/lib/python3.7/site-packages', '/usr/lib/python3.7/site-packages']
sys.pathのデフォルトの最初のフォルダアドレスは空で、現在の作業ディレクトリであるos.getcwd()を指す.そのため、モジュールを検索する際、Pythonはまず現在の作業ディレクトリを検索します.正常にロードされたモジュールは、sys.modules辞書に格納されます.1つのモジュールがロードに成功し、sys.modulesに存在する場合、次にモジュールに対してロード操作を実行すると、検索操作は実行されず、sys.modulesから直接戻る.
$ mkdir /tmp/demo
$ touch /tmp/demo/__init__.py  # /tmp    ,   demo  
$ python
>>> import os, sys
>>> import demo     # /tmp    sys.path ,      
Traceback (most recent call last):
  File "", line 1, in 
ModuleNotFoundError: No module named 'demo'
>>> 'demo' in sys.modules
False
>>> os.chdir('/tmp/')  #         /tmp,    
>>> import demo
>>> demo

>>> demo.__path__
['/tmp/demo']
>>> 'demo' in sys.modules
True

もちろん、sys.pathフォルダリストに'/tmp'フォルダを追加することにより、上記demoモジュールをロードしてもよい.
$ python
>>> import sys
>>> sys.path.append('/tmp')
>>> import demo
>>> demo
['/tmp/demo']

PYTHONPATH
実行中にsys.pathを変更するだけでなく、環境変数PYHTONPATHを使用してモジュール検索のパスを指定することもできます.Pythonは、PYTHONPATHのコンテンツをsys.pathの現在の作業ディレクトリ''と他のデフォルトの検索パスの間に配置します.
$ export PYTHONPATH='/tmp'
$ python
>>> import sys
>>> sys.path
['', '/tmp', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/home/hong/.local/lib/python3.7/site-packages', '/usr/lib/python3.7/site-packages']

複数の検索パスを指定する場合は、PYTHONPATHに配置します.異なる検索パスの間には区切り記号が必要ですが、オペレーティングシステムの習慣によって、異なるシステムで区切り記号の違いが発生します.
$ export PYTHONPATH='/tmp:/tmp/lib2'
$ python
>>> import sys
>>> sys.path
['', '/tmp', '/tmp/lib2', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/home/hong/.local/lib/python3.7/site-packages', '/usr/lib/python3.7/site-packages']

モジュールの再ロード
前述したように、Pythonがモジュールのロードを完了すると、sys.modulesに新しいモジュールが配置されます.モジュールが再ロードされると、異なる検索操作が実行され、sys.modulesから直接返される.しかし、モジュールのソースコードを変更した場合、モジュールを完全に再ロードする必要がある場合があります.この場合、sys.modulesに保存されている元モジュールを削除してから、import操作を実行すればよい.
環境変数PYTHONPATHにより、上記demoモジュールがロードされる.
$ export PYTHONPATH='/tmp'
$ python
>>> import demo
>>> demo.message
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: module 'demo' has no attribute 'message'
demoモジュールにはmessage変数が存在せず、アクセスエラーが発生しました.demoモジュールを変更し、message変数を追加します.
$ echo "message='this is a message.'" > /tmp/demo/__init__.py

Python環境でdemoモジュールを再ロードします.
>>> import sys
>>> sys.modules.pop('demo')
>>> import demo
>>> demo.message
'this is a message'

Reference
  • Python Documentation: The Import System
  • How to set your Python path
  • How does python find packages