Pythonのパスの書き方

pythonコマンドを使ってプログラムを実行した場合のインポートやファイル参照のパスについて、理解できていなかったので、実際に動かして動作を確認しました。
なお、相対パスはpythonコマンドで実行するモジュールでは使えないという制約があります。

fromの指定

ディレクトリ構造

project_root/
|-- main.py
|-- child_dir/
    |-- sub_main.py
    |-- child_module.py
    |-- child_module2.py
    |-- grandchild_dir/
        |-- grandchild_module.py

ファイルの内容

project_root/main.py
from child_dir.child_module import ChildModule

child = ChildModule()
child()
child.child()
project_root/child_dir/sub_main.py
# import ChildModule

child = ChildModule()
child()
child.child()
project_root/child_dir/child_module.py
# import ChildModuleTwo
# import GrandchildModule

class ChildModule:
    def __call__(self):
        print('ChildModule')
    #;

    def child(self):
        child = GrandchildModule()
        child()
        brother = ChildModuleTwo()
        brother()
    #;
#;
project_root/child_dir/child_module2.py
class ChildModuleTwo:
    def __call__(self):
        print('ChildModuleTwo')
    #;
#;
project_root/child_dir/grandchild_dir/grandchild_module.py
class GrandchildModule:
    def __call__(self):
        print('GrandchildModule')
    #;
#;

main.py実行時

project_rootにて、python3 main.pyを実行する。
この時、child_module.pyimport文が以下だと、エラーになる。

from child_module2 import ChildModuleTwo
# エラー内容
ModuleNotFoundError: No module named 'child_module2'
from grandchild_dir.grandchild_module import GrandchildModule
# エラー内容
ModuleNotFoundError: No module named 'grandchild_dir'

次のようにchild_module.pyから見た相対パスまたは、main.pyから見たパスでgrandchild_moduleを記載すると動作する。

# child_module.pyから見た相対パス
from .child_module2 import ChildModuleTwo
from .grandchild_dir.grandchild_module import GrandchildModule
# main.pyから見たパス
from child_dir.child_module2 import ChildModuleTwo
from child_dir.grandchild_dir.grandchild_module import GrandchildModule

結果

ChildModule
GrandchildModule
ChildModuleTwo

child_dir/sub_main.py実行時

次にproject_rootにて、python3 child_dir/sub_main.pyを実行する。

sub_main.pyのimport文

この時、sub_main.pyimport文が以下だと、エラーになる。

# 実行ディレクトリから見たパス
from child_dir.child_module import ChildModule
# エラー内容
ModuleNotFoundError: No module named 'child_dir'
# sub_main.pyから見た相対パス
from .child_module import ChildModule
# エラー内容
ImportError: attempted relative import with no known parent package

次のように、実行するモジュールからのパスだと正常に動作する。

from child_module import ChildModule

child_module.pyのimport文

この時、child_module.pyimport文が以下だと、エラーになる。

# 相対パス
from .grandchild_dir.grandchild_module import GrandchildModule
# エラー内容
ImportError: attempted relative import with no known parent package
# 実行ディレクトリから見たパス
from child_dir.grandchild_dir.grandchild_module import GrandchildModule
# エラー内容
ModuleNotFoundError: No module named 'child_dir'

次のように、実行するモジュールからのパスだと正常に動作する。

from grandchild_dir.grandchild_module import GrandchildModule

結果

ChildModule
GrandchildModule

sub_main.py実行時

次にchild_dirにて、python3 sub_main.pyを実行する。
この時の結果はproject_rootpython3 child_dir/sub_main.py実行時と同じだった。

結論

import文はpythonコマンドで実行するモジュールからのパスで記載するか、importされたファイルから見た相対パスを記載する。

importされたファイルと同一ディレクトリにあるモジュールの相対パスを記載時は
from .パッケージ名 import モジュール名
となる。

pythonコマンドで実行するモジュールだけでなく、実行するモジュールと同じディレクトリにあるモジュールをimportした場合も、そのファイル内で相対パスは使えない。

ファイルパスの指定

ディレクトリ構造

