note.nkmk.me

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

Date: 2018-07-22 / tags: Python
このエントリーをはてなブックマークに追加

パッケージを自作する場合、パッケージ内のモジュールから上位ディレクトリ(親ディレクトリ)や下位ディレクトリ(サブディレクトリ)にあるほかのモジュールをインポートしたいことがある。

そのようなときは、相対パスで相対的な位置を指定してインポート(相対インポート)することができる。

相対インポートはパッケージ内の仕組みなので、パッケージ外のスクリプトファイルから上位ディレクトリを指定してパッケージやモジュールをインポートするには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')
source: mod1.py

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()
source: mod2.py

sub_package1/sub_mod1.py

def func():
    print('sub mod1, func1')
source: sub_mod1.py

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()
source: sub_mod2.py

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

.(ピリオド1つ)が同じディレクトリを表す。

mod2.pyから同じ階層のmod1.pyをインポートする場合、以下のように書く。

from . import mod1
source: mod2.py

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

下の階層のパッケージ(サブパッケージ)をインポートする場合、同じ階層を示す.に続けてパッケージ名(ディレクトリ名)を書く。

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

from .sub_package1 import sub_mod1
source: mod2.py

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

..(ピリオド2つ)が1つ上のディレクトリを表す。

sub_package2/sub_mod2.pyから上位パッケージのmod1.pyをインポートする場合、以下のように書く。

from .. import mod1
source: sub_mod2.py

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

from ..sub_package1 import sub_mod1
source: sub_mod2.py

なお、...(ピリオド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オプションをつけてモジュールとして実行するとOK。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
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事