当前位置: 首页 > news >正文

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

文章目录

  • 需求
  • 方案
  • 讨论
      • 绝对路径缺点
  • 注意事项
    • 相对路径导入不能跳出定义包的目录
    • 在 Python 中,位于脚本顶层目录的模块(即直接运行的脚本)不能使用相对导入。

需求

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

方案

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

mypackage/ __init__.py     A/ __init__.py         spam.py grok.py     B/ __init__.pybar.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

讨论

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

 # 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包的目录会使得导入出现错误

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

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

解释:

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

代码示例:

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

project/
│
├── main.py
├── package/
│   ├── __init__.py
│   ├── module1.py
│   └── module2.py
  1. 错误示例(在 main.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
  1. 正确示例(在 package/module2.py 中使用相对导入):
# package/module2.py
from . import module1  # 这是正确的相对导入def some_function():module1.some_other_function()
  1. 主脚本中的正确做法:
# 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 的模块搜索路径(不推荐,除非必要)。

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

相关文章:

  • 怎么添加网页到桌面快捷方式?
  • 深度学习500问——Chapter11:迁移学习(3)
  • CP AUTOSAR标准之LSduRouter(AUTOSAR_CP_SWS_LSduRouter)
  • Open WebUI的SearXNG网络搜索配置【403报错解决方法】
  • 我又挖到宝了!小米、352、希喂宠物空气净化器除毛能力PK
  • MySQL WHERE子句的使用和优化方法
  • 模方4.0导出osgb模型一直提示正在运行任务,请取消所有任务后再尝试?
  • Python初体验
  • iOS Category
  • rsa加签验签C#和js、java、微信小程序互通
  • 【MYSQL】解决数据库Too many connections
  • 前端菜鸡学习日记 -- 关于pnpm
  • kakfa发版丢消息事件分析
  • CRMEB Pro版高并发商城系统秒杀需要多大的服务器
  • [Linux内核驱动]模块参数
  • 11111111
  • 2017届校招提前批面试回顾
  • Consul Config 使用Git做版本控制的实现
  • EventListener原理
  • php ci框架整合银盛支付
  • Python3爬取英雄联盟英雄皮肤大图
  • react 代码优化(一) ——事件处理
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • Web Storage相关
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 驱动程序原理
  • 数据可视化之 Sankey 桑基图的实现
  • 用 vue 组件自定义 v-model, 实现一个 Tab 组件。
  • 原生Ajax
  • 在Unity中实现一个简单的消息管理器
  • 怎么将电脑中的声音录制成WAV格式
  • Java总结 - String - 这篇请使劲喷我
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • #Java第九次作业--输入输出流和文件操作
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (黑马C++)L06 重载与继承
  • (六)软件测试分工
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (一)utf8mb4_general_ci 和 utf8mb4_unicode_ci 适用排序和比较规则场景
  • (一)为什么要选择C++
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)甲方乙方——赵民谈找工作
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?
  • .NET COER+CONSUL微服务项目在CENTOS环境下的部署实践
  • .NET 使用配置文件
  • .Net插件开发开源框架
  • .NET导入Excel数据
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • [\u4e00-\u9fa5] //匹配中文字符
  • [20140403]查询是否产生日志
  • [Asp.net MVC]Asp.net MVC5系列——Razor语法
  • [C/C++]数据结构 堆的详解
  • [C++] 模拟实现list(二)