本文内容是对包和模块使用中各种奇怪错误产生原因的分析,对包和模块基本概念和用法有疑惑的请看文档【2】
问题
最近把一个python2的老项目重构为python3,运行的时候报错:
ImportError: attempted relative import with no known parent package
看了下是相对路径导入模块时出错,找了一圈资料,程序倒是跑起来了,不过有一个问题还是没有得到答案:对于结构复杂的项目,怎么导入模块才能既要用起来方便,又要迁移的时候不受影响?
看过的有效的文章有:
- 【1】python官方文档的Modules说明
- 【2】菜鸟教程翻译的官方文档Modules说明
- 【3】[译][python]ImportError:attempted relative import with no known parent package
- 【4】彻底明白Python package和模块
- 【5】关于python的pakage和module __all__ __path__的介绍
只能说,众说纷纭,反而更加迷糊了。自己按照文章【4】的思路,下载了文中列出的官方sound包,摸索了一会,发现点门道。这里偷懒只说结论,不上过程。
结论
以下内容每个目录下都已经有了空的__init__.py文件。
1.相对路径
(所有以 . 开头的)导入的时候,总是以import语句自己所在的模块路径为基准去寻找目标包或者模块:
from . import echo
from .. import formats
from ..filters import equalizer
import .sound.effects.echo
from .sound.effects import *
2.绝对路径
(所有不以 . 开头的)导入的时候,总是以主程序入口所在的路径(工作路径)为基准去寻找目标包或者模块,而与import语句自己所在的模块路径无关:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
进一步地,所有相对路径能导入成功的都是:执行到import语句时,以import语句自己所在的模块的__package__值为基准去寻找目标包或者模块。
__file__=***\sound\effects\echo.py | __name__=sound.effects.echo | __package__=sound.effects
而主程序的__package__为空,自然不能用任何相对导入方式
__file__=***/pya.py | __name__=__main__ | __package__=None
现在再来看文章【3】给出的两种办法,其实都是想办法指定了__package__值,从而使相对导入成功的。但是这两种方法都太麻烦,局限性太大,我们就没有其他办法吗?
当然有!凡是这种以 from * import 方式,以及 * . * 方式导入的,被导入模块都有__package__,该模块的import都可以用相对路径。
import sound.effects
from sound.effects import *
总结:
- 主程序只能用绝对路径导入(不需要完整路径)。
- 导入时指定了包结构的模块,该模块导入其他模块时都可以用相对路径。
- 你还可以在每一个import之前,把要导入的包的相对路径转化为绝对路径(完整路径),然后再加入 sys.path,这样优点是可以直接用模块名,省去了一长串...加包名,缺点是每个有import的模块都需要这么做。这篇文章就是这么做的https://www.cnblogs.com/yinzhengjie/p/8587656.html
用如下执行方式试了,运行的很好啊。足以保证迁移也不会出问题了。
python ..\..\pya.py
最后
如果pya.py导入了
# import effects.echo
from sound.effects import echo
echo.py又导入
from ..filters import karaoke
那么karaoke.py还能再导入吗?
from ..effects import reverse
答案是,被导入的是否属于指明的前一级路径的顶级路径以内,否则就会:
import effects.echo
# from sound.effects import echo
ValueError: attempted relative import beyond top-level package