跨Python 2/3同目录下含doctest模块的兼容导入问题
这个问题我之前踩过坑!核心原因是Python 2和3的模块导入机制差异,再加上「直接运行脚本」和「作为包模块导入」这两种场景的冲突。咱们先拆解问题,再给你几个可行的解决办法:
先搞清楚为什么两种导入方式都没法兼顾:
from core import *的问题:当用户把你的module目录放到Python的sys.path中,执行from module.extend import *时,Python 3会在全局模块命名空间里找core,而不是module包内的core模块,直接就会报ImportError: No module named 'core'。from .core import *的问题:当你直接运行extend.py(比如python extend.py跑doctest),Python会把这个脚本当作顶层模块,而非module包的一部分。此时Python 3里的相对导入(带.的语法)会直接报错SystemError: Parent module '' not loaded, cannot perform relative import;Python 2里虽然能勉强跑,但本质也是把core当作顶层模块导入,和包内导入的场景逻辑不一致。
这里有几个靠谱的方案,你可以根据自己的习惯选择:
方案1:条件式兼容导入
在extend.py里用try-except适配两种场景,代码如下:
try: # 当作为包模块被用户导入时,用相对导入 from .core import * except ImportError: # 当直接运行脚本(比如跑doctest)时,用绝对导入 from core import *
这个方法不用改测试方式,直接运行python extend.py或者用户导入module.extend都能正常工作。原理很简单:作为包的一部分被导入时,相对导入成功;直接运行脚本时,相对导入失败,自动降级到绝对导入——此时当前目录在sys.path里,core.py能被正常找到。
方案2:用模块方式运行测试脚本
不要直接运行extend.py,而是用Python的-m参数把它当作包模块来运行,这样就能让相对导入在Python 3里正常生效。
比如跑doctest的话,在module目录的上级目录执行:
# Python 2和3都支持 python -m doctest module/extend.py -v
如果你的extend.py里写了if __name__ == '__main__': doctest.testmod(),那可以直接运行:
python -m module.extend
这个方案更规范,完全符合Python的包导入设计,从根源上避免了脚本运行和模块导入的场景冲突。
方案3:调整包结构与__init__.py
如果你想彻底统一导入逻辑,可以修改module/__init__.py来中转导入:
- 在
module/__init__.py里添加:
from .core import *
- 在
extend.py里直接写:
from module import *
不过这个方案需要用户把module目录放到sys.path里,而且extend模块会依赖包名module,以后如果改包名要同步修改,灵活性稍差胜在逻辑清晰。
个人更推荐方案2,因为它完全遵循Python的模块导入规范,避免了条件导入的小技巧;如果不想改测试命令,方案1也是非常实用的妥协。
内容的提问来源于stack exchange,提问作者Carl Younger

