Python如何导入存在相同子模块名的未打包外部项目?
环境配置
假设我要使用两个从Github克隆的第三方复杂项目,分别名为foo和bar,二者包含同名子目录。值得注意的是,foo和bar结构复杂,大量代码依赖自身项目结构,且均未被配置为Python包。
我的项目与外部项目的结构示例如下:
project_root/ myprog.py external/ foo/ models/ ... # other code that involve `import models...` bar/ models/ ... # other code that involve `import models...`
其中external/foo和external/bar本质上是我自有项目的git submodules。
两个项目的内部代码都可能存在如下导入语句:
import models.some_model.some_stuff ...
注:即便上述代码出现在
foo或bar的子目录中,在对应项目内也可正常运行,因为项目原本的使用逻辑会将自身路径加入sys.paths
问题描述
我在编写myprog.py时已知,若仅需导入foo项目的模块,可通过两种方式实现:
- 在
project_root目录下创建指向foo项目的软链接 - 将
external/foo的路径添加到sys.path中,示例代码如下:
# myprog.py import sys sys.path.append("absolute/path/to/external/foo")
第一种方式支持我在myprog.py中执行如下格式的导入:
# myprog.py from foo import ...
第二种方式可保证foo内部import models...类语句正常生效,因为此时models所在路径已被加入sys.path。
但是,当有两个存在相同子模块名的外部项目时,我该如何处理导入逻辑?
若将两个项目的路径都加入sys.path,会出现名称冲突,import models语句将产生歧义,导致外部项目代码运行出错。请问这种场景下有什么可行的解决方案?
可行解决方案
方案1:临时修改sys.path+导入后隔离(无侵入,优先推荐)
不需要修改任何第三方submodule的代码,利用Python模块导入后会缓存到sys.modules的特性规避冲突:
import sys import os PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) FOO_PATH = os.path.join(PROJECT_ROOT, "external", "foo") BAR_PATH = os.path.join(PROJECT_ROOT, "external", "bar") # 备份原始sys.path original_path = sys.path.copy() # 先处理foo导入 sys.path.append(FOO_PATH) import foo # 重命名foo的models缓存,避免后续被覆盖 sys.modules['foo_models'] = sys.modules.pop('models') foo.models = sys.modules['foo_models'] # 恢复原始路径 sys.path = original_path.copy() # 再处理bar导入 sys.path.append(BAR_PATH) import bar sys.modules['bar_models'] = sys.modules.pop('models') bar.models = sys.modules['bar_models'] # 恢复原始路径 sys.path = original_path
后续直接通过foo.xxx、bar.xxx调用功能即可,内部的模块引用不会发生冲突。如果项目还有其他同名子模块,按照相同逻辑重命名缓存即可。
方案2:最小侵入包封装(适合长期维护场景)
如果两个项目需要频繁调用,方案1的隔离逻辑太繁琐,可以给submodule加不影响原有功能和更新的最小包结构:
- 在
external/foo和external/bar目录下分别新增空的__init__.py文件 - 分别在两个新增的
__init__.py中加入路径前置逻辑:
# external/foo/__init__.py内容 import os, sys sys.path.insert(0, os.path.dirname(__file__))
- 在
myprog.py中仅把external目录加入sys.path即可:
import sys, os sys.path.append(os.path.join(os.path.dirname(__file__), "external")) # 直接按包名导入即可 from foo import xxx from bar import yyy
两个项目的内部导入会优先找自身目录下的模块,不会发生命名冲突。
方案3:子进程隔离(零冲突,适合功能独立场景)
如果两个项目的功能完全独立,不需要在同一个运行时交互,可以直接用子进程分别调用:
import subprocess, sys # 调用foo功能 subprocess.run([sys.executable, "external/foo/入口文件.py", "参数1", "参数2"], capture_output=True) # 调用bar功能 subprocess.run([sys.executable, "external/bar/入口文件.py", "参数1", "参数2"], capture_output=True)
该方案完全避免导入冲突,仅需要通过json、临时文件等方式处理进程间的结果传递即可。
内容的提问来源于stack exchange,提问作者zkytony

