如何让Python程序加载并使用几个不同的同名包?

How to enable the Python program to load and use a few different packages with the same name?

我正在开发一个 Python 控制硬件的应用程序。硬件由基于 FPGA 的系统组成。从控制的角度来看,它们的内部结构由自动化系统 AGWB ( http://github.com/wzab/agwb.git ) 管理。

每个系统的描述都被编译成一个 Python 模块“agwb”,其中包含一些子模块。 加载模块后,它用于生成处理与关联系统的进一步通信的对象。

当我的应用程序与几个不同的系统一起工作时,它必须加载几个不同的“agwb”模块(当然,每个模块都来自不同的目录)。

通过在 sys.path 的开头添加该目录(并在加载模块后删除它),可以轻松地从指定目录加载模块。

不幸的是,Python 只导入模块“agwb”一次。稍后尝试导入它会导致使用先前加载的一个。 使用 importlib.reload 也无济于事。 在阅读 https://docs.python.org/3/reference/import.html 之后,我找到了以下解决方案:

import sys
import importlib

def load_agwb(lddir,top):
    sys.path.insert(0,lddir)
    importlib.import_module('agwb')
    # Get the class for the top entity
    res = getattr(sys.modules['agwb'],top)()
    sys.path.remove(lddir)
    sys.modules.pop('agwb')
    mods_to_remove = []
    for mod in sys.modules.keys():
        if mod.find('agwb.') == 0:
            mods_to_remove.append(mod)
    for mod in mods_to_remove:
        sys.modules.pop(mod)
    return res

at1 = load_agwb('./t1','top_v0')
at2 = load_agwb('./t2','top_v0')
print("Call at1.show")
at1.show()
print("Call at2.show")
at2.show()

目录“./t1”和“./t2”包含两个不同版本的“agwb”模块。

执行脚本后,我们得到以下输出,证明确实加载并同时使用了两个不同版本的“agwb”:

Call at1.show
object from t1, desc= That's version from directory t1
type ID= 1111
Call at2.show
object from t2, desc= That's version from directory t2
type ID= 2222

完整的最小演示程序(作为 shar 存档)可在 https://pastebin.com/ZGZj8qV1 获得(不幸的是,我无法将文件附加到我的问题)。

我的问题来了:

我根据 pavel's 评论创建了一个答案(非常感谢!)。 我已经使用临时目录和符号链接来解决又长又复杂的路径问题。以下是结果:

import sys
import os
import importlib
import tempfile

# Directory with description of the first board
dir1 = './xt1/very/long/path'
# Directory with description of the second board
dir2 = './xt2/completely/different/path'

# Name of the top entity in the design
top = 'top_v0'

# Create a temporary directory
tmpdir = tempfile.TemporaryDirectory()

# Add it to the Python path
sys.path.insert(0,tmpdir.name)

# Create symlinks to the directories with descriptions of the boards
os.symlink(os.path.abspath(dir1),tmpdir.name+"/t1")
os.symlink(os.path.abspath(dir2),tmpdir.name+"/t2")

# Import description of the first board as agwb
import t1.agwb as agwb

# use the imported agwb
at1 = getattr(agwb,top)()
# if the name of the top entity is constant, you can do it easier
bt1 = agwb.top_v0()

# Import description of the second board as agwb
import t2.agwb as agwb

# use the imported agwb
at2 = getattr(agwb,top)()
# if the name of the top entity is constant, you can do it easier
bt2 = agwb.top_v0()

# Now we can remove the temporary directory from the python path
sys.path.remove(tmpdir.name)
# We can even delete it (otherwise it will be deleted when Python session ends)
tmpdir.cleanup()

# Check if both versions are loaded and available
print("Call at1.show")
at1.show()
print("Call at2.show")
at2.show()

print("Call bt1.show")
bt1.show()
print("Call bt2.show")
bt2.show()

和以前一样,整个 minimal reproducer 可以在 https://pastebin.com/GrRUh8U9 的 shar 格式中找到。