project_root/
|-- main.py
|-- sample.txt
|-- child_dir/
    |-- sub_main.py
    |-- child_module.py
    |-- sample2.txt
    |-- grandchild_dir/
        |-- sample3.txt
        |-- grandchild_module.py
project_root/main.py
from __future__ import with_statement
from child_dir.child_module import ChildModule

file_path = 'sample.txt'
with open(file_path, 'r') as f_in:
    file_contents = f_in.read()
#;
print(file_contents)

child = ChildModule()
child.read()
child.read_child()
project_root/child_dir/sub_main.py
from child_module import ChildModule

file_path = 'sample2.txt'
with open(file_path, 'r') as f_in:
    file_contents = f_in.read()
#;
print(file_contents)

child = ChildModule()
child.read()
child.read_child()
project_root/child_dir/grandchild_dir/child_module.py
from __future__ import with_statement
from .grandchild_dir.grandchild_module import GrandchildModule

file_path = 'sample2.txt'
class ChildModule:
    def read(self):
        with open(file_path, 'r') as f_in:
            file_contents = f_in.read()
        #;
        print(file_contents)
    #;

    def read_child(self):
        child = GrandchildModule()
        child.read()
    #;
#;
project_root/child_dir/grandchild_module.py
file_path = 'sample3.txt'
class GrandchildModule:
    def read(self):
        with open(file_path, 'r') as f_in:
            file_contents = f_in.read()
        #;
        print(file_contents)
    #;
#;

main.py実行時

project_rootにて、python3 main.pyを実行する。
main.pyfile_pathは以下のどちらでも正常に動作する。

file_path = 'sample.txt'
file_path = './sample.txt'

この時、child_module.pyfile_pathは以下だとエラーになる。

file_path = 'sample2.txt'
file_path = './sample2.txt'

次のように、main.pyから見たパスだと正常に動作する。

file_path = 'child_dir/sample2.txt'
file_path = './child_dir/sample2.txt'

grandchild_module.pyfile_pathは以下だとエラーになる。

file_path = 'sample3.txt'
file_path = './sample3.txt'
file_path = 'grandchild_dir/sample3.txt'
file_path = './grandchild_dir/sample3.txt'

次のように、main.pyから見たパスだと正常に動作する。

file_path = 'child_dir/grandchild_dir/sample3.txt'
file_path = './child_dir/grandchild_dir/sample3.txt'

child_dir/sub_main.py実行時

次にproject_rootにて、python3 child_dir/sub_main.pyを実行する。
この時、sub_main.pyfile_pathが以下だと、エラーになる。

file_path = 'sample2.txt'
file_path = './sample2.txt'

次のように、pythonコマンドを実行したディレクトリ(project_root)から見たパスだと正常に動作する。

file_path = 'child_dir/sample2.txt'
file_path = './child_dir/sample2.txt'

同様にchild_module.pyfile_pathもpythonコマンドを実行したディレクトリ(project_root)から見たパスだと正常に動作する。

grandchild_module.pyfile_pathも以下だとエラーになる。

file_path = 'sample3.txt'
file_path = './sample3.txt'
file_path = 'grandchild_dir/sample3.txt'
file_path = './grandchild_dir/sample3.txt'

次のように、pythonコマンドを実行したディレクトリ(project_root)から見たパスだと正常に動作する。

file_path = 'child_dir/grandchild_dir/sample3.txt'
file_path = './child_dir/grandchild_dir/sample3.txt'

sub_main.py実行時

次にchild_dirにて、python3 sub_main.pyを実行する。
この時、sub_main.pyfile_pathが以下だと、エラーになる。

file_path = 'child_dir/sample2.txt'
file_path = './child_dir/sample2.txt'

次のように、sub_main.pyから見たパスだと正常に動作する。

file_path = 'sample2.txt'
file_path = './sample2.txt'

同様にchild_module.pyfile_pathもpythonコマンドを実行したディレクトリ(child_dir)から見たパスだと正常に動作する。

grandchild_module.pyfile_pathは以下だとエラーになる。

