Python Tips: .py 拡張子の無い Python ファイルを import したい

Python で .py 拡張子のついていない Python ファイル(モジュール)を import する方法についてです。

まず先に結論ですが、 .py 拡張子の無い Python ファイルを読み込みたくなったときは、 .py 拡張子をつけてふつうの import 文で読み込む というのがベストかと思います。 何らかの理由で .py 拡張子をつけられない事情があるなら、それができる方法がないかをまずは考えてみるべきです。 考えてもどうしてもダメだった場合のみ以下の方法を検討するとよいと思います。

確認時のバージョン

  • Python 3.10

.py 拡張子の無い Python ファイルを import する方法

Python 本体に同梱のライブラリ importlib を使うとできます。

次の関数 import_path() を定義すると

from importlib.machinery import SourceFileLoader
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path


def import_path(path: Path):
    """パス指定で Python モジュールを読み込む"""
    module_name = path.stem.replace('-', '_')

    loader = SourceFileLoader(module_name, str(path))
    spec = spec_from_file_location(module_name, path, loader=loader)
    module = module_from_spec(spec)
    spec.loader.exec_module(module)

    return module

次の形で pathlib.Path インスタンスを使ってモジュールを読み込めます。

mymodule_path = Path(...)
mymodule = import_path(mymodule_path)

試しに私の手元で load_mymodule.pymymodule という 2 つのファイルを作成し同階層に置いて load_mymodule.py を実行してみました。

cd sample/
tree
.
├── load_mymodule.py
└── mymodule

ファイルの中身はそれぞれ次のとおりです。

sample/load_mymodule.py:

from importlib.machinery import SourceFileLoader
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path


def import_path(path: Path):
    """パス指定で Python モジュールを読み込む"""
    module_name = path.stem.replace('-', '_')

    loader = SourceFileLoader(module_name, str(path))
    spec = spec_from_file_location(module_name, path, loader=loader)
    module = module_from_spec(spec)
    spec.loader.exec_module(module)

    return module


if __name__ == '__main__':
    mymodule_path = Path(__file__).parent / 'mymodule'
    mymodule = import_path(mymodule_path)

sample/mymodule:

print(f'{__name__} is loaded.')

load_mymodule.py を実行すると無事に mymodule が読み込めることが確認できました。

python load_mymodule.py
# => mymodule is loaded.

以上です。

ただし、この方法が正規に推奨されている方法というわけではないので、参考にされる際はあくまでひとつのやり方として参考にしてください。

ちなみに、 Python 3.4 以前のバージョンでは importlib ではなく imp ライブラリを使ったシンプルな方法があったらしく、検索していると imp を使った方法が紹介されているのをちらほら目にしました。

import imp

def import_path(path: Path):
    module_name = path.stem.replace('-', '_')
    return imp.load_source(module_name, str(path))

しかし imp は deprecated となっており Python 3.12 で削除される予定とされています。

DeprecationWarning: the imp module is deprecated in favour of importlib and slated for removal in Python 3.12; see the module's documentation for alternative uses

冒頭のコメントの繰り返しですが、ほとんどの場合は単純に拡張子 .py をつけて import 文で読み込むというのがおそらく素直でベストなやり方なので、まずはそのやり方が採れないかとよく検討するのがよいかと思います。

GitHub Gist

参考