note.nkmk.me

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

Date: 2018-07-22 / Modified: 2019-07-19 / tags: Python

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

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

パッケージ内のファイルのように、他のファイルからモジュールとしてインポートされるファイルの中では相対パスを使えるが、pythonコマンドなどで実行されるファイルの中では相対パスは使えないので注意。

ここでは、まず、パッケージ内のモジュールから別ディレクトリのモジュールをインポートする方法について説明する。

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

次に、パッケージ外のファイル、pythonコマンドなどで直接実行されるファイル(メインファイル)から別ディレクトリにあるモジュールやパッケージをインポートする方法について説明する。メインファイルというのは便宜的に呼んでいるだけで、特に一般的な名称ではない(たぶん)。

  • メインファイルで別ディレクトリからインポート
    • 同じディレクトリからインポート
    • 下位ディレクトリからインポート
    • 上位ディレクトリ(親ディレクトリ)からインポート
      • 相対インポート
      • 絶対インポート
      • モジュール探索パスを追加して絶対インポート

なお、自作のパッケージやモジュールを標準ライブラリやpipでインストールしたサードパーティライブラリのように使いまわしたい場合は、環境変数PYTHONPATHでモジュール探索パスを追加するのが便利。以下の記事を参照。

importの基本については以下の記事を参照。

スポンサーリンク

自作パッケージの例

以下の構造のパッケージ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 is called')
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 is called')
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 is called

mod2.func_sub()
# from mod2
# -- sub_mod1.func1 is called

sub_mod2.func_parent()
# from sub_mod2
# -- mod1.func is called

sub_mod2.func_parent_sub()
# from sub_mod2
# -- sub_mod1.func1 is called

モジュール内の関数などのオブジェクトを直接インポートすることも可能。

from my_package.mod2 import func_same, func_sub
from my_package.sub_package2.sub_mod2 import func_parent, func_parent_sub

func_same()
# from mod2
# -- mod1.func is called

func_sub()
# from mod2
# -- sub_mod1.func1 is called

func_parent()
# from sub_mod2
# -- mod1.func is called

func_parent_sub()
# from sub_mod2
# -- sub_mod1.func1 is called

なお、相対インポートではpythonコマンドで実行したファイルより上の階層には遡れない。

例えば、my_package内にsub_package2/sub_mod2.pyをインポートするmain.pyを作成した場合。

from sub_package2 import sub_mod2

sub_mod2.func_parent()
source: main.py

これを実行するとエラーになってしまう。

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

python3 main.py
# Traceback (most recent call last):
#   File "main.py", line 1, in <module>
#     from sub_package2 import sub_mod2
#   File "/Users/mbp/Documents/my-project/python-snippets/notebook/my_package/sub_package2/sub_mod2.py", line 1, in <module>
#     from .. import mod1
# ValueError: attempted relative import beyond top-level package

エラーメッセージを読むと、sub_package2/sub_mod2.pyはインポートできているが、sub_mod2.pyの中で上位ディレクトリからmod1.pyを相対インポートしている部分でエラーが発生していることが分かる。

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

テストなどの目的で、上述のパッケージ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 is called

このとき、カレントディレクトリがmy_packageを含むディレクトリ(my_packageの一つ上の階層)でないとエラーになるので注意。

cd my_package

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

python3 -m mod2
# Traceback (most recent call last):
#   File "/usr/local/Cellar/python/3.7.4/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.4/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

メインファイルで別ディレクトリからインポート

これまでの例のようにパッケージ内のモジュール(ファイル)ではなく、パッケージ外のスクリプトファイル、pythonコマンドなどで直接実行されるファイルから別ディレクトリのモジュールやパッケージをインポートする場合について説明する。

以下のような構成を例とする。

dir_import_test/
├── dir
│   ├── main_absolute.py
│   ├── main_relative.py
│   └── main_sys_path_append.py
├── dir_for_mod
│   └── mod2.py
├── main_base.py
└── mod1.py

