LoginSignup
17
16

More than 1 year has passed since last update.

【python再入門】親ディレクトリを経由したimportを行う方法

Last updated at Posted at 2020-01-08

pythonで自作モジュールをimportする際の対処方法がよくわからなくなってしまったのでメモ

背景

pythonに限らずプラグラミングをしていると、最初1ファイルで記載していた処理を複数ファイルに分割したくなってきます。
そしてそのうち分割したファイルをディレクトリに分けて管理したくなります。
これらのディレクトリに分けて管理を始めたファイルをimportする際に少し工夫が必要で手間取ったので対応方法を残しておきます。

なお、細かいですが、パターンが以下の2つに別れます。この記事では1の方について記載します。
1.実行ファイル内で親ディレクトリ経由のパスにあるモジュールをimportしたい場合

.
├package1
│  ├__init__.py
│  └module1.py # <= 実行ファイル
└package2
   ├__init__.py
   └module2.py

↑実行ファイルがmodule1.pyで、module1.pyからmodule2.pyを呼び出したい場合

2.実行ファイルがimportしているモジュールの中で、親ディレクトリ経由のパスにあるモジュールをimportしたい場合

.
├package1
│  ├__init__.py
│  └module1.py
├package2
│  ├__init__.py
│  └module2.py
└module3.py # <= 実行ファイル

↑実行ファイルがmodule3.pyで、module3.pyがmodule1.pyをimport、module1.pyがmodule2.pyをimportしたい場合

もう少し具体的に

共通処理の切り出しまで

pythonで何か機能を開発するとき、最初は1ファイルだったのが、2ファイル、3ファイルと増えていくに連れ、共通処理を切り出して、モジュール化したくなると思います。

初期状態

python_import_test
├functionA.py
├functionB.py
└functionC.py


共通処理をcommonパッケージに切り出し

python_import_test
├common
│  ├util.py
│  └__init__.py # <= python3.3以降は__init__.pyなしでもimport可能
├functionA.py
├functionB.py
└functionC.py

common/util.pyの中身

common/util.py
import os


def test():
    print("this method is in %s" % os.path.basename(__file__))

functionA.pyの中身

functionA.py
from common import util


print("this is %s" % __file__)
util.test()

実行

$ pwd
/python_import_test

$ python functionA.py
this is functionA.py
this method is in util.py

本題:ディレクトリを分けて管理をしたい

ここまでは特に問題ないと思うのですが、この後さらにファイルが増え、以下のようにfunctionごとにディレクトリを分けて管理したくなってきます。

ディレクトリ管理前

.
├common
│  ├db.py
│  └__init__.py
├functionA_1.py
├functionA_2.py
├functionA_3.py
├functionB_1.py
├functionB_2.py
└functionB_3.py


ディレクトリ管理後

.
├common
│  ├util.py
│  └__init__.py
├functionA
│ ├functionA_1.py
│ ├functionA_2.py
│ └functionA_3.py
└functionB
  ├functionB_1.py
  ├functionB_2.py
  └functionB_3.py

試しに以下のように記載して実行しようとしてみたところ、エラーが発生します。

functionA/functionA_1.py
from ..common import util

print("this is %s" % __file__)
util.test()
$ pwd
/python_import_test/functionA

$ python functionA_1.py
Traceback (most recent call last):
  File "functionA_1.py", line 1, in <module>
    from ..common import util
ImportError: attempted relative import with no known parent package

pythonは実行時にsys, built-in, mainの3つを初期化します。
mainの初期化の際に実行ファイルの上位ディレクトリ(上の例で言うfunctionAディレクトリ)はモジュールの探索パスに含まれないので、いざimportしようとしても探索にひっかからずエラーとなってしまいます。

解決策

自分で調べた限りだと以下2つの解決策がありました

  1. モジュール探索パスを追加する
  2. 親ディレクトリを環境変数PYTHONPATHに通す

1. モジュール探索パスを追加

pythonではimportはモジュールの探索 => ロードの順に実行されます
モジュールの探索対象のパスがsys.pathに格納されているので、そこに親ディレクトリのパスを追加してあげると正常にimportが実行されます

functionA/functionA_1.py
import os
import sys
sys.path.append(os.pardir)
from common import util

print("this is %s" % __file__)
util.test()
$ pwd
/python_import_test/functionA

$ python functionA_1.py
this is functionA_1.py
this method is in util.py

ただし、これはPythonのコーディング規約であるPEP8に違反しており(規約ではfromとimportをファイルの一番上に記載することになっているが、sys.path.appendを先に記載しないとimportができないため)、文法チェックツールを使っていると怒られます(動作自体はするのであまり問題になりませんが)

2. 親ディレクトリを環境変数PYTHONPATHに通す

sys.pathをいじるのが邪道だと感じる場合はこちらの方法があります(おそらくこっちが王道?)
ローカル実行のみであれば、.bashrcに記載しておけば良いのですが、Dockerコンテナ内で.pyファイルを動かしたい場合は、ENVコマンド内で設定してあげる必要があります。

.bashrc

export PYTHONPATH="<python_import_testまでのフルパス>:$PYTHONPATH"

Dockerfile

ENV PYTHONPATH "<python_import_testまでのフルパス>:$PYTHONPATH"
functionA/functionA_1.py
from common import util
print("this is %s" % __file__)
util.test()

環境変数の設定は手間なので固く作るときは2を、それ以外は1を使う感じかなと思いました。

17
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
16