Python で親フォルダのファイルを import する
◆ 親フォルダを .. で参照できない
◆ sys.path の追加が必要
◆ 相対パスできるような仕組みをつくってみた
◆ sys.path 以下しかインポートできないのでメインモジュールの場所は一番上の階層に固定
◆ そこからの「.」区切りパスに変換してインポート
◆ __main__ をインポートして その import_module 関数を使って相対パスでインポートする
◆ 相対パスにしなくてもパッケージルートからのパスでインポートで十分そう
◆ sys.path の追加が必要
◆ 相対パスできるような仕組みをつくってみた
◆ sys.path 以下しかインポートできないのでメインモジュールの場所は一番上の階層に固定
◆ そこからの「.」区切りパスに変換してインポート
◆ __main__ をインポートして その import_module 関数を使って相対パスでインポートする
◆ 相対パスにしなくてもパッケージルートからのパスでインポートで十分そう
Python のインポート
こういうフォルダ構造があるとします- a.py
- d/
- m.py
- b.py
- s/
- c.py
m.py がメインのファイルで これを実行します
python3 m.py
m.py では a.py, b.py, c.py を import したいです
b.py と c.py は単純にこれでインポートできます
import b
import s.c
print(b.name)
print(s.c.name)
「import」 の後に書くのは相対パスではなく sys.path のパスから探すパッケージ名です
最初に実行したメインモジュールがあるフォルダは sys.path に追加されます
なので m.py と同じフォルダの b.py は b という名前でインポートできます
また フォルダ s の中の c.py は s.c でインポートできます
sys.path から見つけられるパッケージの内側のモジュールはアクセスできますが sys.path にあるパスより外側はアクセスできません
from を使って 「..」 を指定することでパス的には親の指定ができます
しかし Python3 からは相対パスで親フォルダの指定は禁止されたようです
やってみても
from .. import a
ImportError: attempted relative import with no known parent package
というエラーです
インポートするためには sys.path を追加するしかないです
import sys
import os
import b
import s.c
sys.path.append(os.path.abspath(".."))
import a
print(a.name)
print(b.name)
print(s.c.name)
A
B
C
a.py があるのは m.py から一つ親なので 「..」 の abspath を sys.path に追加しました
こうした場合 a.py があるフォルダも m.py があるフォルダも sys.path に存在します
そうなると b.py は d.b という名前でも b という名前でもインポートできます
import d.b
import b
print(d.b.__file__ == b.__file__)
print(d.b)
print(b)
True
<module 'd.b' from 'C:\\Users\\winuser\\code\\562\\d\\b.py'>
<module 'b' from 'C:\\Users\\winuser\\code\\562\\d\\b.py'>
こういうことができるので インポートする .py ファイルを含む全部のフォルダを sys.path に追加するのは良い方法ではありません
ファイル名が重複する問題もあります
相対パスでインポートする
そういう面倒なのをなくして Node.js のように相対パスでインポートできるようにしたいです良い方法ないかなと考えていて こういうものを作ってみました
[m.py]
import os
import importlib
base_path = os.path.dirname(os.path.abspath(__file__))
def import_module(path, file):
dir = os.path.dirname(file)
module_path = os.path.join(dir, path)
rel = os.path.relpath(module_path, base_path)
if rel.startswith(".."):
raise Exception("module path is out of root, " + rel)
return importlib.import_module(rel.replace(os.sep, "."))
###
c = import_module("s/c", __file__)
print("c", c)
[c.py]
import __main__
b = __main__.import_module("../b", __file__)
print("b", b)
>py m.py
b <module 'b' from 'C:\\Users\\winuser\\code\\562\\d\\b.py'>
c <module 's.c' from 'C:\\Users\\winuser\\code\\562\\d\\s\\c.py'>
m.py が s/c.py をインポートして s/c.py が b.py をインポートします
メインの m.py に import_module 関数を作っていて これを使います
1 つ目の引数には相対パスを 2 つ目の引数には __file__ を渡します
__file__ は相対パスの解決用です
実際のインポート処理を m.py で行うので 別階層だと相対パスが変わります
インポート時に abspath とか使って絶対パスに固定するのも面倒なので __file__ を渡す形にしました
import_module は c.py のように __main__ をインポートして使います
__main__ はメインのモジュールを指すので m.py になります
sys.path は自動で追加されるメインモジュールのフォルダのみで手動で追加はしません
メインモジュールからの相対パスを 「.」 区切りの s.c などに変換してインポートします
つまり これだと a.py はインポートできません
メインモジュールより上の階層のモジュールをインポートしなくて良いように最も上の階層にメインモジュールを配置します
m.py 自体の位置を変えたくないなら a.py の階層に main.py を作って main.py から m.py をインポートするように変更します
こうすることで 自分のファイルからの相対パスで親を含むモジュールをインポートできるようになりました
パッケージ
色々工夫しましたが 準備が面倒なのが欠点ですimport_module 関数を定義したメインモジュールをトップ階層に配置して それをエントリポイントとして起動しないといけないですし
もっとシンプルにできていたならともかく ここまで来ると相対パスにこだわって特殊なことをするよりパッケージとして考えて パッケージルートからのパスでインポートするほうが楽だと思いました
- main.py
- dir1/
- f1.py
- dir2/
- f2.py
こういうフォルダ構造で main.py が f2.py をインポートして f2.py が f1.py をインポートします
[main.py]
import dir1.dir2.f2
[f2.py]
import dir1.f1
[f1.py]
print("loaded")
>py main.py
loaded
main.py が f2.py をインポートするときは dir1.dir2.f2
f2.py が f1.py をインポートするときは dir1.f1
という指定です
main.py があるフォルダが sys.path に含まれているので そこからのパスとします
自分から相対パスだから 「..」 という考え方はしません
この場合も sys.path に手動追加をしなくて済むように main.py はそれより上の階層を見なくて良いパッケージルートに配置します