Pythonの相対インポートで上位ディレクトリ・サブディレクトリを指定

パッケージを自作する場合、パッケージ内のモジュールから上位ディレクトリ(親ディレクトリ)や下位ディレクトリ(サブディレクトリ)にあるほかのモジュールをインポートしたいことがあります。 そのようなときは、相対パスで相対的な位置を指定してインポート(相対インポート)することができます。

  1. モジュール (module) パッケージ内参照 — Python 3.6.5 ドキュメント

相対インポートはパッケージ内の仕組みなので、パッケージ外のスクリプトファイルから上位ディレクトリを指定してパッケージやモジュールをインポートするにはimportの対象ディレクトリ(モジュール探索パス)を追加する必要があります。 なお、自作のパッケージを標準ライブラリやpipでインストールしたサードパーティライブラリのように使いまわしたい場合は、環境変数PYTHONPATHでモジュール探索パスを追加するのが便利です。

ここでは以下の内容について説明します。

自作パッケージの例 同じパッケージ(同じディレクトリ)からインポート サブパッケージ(サブディレクトリ)からインポート 上位パッケージ(上位ディレクトリ)からインポート 結果の例 相対インポートしているパッケージ内のモジュールを単体で実行 パッケージ外のスクリプトから上位ディレクトリを指定

自作パッケージの例

以下の構造のパッケージmy_packageを例とします。init.pyはすべて空ファイル。 my_package/ ├── init.py ├── mod1.py ├── mod2.py ├── sub_package1 │   ├── init.py │   └── sub_mod1.py └── sub_package2 ├── init.py └── sub_mod2.py

各ファイルの中身を示す。説明は後述します。 mod1.py。

def func():
    print('mod1, func')

mod2.py。

from . import mod1
from .sub_package1 import sub_mod1

def func_same():
    print('from mod2')
    mod1.func()
def func_sub():
    print('from mod2')
    sub_mod1.func()
if __name__ == '__main__':
    func_sub()

sub_package1/sub_mod1.py。

def func():
    print('sub mod1, func1')

sub_package2/sub_mod2.py。

from .. import mod1
from ..sub_package1 import sub_mod1

def func_parent():
    print('from sub mod2')
    mod1.func()
def func_parent_sub():
    print('from sub mod2')
    sub_mod1.func()

同じパッケージ(同じディレクトリ)からインポート

.(ピリオド1つ)が同じディレクトリを表す。 mod2.pyから同じ階層のmod1.pyをインポートする場合、以下のように書きます。

from . import mod1

サブパッケージ(サブディレクトリ)からインポート

下の階層のパッケージ(サブパッケージ)をインポートする場合、同じ階層を示す.に続けてパッケージ名(ディレクトリ名)を書きます。 mod2.pyからsub_package1/sub_mod1.pyをインポートする場合、以下のように書きます。

from .sub_package1 import sub_mod1

上位パッケージ(上位ディレクトリ)からインポート

..(ピリオド2つ)が1つ上のディレクトリを表す。 sub_package2/sub_mod2.pyから上位パッケージのmod1.pyをインポートする場合、以下のように書きます。

from .. import mod1

sub_package2/sub_mod2.pyからsub_package1/sub_mod1.pyをインポートする場合、以下のように書きます。

from ..sub_package1 import sub_mod1

なお、...(ピリオド3つ)はさらに上の階層(2階層上)となり、.を増やすとさらに上の階層を表すことができます。

結果の例

my_packageをインポートした結果の例を示す。 各モジュールから別のモジュールがインポートされ関数が呼び出せていることが分かる。

from my_package import mod2
from my_package.sub_package2 import sub_mod2

mod2.func_same()
# from mod2
# mod1, func

mod2.func_sub()
# from mod2
# sub mod1, func1

sub_mod2.func_parent()
# from sub mod2
# mod1, func

sub_mod2.func_parent_sub()
# from sub mod2
# sub mod1, func1

相対インポートしているパッケージ内のモジュールを単体で実行

テストなどの目的で上述のパッケージmy_package内のモジュールmod2.pyを単体で実行したい場合、python(環境によってはpython3)コマンドで実行するとエラーとなります。