同じディレクトリからインポート

main_base.pyからmod1.pyをインポートする例。同じディレクトリにあるモジュール(ファイル)は特別な指定は必要なくそのままインポートできる。

モジュールをインポート。

import mod1

mod1.func()
# -- mod1.func is called
source: main_base.py

モジュール内の関数をインポート。

from mod1 import func

func()
# -- mod1.func is called
source: main_base.py

下位ディレクトリからインポート

main_base.pyからdir_for_mod/mod2.pyをインポートする例。Python3.3以降では、__init__.pyを含まないディレクトリもパッケージとしてインポートできるようになった。3.2以前はdir_for_mod__init__.pyがないとエラーになるので注意。

モジュールをインポート。

import dir_for_mod.mod2

dir_for_mod.mod2.func()
# -- dir_for_mod.mod2.func is called
source: main_base.py
from dir_for_mod import mod2

mod2.func()
# -- dir_for_mod.mod2.func is called
source: main_base.py

モジュール内の関数をインポート。

from dir_for_mod.mod2 import func

func()
# -- dir_for_mod.mod2.func is called
source: main_base.py

上位ディレクトリ(親ディレクトリ)からインポート

dir内にあるスクリプトファイルから上位ディレクトリ(親ディレクトリ)のmod1.pydir_for_mod/mod2.pyをインポートする場合。

以下に説明するように、上位ディレクトリからインポートするのは面倒。強い理由がなければインポートしたいモジュールやパッケージと同じ階層または上の階層にメインファイルを置いたほうが楽。

また、冒頭でも書いたように、自作のパッケージやモジュールを標準ライブラリやpipでインストールしたサードパーティライブラリのように使いまわしたい場合は、環境変数PYTHONPATHでモジュール探索パスを追加するのが便利。以下の記事を参照。

相対インポート

相対インポートはパッケージ内の仕組みであり、パッケージ外のスクリプトファイル、つまりpythonコマンドで実行するようなスクリプトファイルでは使えない。

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

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

from .. import mod1
from ..dir_for_mod import mod2

mod1.func()
mod2.func()

これをpythonコマンドで実行するとエラー。

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

python3 dir/main_relative.py
# Traceback (most recent call last):
#   File "dir/main_relative.py", line 1, in <module>
#     from .. import mod1
# ValueError: attempted relative import beyond top-level package

上述のように、上の階層のディレクトリからpythonコマンドに-mオプションを付けてモジュールとして実行すると動作する。

cd ..

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

python3 -m dir_import_test.dir.main_relative
# -- mod1.func is called
# -- dir_for_mod.mod2.func is called

絶対インポート

以下のdir/main_absolute.pyを例とする。

import mod1
from dir_for_mod import mod2

mod1.func()
mod2.func()

importの対象ディレクトリ(モジュール探索パス)にはカレントディレクトリ(作業ディレクトリ=Pythonを起動したディレクトリ)が含まれる。したがって、カレントディレクトリがmod1.pyおよびdir_for_modを含むディレクトリであればインポートできる。

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

python3 dir/main_absolute.py
# -- mod1.func is called
# -- dir_for_mod.mod2.func is called

カレントディレクトリが異なるとエラーとなってしまう。

cd dir

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

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

モジュール探索パスを追加して絶対インポート

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

以下のdir/main_sys_path_append.pyを例とする。モジュール探索パスを追加してから親ディレクトリのファイルを絶対インポートする。

import os
import sys

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

import mod1
from dir_for_mod import mod2

mod1.func()
mod2.func()

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

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

python3 dir/main_sys_path_append.py
# -- mod1.func is called
# -- dir_for_mod.mod2.func is called

cd dir

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

python3 main_sys_path_append.py
# -- mod1.func is called
# -- dir_for_mod.mod2.func is called

cd ../..

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

python3 dir_import_test/dir/main_sys_path_append.py
# -- mod1.func is called
# -- dir_for_mod.mod2.func is called
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事