我们将代码组织成了一个包,想从其中一个子模块中导入另一个子模块,但是又不希望在import语句中硬编码包的名称
要在软件包的子模块中导入同一个包中其他的子模块,请使用相对名称来导入。例如,假设有一个名为mypackage的包,它在文件系统上组织成如下的形式:
mypackage/
__init__.py
A/
__init__.py
spam.py
grok.py
B/
__init__.py
bar.py
在包的内部,要在其中一个子模块中导入同一个包中其他的子模块,既可以通过给出完整的绝对名称,也可以通过上面示例中采用的相对名称来完成导入。示例如下:
# mypackage/A/spam.py
from mypackage.A import grok # OK
from . import grok # OK
import grok # Error (not found)
使用绝对名称的缺点在于这么做会将最顶层的包名称硬编码到源代码中,这使得代码更加脆弱,如果想重新组织一下结构会比较困难。例如,如果修改了包的名称,将不得不搜索所有的源代码文件并修改硬编码的名称。类似地,硬编码名称使得其他人很难移动这部分代码。例如,也许有人想安装两个不同版本的包,只通过名字来区分它们。如果采用相对名称导入,那么不会有任何问题,但是采用绝对名称导入则会使程序崩溃。
import语句中的.和…语法可能看起来比较有趣,把它们想象成指定目录名即可。.意味着在当前目录中查找,而…B表示在…/B目录中查找。这种语法只能用在from xx import yy这样的导入语句中。示例如下:
from . import grok # OK
import .grok # ERROR
尽管看起来似乎可以利用相对导入来访问整个文件系统,但实际上是不允许跳出定义包的那个目录的。也就是说,利用句点的组合形式进入一个不是Python包的目录会使得导入出现错误
这是因为相对导入是基于包结构工作的,而直接运行的脚本不被视为包的一部分。
解释:
__name__
设置为 '__main__'
。代码示例:
假设我们有以下目录结构:
project/
│
├── main.py
├── package/
│ ├── __init__.py
│ ├── module1.py
│ └── module2.py
# main.py
from .package import module1 # 这将引发错误
def main():
module1.some_function()
if __name__ == "__main__":
main()
运行 python main.py
会得到类似这样的错误:
ValueError: attempted relative import beyond top-level package
# package/module2.py
from . import module1 # 这是正确的相对导入
def some_function():
module1.some_other_function()
# main.py
from package import module1 # 使用绝对导入
def main():
module1.some_function()
if __name__ == "__main__":
main()
解决方法:
-m
选项运行你的脚本,例如:python -m package.main
。sys.path
修改 Python 的模块搜索路径(不推荐,除非必要)。总结:直接运行的脚本应该使用绝对导入,而相对导入应该保留给包内部的模块使用。这有助于保持代码的清晰性和可移植性。