file_path = 'child_dir/grandchild_dir/sample3.txt'
file_path = './child_dir/grandchild_dir/sample3.txt'
file_path = 'sample3.txt'
file_path = './sample3.txt'

次のように、pythonコマンドを実行したディレクトリ(child_dir)から見たパスだと正常に動作する。

file_path = 'grandchild_dir/sample3.txt'
file_path = './grandchild_dir/sample3.txt'

結論

ファイルパスはpythonコマンドを実行するディレクトリからのパスで記載する。

FastAPI

ディレクトリ構成

project_root/
|-- main.py
|-- child_dir/
    |-- sub_main.py
    |-- child_module.py
    |-- grandchild_dir/
        |-- grandchild_module.py

ファイルの内容

project_root/main.py
from fastapi import FastAPI
from child_dir.child_module import ChildModule

app = FastAPI()

@app.get("/")
def get_root():
    child = ChildModule()
    child()
    child.child()
    return { "message": 'ok' }
#;

project_root/main.pyでは、相対パスのimport文だとエラーになる。

from .child_dir.child_module import ChildModule
project_root/child_dir/sub_main.py
from fastapi import FastAPI
# import ChildModule

app = FastAPI()

@app.get("/")
def get_root():
    child = ChildModule()
    child()
    child.child()
    return { "message": 'ok' }
#;
project_root/child_dir/child_module.py
# import ChildModuleTwo
# import GrandchildModule

class ChildModule:
    def __call__(self):
        print('ChildModule')
    #;

    def child(self):
        child = GrandchildModule()
        child()
        brother = ChildModuleTwo()
        brother()
    #;
#;
project_root/child_dir/grandchild_dir/grandchild_module.py
class GrandchildModule:
    def __call__(self):
        print('GrandchildModule')
    #;
#;

main.py実行時

project_rootにて、uvicorn main:app --host 0.0.0.0 --port 8000を実行する。

この時、child_module.pyimport文が以下だと、エラーになる。

from child_module2 import ChildModuleTwo
from grandchild_dir.grandchild_module import GrandchildModule

次のいずれかであれば、importできる。(通常のpython実行時と同じ)

from .child_module2 import ChildModuleTwo
from .grandchild_dir.grandchild_module import GrandchildModule
from child_dir.child_module2 import ChildModuleTwo
from child_dir.grandchild_dir.grandchild_module import GrandchildModule

child_dir/sub_main.py実行時

次にproject_rootにて、uvicorn child_dir.sub_main:app --host 0.0.0.0 --port 8000を実行する。 この時、sub_main.pyimport文が以下だと、エラーになる。

# 実行ファイルから見たパス
from child_module import ChildModule

実行ディレクトリから見たパスまたは、実行ファイルから見た相対パスだとimportできる。

from child_dir.child_module import ChildModule
from .child_module import ChildModule
サブディレクトリのファイルを実行の場合、相対パスがOKとなっている。

importされたファイルでは、実行ファイルから見たパスではimportできない。また、importされたファイルが、

  • 実行ファイルと同じディレクトリの場合、実行ディレクトリからのパスでimport文を記述できる
  • 実行ファイルのサブディレクトリの場合、実行ディレクトリからのパスまたは、対象ファイルからの相対パスでimport文を記述できる

sub_main.py実行時

次にchild_dirにて、python3 sub_main.pyを実行する。
この時、sub_main.pyimport文が実行ファイルからの相対パスだと、エラーになる。

from .child_module import ChildModule

実行ディレクトリ(=実行ファイル)から見たパスだとimportできる。

from child_module import ChildModule

importされたファイルでは、

  • 実行ディレクトリと同じディレクトリ(=実行ファイルと同じディレクトリ)の場合、実行ディレクトリからのパスでimport文を記述できる
  • サブディレクトリの場合、実行ディレクトリからのパスまたは、対象ファイルからの相対パスでimport文を記述できる

結論

uvicorn実行時はサブディレクトリに実行ファイルを配置することで、全て相対パスでの記述が可能になる。
このエントリーをはてなブックマークに追加
にほんブログ村 IT技術ブログへ

コメント

メールアドレスが公開されることはありません。 が付いている欄は必須項目です