web-dev-qa-db-ja.com

Python 3.5+:完全なファイルパスを指定してモジュールを動的にインポートする方法(暗黙的な兄弟インポートがある場合)?

質問

標準ライブラリでは、明確にドキュメント化されています ソースファイルを直接インポートする方法 (ソースファイルへの絶対ファイルパスを指定した場合) 。

暗黙の兄弟インポートが存在する場合に、この例をどのように適合させることができますか?

私はすでにトピックについて this および this other Stackoverflowの質問をチェックアウトしましたが、暗黙の兄弟インポートwithin手動でインポートされるファイル。

セットアップ/例

これが実例です

ディレクトリ構造:

_root/
  - directory/
    - app.py
  - folder/
    - implicit_sibling_import.py
    - lib.py
_

_app.py_:

_import os
import importlib.util

# construct absolute paths
root = os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
isi_path = os.path.join(root, 'folder', 'implicit_sibling_import.py')

def path_import(absolute_path):
   '''implementation taken from https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly'''
   spec = importlib.util.spec_from_file_location(absolute_path, absolute_path)
   module = importlib.util.module_from_spec(spec)
   spec.loader.exec_module(module)
   return module

isi = path_import(isi_path)
print(isi.hello_wrapper())
_

_lib.py_:

_def hello():
    return 'world'
_

_implicit_sibling_import.py_:

_import lib # this is the implicit sibling import. grabs root/folder/lib.py

def hello_wrapper():
    return "ISI says: " + lib.hello()

#if __name__ == '__main__':
#    print(hello_wrapper())
_

_python folder/implicit_sibling_import.py_ブロックをコメントアウトして_if __name__ == '__main__':_を実行すると、_ISI says: world_ in Python 3.6。

ただし、_python directory/app.py_を実行すると次の結果が得られます。

_Traceback (most recent call last):
  File "directory/app.py", line 10, in <module>
    spec.loader.exec_module(module)
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 205, in _call_with_frames_removed
  File "/Users/pedro/test/folder/implicit_sibling_import.py", line 1, in <module>
    import lib
ModuleNotFoundError: No module named 'lib'
_

Workaround

import sys; sys.path.insert(0, os.path.dirname(isi_path))を_app.py_に追加すると、_python app.py_は意図したとおりworldを生成しますが、可能であれば_sys.path_を変更しないでください。

回答要件

_python app.py_で_ISI says: world_を出力し、_path_import_関数を変更することでこれを達成したいと思います。

_sys.path_をマングルすることの意味がわかりません。例えば。 _directory/requests.py_があり、directoryへのパスを_sys.path_に追加した場合、インポートする代わりに_import requests_が_directory/requests.py_のインポートを開始したくない 要求ライブラリ _pip install requests_でインストールしたもの。

ソリューション[〜#〜] [[##〜]は、python目的のモジュールへのファイルパスで、 モジュールオブジェクト を返します。

理想的には、ソリューションは副作用を導入すべきではありません(たとえば、_sys.path_を変更する場合、_sys.path_を元の状態に戻す必要があります)。解決策が副作用を引き起こす場合、副作用を導入しないと解決策を達成できない理由を説明する必要があります。


PYTHONPATH

これを行う複数のプロジェクトがある場合、プロジェクトを切り替えるたびにPYTHONPATHを設定することを覚えておく必要はありません。ユーザーは、プロジェクトを_pip install_実行し、追加のセットアップなしで実行できる必要があります。

_-m_

_-m_フラグ が推奨/ Pythonのアプローチですが、標準ライブラリには明確に文書化されています ソースファイルを直接インポートする方法 。暗黙の相対的なインポートに対処するために、このアプローチをどのように適応させることができるか知りたいです。明らかに、Pythonの内部はこれを行う必要があります。したがって、内部は「ソースファイルを直接インポートする」ドキュメントとどのように異なりますか?

29
Pedro Cattori

PYTHONPATH 環境変数にアプリケーションのパスを追加します

モジュールファイルのデフォルトの検索パスを増やします。形式は、シェルのPATHと同じです。os.pathsepで区切られた1つ以上のディレクトリパス名(例:UnixのコロンまたはWindowsのセミコロン)。存在しないディレクトリは黙って無視されます。

bashでは次のようになります。

export PYTHONPATH="./folder/:${PYTHONPATH}"

または直接実行:

PYTHONPATH="./folder/:${PYTHONPATH}" python directory/app.py
6
ShmulikA

OPのアイデアは素晴らしいです。この例では、sys.modulesに適切な名前の兄弟モジュールを追加することでのみ機能します。PYTHONPATHを追加するのと同じだと思います。テスト済みで、バージョン3.5.1で動作します。

import os
import sys
import importlib.util


class PathImport(object):

    def get_module_name(self, absolute_path):
        module_name = os.path.basename(absolute_path)
        module_name = module_name.replace('.py', '')
        return module_name

    def add_sibling_modules(self, sibling_dirname):
        for current, subdir, files in os.walk(sibling_dirname):
            for file_py in files:
                if not file_py.endswith('.py'):
                    continue
                if file_py == '__init__.py':
                    continue
                python_file = os.path.join(current, file_py)
                (module, spec) = self.path_import(python_file)
                sys.modules[spec.name] = module

    def path_import(self, absolute_path):
        module_name = self.get_module_name(absolute_path)
        spec = importlib.util.spec_from_file_location(module_name, absolute_path)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
        return (module, spec)

def main():
    pathImport = PathImport()
    root = os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
    isi_path = os.path.join(root, 'folder', 'implicit_sibling_import.py')
    sibling_dirname = os.path.dirname(isi_path)
    pathImport.add_sibling_modules(sibling_dirname)
    (lib, spec) = pathImport.path_import(isi_path)
    print (lib.hello())

if __name__ == '__main__':
    main()
1
Gang

試してください:

export PYTHONPATH="./folder/:${PYTHONPATH}"

または直接実行:

PYTHONPATH="./folder/:${PYTHONPATH}" python directory/app.py

ルートがPYTHONPATHで明示的に検索されるフォルダーにあることを確認してください。絶対インポートを使用します。

from root.folder import implicit_sibling_import #called from app.py
1
  1. ルートがPYTHONPATHで明示的に検索されているフォルダーにあることを確認してください
  2. 絶対インポートを使用します。

    root.folderからインポートimplicit_sibling_import#app.pyから呼び出される

1