pwd
# /Users/mbp/Documents/my-project/python-snippets/notebook

python3 my_package/mod2.py
# Traceback (most recent call last):
#   File "my_package/mod2.py", line 1, in <module>
#     from . import mod1
# ImportError: cannot import name 'mod1' from '__main__' (my_package/mod2.py)

-mオプションをつけてモジュールとして実行すると問題ありません。my_package/mod2.pyではなくmy_package.mod2と指定します。

python3 -m my_package.mod2
# from mod2
# sub mod1, func1

このとき、カレントディレクトリがmy_packageを含むディレクトリでないとエラーになるので気をつけてください。

cd my_package

python3 -m mod2
# Traceback (most recent call last):
#   File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/runpy.py", line 193, in _run_module_as_main
#     "__main__", mod_spec)
#   File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/runpy.py", line 85, in _run_code
#     exec(code, run_globals)
#   File "/Users/mbp/Documents/my-project/python-snippets/notebook/my_package/mod2.py", line 1, in <module>
#     from . import mod1
# ImportError: attempted relative import with no known parent package

パッケージ外のスクリプトから上位ディレクトリを指定

これまでの例のようにパッケージ内のモジュールではなく、パッケージ外のスクリプトファイルから上位ディレクトリのパッケージをインポートする場合は注意が必要。 以下のような構成でdir内の.pyファイルからmy_package内のモジュールをインポートしたい場合を例とします。 notebook/ ├── dir │ ├── main_absolute.py │ ├── main_relative.py │ └── main_sys_path_append.py ├── main.py └── my_package

なお、上の構成のmain.pyの位置のようにスクリプトファイルがmy_packageと同一階層にあれば何の問題もなくimport my_packageでインポートできます。やむを得ない理由がない限りはスクリプトファイルをインポートしたいパッケージと同一階層においたほうが簡単。 相対インポート .を使った相対インポートだとエラーとなります。

メインモジュールの名前は常に "main" なので、Python アプリケーションのメインモジュールとして利用されることを意図しているモジュールでは絶対 import を利用するべきです。 6. モジュール (module) パッケージ内参照 — Python 3.6.5 ドキュメント

以下のdir/main_relative.pyを例とします。

from .my_package import mod1

mod1.func()

これを実行するとエラー。

pwd
# /Users/mbp/Documents/my-project/python-snippets/notebook

python3 dir/main_relative.py
# Traceback (most recent call last):
#   File "dir/main_relative.py", line 1, in <module>
#     from .my_package import mod1
# ModuleNotFoundError: No module named '__main__.my_package'; '__main__' is not a package

絶対インポート モジュール名を指定して絶対インポートすると、カレントディレクトリによってうまくいったりエラーになったりします。 importの対象ディレクトリ(モジュール探索パス)にはカレントディレクトリも含まれるので、my_packageを直下に含むディレクトリで実行すると正しく処理されるが、移動するとエラーとなります。 以下のdir/main_absolute.pyを例とします。

from my_package import mod1

mod1.func()

my_packageを直下に含むディレクトリから実行すると正しく処理されます。

pwd
# /Users/mbp/Documents/my-project/python-snippets/notebook

python3 dir/main_absolute.py
# mod1, func

移動して実行するとエラーとなります。

cd dir

python3 main_absolute.py
# Traceback (most recent call last):
#   File "main_absolute.py", line 1, in <module>
#     from my_package import mod1
# ModuleNotFoundError: No module named 'my_package'

モジュール探索パスを追加して絶対インポート スクリプトファイルのパスを__file__で取得し、それを基準にimportの対象ディレクトリ(モジュール探索パス)を追加すると、カレントディレクトリによらず正しく処理されます。

以下のdir/main_sys_path_append.pyを例とします。

import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from my_package import mod1

mod1.func()

カレントディレクトリによらず正しく処理されます。

pwd
# /Users/mbp/Documents/my-project/python-snippets/notebook/dir

python3 main_sys_path_append.py
# mod1, func

cd ..

python3 dir/main_sys_path_append.py
# mod1, func

cd ..

python3 notebook/dir/main_sys_path_append.py
# mod1, func
Last Updated: 6/26/2019, 10:34:03 PM