相对导入和模块重新加载

Relative importing and module reloading

我有一个机器学习项目,我必须进行 m.l。实验。每个实验都是用一个强烈依赖于存储库中其他文件的模块进行的。为了可重复性,我将每个实验视为存储库的快照。所以稍后我想重现该实验并将其与 ONE 脚本中的另一个实验进行比较。为此,我在特殊文件夹 reproduce/experiment_name 中恢复实验时存储库的状态并组织以下结构:

├── launcher.py
├── package
│   ├── dependency.py
│   └── subpackage
│       └── module.py
└── reproduce
    ├── experiment_23
    │   └── package
    │       ├── dependency.py
    │       └── subpackage
    │           ├── module.py
    └── experiment_24
        └── package
            ├── dependency.py
            └── subpackage
                └── module.py

所以在 launcher.py 中,我想导入 experiment_23 的内容,给定 importlib.utils 所需模块的路径。但我需要保留它的依赖关系,以便旧代码在它自己的领域中工作。我想出了一个想法来修改 sys.path 并在导入之前添加路径,然后将其删除。这里是launcher.py.

的内容
import sys
import importlib.util


path_to_module = "/package/subpackage/module.py"
experiment_path = "/reproduce/exp23"
project_path = "/home/user/PycharmProjects/PathTest"

# Here I add path to experiment directory
sys.path.insert(0, project_path + experiment_path)

spec = importlib.util.spec_from_file_location("", project_path + experiment_path + path_to_module, )
exp23 = importlib.util.module_from_spec(spec)
spec.loader.exec_module(exp23)

# Clear path
sys.path = sys.path[1:]
# This runs successfully
exp23.run()


# So now i want to import my newest version of the module.py
# And this fails, meaning it executes code from exp23
import package.subpackage.module as exp
exp.run()

module.py

的内容
from package.dependency import function


def run():
    function()

dependency.py是成功标志:

def function():
    print("23")

所以问题是一旦完成导入并加载依赖项 from package.dependency import function,python 将不再在 sys.path 中查找它并使用某种缓存。我的问题是在这种情况下如何避免使用缓存,或者至少如何使用不同的方法实现类似的功能?

因此,首先,我假设您在每个 packagesubpackage 目录中都有一个 __init__.py,以使它们成为实际的 "packages";按照指定 here.

现在,您应该注意到包含 module.pysubpackagepackage 的实际子包。因此,当您执行 from package.dependency import ... 时,您正在尝试从父包导入。这不仅是一种不好的做法,而且您在 launcher.py 中使用的路径仅直接针对 module.py,它不知道 package 是什么,甚至 subpackage 就此而言。

您应该定位的是 package,它(再次假设您在其中有一个 __init__.py)将作为实际 "package" 加载。使其内部的所有内容都可以正常访问...经过一些调整。

在您的 __init__.py 中,您应该公开您希望此模块包含的所有内容。例如,在您的 package/__init__.py 中,您应该有行 from . import subpackage。这不仅可以让您执行 package.subpackage 之类的操作,而且对于加载 subpackage.

的内容也至关重要

按照同样的逻辑,在你的subpackage/__init__.py中应该有from . import module,加载和暴露module.py,允许你做package.subpackage.module

这应该是您正确加载和访问所有所需内容所需的全部...除了您还将所有内容包装在 experiment_n 文件夹中。在这里你有两个选择:你可以制作 experiment_n 一个带有 __init__.py 的包,公开它的 package 并用 importlib 定位它,或者你可以重命名 package experiment_n 并删除顶级文件夹。从这里开始:

experiment_23
    └── package
        ├── __init__.py
        ├── dependency.py
        └── subpackage
            ├── __init__.py
            └── module.py

为此:

experiment_23
    ├── __init__.py
    ├── dependency.py
    └── subpackage
        ├── __init__.py
        └── module.py

让我们从文件夹编辑中休息一下,看看您将如何在代码中使用我们当前的迭代。考虑以下 launcher.py

import sys, os
import importlib.util

current_directory = os.path.dirname(os.path.realpath(__file__))

path_to_experiment = os.path.join(current_directory, 'reproduce/experiment_23')

path_to_experiment = os.path.join(path_to_experiment, '__init__.py') # package!

spec = importlib.util.spec_from_file_location('exp23', path_to_experiment, submodule_search_locations = [])

exp23 = importlib.util.module_from_spec(spec)

sys.modules[exp23.__name__] = exp23

# old one
spec.loader.exec_module(exp23)

exp23.subpackage.module.run()

# new one
import package.subpackage.module as exp

exp.run()

主要功能打印 main 和复制一个打印 exp23

首先,我们编写 path_to_experiment,指向我们包的 __init__.pymodule_from_file_location 需要 文件 ,而不是文件夹,因此我们不直接以 /expriment_23 为目标。之后,使用实际名称 (exp23),我们得到带有 spec_from_file_locationspec,将 submodule_search_locations 设置为空列表以指示它是指定的包 here.最后,使用 module_from_spec.

获取我们的模块

将新模块添加到 sys.modules 很重要,这样相对导入(我们使用的)就可以找到它们的父模块。

我们让我们的模块执行并调用它和我们新模块的 run 函数:

main
main

好像不行,我们希望第一个是exp23

罪魁祸首是我们 experiment_23/subpackage/module.py 中的 from package.dependency import function 行。这里的导入是 absolute,而不是 relative,这意味着它会在 / 中查找 / 或导入 "package"sys.modules 并使用它。在本例中,this 直接指向我们的主包。将其更改为 from ..dependency import function 并重新 运行 launcher.py 给我们:

exp23
main

成功:)

导入系统可能会非常混乱,花点时间在 import system docs and importlib docs 上会让您在遇到类似情况时省去很多麻烦。

作为可选建议,您可以将 module.py 移入 experiment_23 并移出 subpackage 并删除 subpackage。现在你可以做 from .dependency import function。有些人会争辩说,进行超相对导入(使用多个 .)是不好的做法,或者至少表明对包结构的理解不足。

编辑:应用于 experiment_23 的所有更改都对应于主要实验的包和所有其他 experiment_n...