如何使用 python importlib 从相对路径动态 import_module

How to dynamically import_module from a relative path with python importlib

我的文件夹结构如下:

__init__.py
core/
    fields.py
    managers.py
    models.py
    __init__.py
models/
    products.py
    suppliers.py
    __init__.py

From class A in fields.py 我正在尝试加载 class Supplier using importlib.load_module from which is defined in products.py or suppliers.py.这需要填充一些配置以生成一些 sql.

下面的方法生成 TypeError: the 'package' argument is required to perform a relative import for '..models.suppliers.Supplier''。我不确定如何定义包,因为这不是已安装的包。

import importlib

model_name = "Supplier"
path = f"...models.{model_name.lower()}s.{model_name}"
model = importlib.import_module(path)

为了尝试解决这个问题,我尝试了如下所示的各种组合,但均无济于事。错误 ModuleNotFoundError: No module named '.core'ModuleNotFoundError: No module named '.models' 取决于 .. 我正在使用的组合。

import importlib

model_name = "Supplier"
path = f"...models.{model_name.lower()}s.{model_name}"
model = importlib.import_module(path, package=".core.fields")
path = f"{model_name.lower()}s"
model = importlib.import_module(path, package="..models")

也不起作用 - 但简单地导入 from ..models import Supplier 可以。

我也尝试让 python 使用绝对路径。但是通过使用这种方法,错误变为:ImportError: attempted relative import with no known parent package(参考suppliers.py中的导入)。

在这种情况下,suppliers.py 中使用的相对导入似乎失败了。请参阅下面的示例。

from importlib.machinery import SourceFileLoader
from pathlib import Path
import os

model_name = "Supplier"
parent_path = Path(__file__).resolve().parent.parent
module_path = os.path.join(parent_path, 'models', f'{model_name.lower()}s.py')
Model = getattr(SourceFileLoader(model, module_path).load_module(), model_name)

编辑:为了完整起见,抛出 ModuleNotFoundError: No module named '.'。所以也不行。

model = __import__(f"..models.{model.lower()}.{model}")

我错过了什么?

以下是文档中有关如何在给定文件路径的情况下加载模块的方法: https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly

...
import importlib

spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)


不使用路径库:

(我保留这个是因为文件名操作和目录处理的例子可能仍然需要,即使按照上面的方法)

的确,在给定 Python 中的 file-path 的情况下,导入文件的正确方法很复杂。无需搜索其他食谱,这里有一种方法 临时修补 sys.path,并调用普通 __import__ 从指定的目录

获取文件

(Python 文件的路径应使用文件系统符号给出:/ 分隔符,而不是 .。不过,此代码将忽略文件扩展名)

import sys
from pathlib import Path

def importpath(path):
    strpath = str(path)
    if not strpath.startswith("/"):
        parent_path = Path(sys._getframe().f_globals.get("__file__", ".")).parent
        path = parent_path / path
    else:
        path = Path(path)
    try:
        sys.path.insert(0, str(path.parent))
        module = __import__(path.stem)
    finally:
        sys.path.pop(0)
    return module

但是,此代码会将给定文件作为单独的 Python 模块导入,而不知道它应该在哪个包中。这意味着如果该模块中有任何相关导入,它们将失败.

阅读更多内容后,发现解决方案就在眼前。

您可以使用 getattr

而不是尝试使用 importlib 做有趣的事情
def find_my_class(class_name):
    from .. import models as configuration_models

    module = getattr(configuration_models, f"{class_name.lower()}s")
    return getattr(module, class_name)