Python 用相对名称来导入包中的子模块

文章目录

需求

我们将代码组织成了一个包,想从其中一个子模块中导入另一个子模块,但是又不希望在import语句中硬编码包的名称

方案

要在软件包的子模块中导入同一个包中其他的子模块,请使用相对名称来导入。例如,假设有一个名为mypackage的包,它在文件系统上组织成如下的形式:

python 复制代码
mypackage/ 
	__init__.py     
	A/ 
		__init__.py         
		spam.py 
		grok.py     
	B/ 
		__init__.py
	    bar.py
  1. 如果模块mypackage.A.spam希望导入位于同一个目录中的模块grok,那么它应该包含一条这样的import语句:# mypackage/A/spam.pyfrom . import grok
  2. 如果模块mypackage.A.spam希望导入位于不同目录中的模块B.bar,可以使用下面的import语句来完成:# mypackage/A/spam.pyfrom ...B import bar

讨论

在包的内部,要在其中一个子模块中导入同一个包中其他的子模块,既可以通过给出完整的绝对名称,也可以通过上面示例中采用的相对名称来完成导入。示例如下:

python 复制代码
 # 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这样的导入语句中。示例如下:

python 复制代码
from . import grok     # OK
import .grok           # ERROR

注意事项

相对路径导入不能跳出定义包的目录

尽管看起来似乎可以利用相对导入来访问整个文件系统,但实际上是不允许跳出定义包的那个目录的。也就是说,利用句点的组合形式进入一个不是Python包的目录会使得导入出现错误

在 Python 中,位于脚本顶层目录的模块(即直接运行的脚本)不能使用相对导入。

这是因为相对导入是基于包结构工作的,而直接运行的脚本不被视为包的一部分。

解释:

  1. 相对导入是为了在包内部的模块之间进行导入设计的。
  2. 当你直接运行一个 Python 脚本时,Python 会将该脚本的 __name__ 设置为 '__main__'
  3. 相对导入需要知道当前模块在包结构中的位置,但直接运行的脚本没有这个上下文信息。

代码示例:

假设我们有以下目录结构:

复制代码
project/
│
├── main.py
├── package/
│   ├── __init__.py
│   ├── module1.py
│   └── module2.py
  1. 错误示例(在 main.py 中使用相对导入):
python 复制代码
# 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
  1. 正确示例(在 package/module2.py 中使用相对导入):
python 复制代码
# package/module2.py
from . import module1  # 这是正确的相对导入

def some_function():
    module1.some_other_function()
  1. 主脚本中的正确做法:
python 复制代码
# main.py
from package import module1  # 使用绝对导入

def main():
    module1.some_function()

if __name__ == "__main__":
    main()

解决方法:

  1. 在主脚本中使用绝对导入。
  2. 将你的代码组织成一个适当的包结构,并使用 -m 选项运行你的脚本,例如:python -m package.main
  3. 使用 sys.path 修改 Python 的模块搜索路径(不推荐,除非必要)。

总结:直接运行的脚本应该使用绝对导入,而相对导入应该保留给包内部的模块使用。这有助于保持代码的清晰性和可移植性。

相关推荐
咖啡续命又一天20 分钟前
python 自动化采集 ChromeDriver 安装
开发语言·python·自动化
huohaiyu1 小时前
synchronized (Java)
java·开发语言·安全·synchronized
_OP_CHEN1 小时前
C++基础:(九)string类的使用与模拟实现
开发语言·c++·stl·string·string类·c++容器·stl模拟实现
蓝天智能1 小时前
QT MVC中View的特点及使用注意事项
开发语言·qt·mvc
松果集1 小时前
【1】数据类型2
python
且慢.5891 小时前
命令行的学习使用技巧
python
木觞清2 小时前
喜马拉雅音频链接逆向实战
开发语言·前端·javascript
海琴烟Sunshine2 小时前
leetcode 66.加一 python
python·算法·leetcode
wuxuanok2 小时前
苍穹外卖 —— 公共字段填充
java·开发语言·spring boot·spring·mybatis
偷光2 小时前
浏览器中的隐藏IDE: Console (控制台) 面板
开发语言·前端·ide·php