道一站 道一站
首页
  • 历史文明
  • 自我管理
  • 经济金融
  • 未来科技
  • 李笑来
  • 股票
  • 期货
  • 期权
  • 外盘
  • 量化
  • 区块
  • 认知
  • 数理
  • 收藏
  • 前端

    • JavaScript
    • TypeScript
  • 页面

    • HTML
    • CSS
  • 编程

    • C++
    • Python
    • Shell
    • 小程序开发
  • 笔记

    • 《Git》
    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
  • 技术文档
  • Linux
  • Docker
  • GitHub
  • 博客搭建
  • 效用工具
  • 周易基础
  • 奇门遁甲
  • 收藏资源
  • 学习方法
  • 实用技巧
  • 友情链接
  • 分类
  • 标签
  • 归档
关于

daotoyi

静水流深
首页
  • 历史文明
  • 自我管理
  • 经济金融
  • 未来科技
  • 李笑来
  • 股票
  • 期货
  • 期权
  • 外盘
  • 量化
  • 区块
  • 认知
  • 数理
  • 收藏
  • 前端

    • JavaScript
    • TypeScript
  • 页面

    • HTML
    • CSS
  • 编程

    • C++
    • Python
    • Shell
    • 小程序开发
  • 笔记

    • 《Git》
    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
  • 技术文档
  • Linux
  • Docker
  • GitHub
  • 博客搭建
  • 效用工具
  • 周易基础
  • 奇门遁甲
  • 收藏资源
  • 学习方法
  • 实用技巧
  • 友情链接
  • 分类
  • 标签
  • 归档
关于
  • C++

  • JavaScript

  • TypeScript

  • CSS

  • HTML

  • Python

    • Python 速查表
    • python 命名规则
    • Python 相关文件常见的后缀名
    • Python 中最常用的 5 种线程
    • Python 数据规范化(归一化)及Z-score标准化
    • Python 中的绝对导入和相对导入
      • 1 基本原理
      • 2 绝对导入
        • 2.1 用法
        • 2.2 不足
      • 3 相对导入
        • 3.1 用法
        • 3.2 实现机制
        • 3.3 限制条件
        • 相对导入只适用于顶级包内的模块
        • 使用了相对导入的模块文件不能作为顶层执行文件
    • Python 中有 3 个不可思议的返回功能
    • Python 技巧分享
    • PyQt5学习资料
    • PyQt5
    • PyQt5 QDockWidget
    • Python @1装饰器入门教程!
    • Python @函数装饰器及用法
    • Python f-string 格式化字符串的 7 个层级
    • Python 中最常用的 5 种线程
    • Python 中有 3 个不可思议的返回功能
    • Python string去除(中文、英文、数字、标点符号)
  • Shell

  • Notes

  • 编程
  • Python
daotoyi
2022-04-29
目录

Python 中的绝对导入和相对导入

[toc]

当工程大的时候,就需要分出很多包来管理,各个模块间不可避免地出现要引入其它模块。如果没有仔细去了解绝对导入和相对导入的规则,我们在引入模块的时候就很可能遇见各种问题。最常见的就是 ModuleNotFoundError: No module named 'moduleY'、ValueError: attempted relative import beyond top-level package、ModuleNotFoundError: No module named '__main__.moduleY'; '__main__' is not a package等这些异常,本文试着从导入语句的角度带大家了解 Python 的导入机制的冰山一角。

Python 2 和 Python 3 的导入机制会有些差异,本文以 Python 3.8 为基准。

假如我们目前遇到的包的布局如下:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py
1
2
3
4
5
6
7
8
9
10

# 1 基本原理

在 Python 里面,一个 .py 文件可以称为模块,包含了 __init__.py 文件的称为包。

当一个模块被执行时,Python 会从 sys.path 给出的路径去找在模块中引入的包或其它模块,如果找不到,程序就会报错。

# 2 绝对导入

绝对导入是默认的导入方式,因为它更常见,并且它有相对导入的所有功能。

# 2.1 用法

绝对导入可以使用 import <> 或 from <> import <> 这两种语法,比如在 moduleA 模块里面,使用下面的绝对导入语句是有效的。

import package.subpackage1.moduleX as moduleX
from package.subpackage1 import moduleY
from subpackage2 import moduleZ
1
2
3

不过,需要注意的是,在当前模块中引入同级别的模块或者同级别的包下的模块的时候,最好把导入路径写完整。比如,在 moduleX 模块中引入了同一级别的 moduleY:

# moduleX.py

import moduleY
1
2
3

moduleA 的代码:

# moduleA.py

import package.subpackage1.moduleX
1
2
3

执行 python moduleA.py 会提示 ModuleNotFoundError: No module named 'moduleY'。

因为执行模块 moduleA 的路径是D:/top/package(D:/top 是示例程序在我本机的目录),导入语句import moduleY 将会被解析并定位到 D:/top/package/moduleY.py,很明显这个路径不存在。

把模块 moduleX 中的 import moduleY 改成 import package.subpackage1.moduleY 或 from package.subpackage1 import moduleX 就可以正常运行。

# 2.2 不足

  1. 导入同一个包下的模块需要写完整的导入路径,如果层级很深,这个路径会显得很长。
  2. 假如要改变层级较高的包名,比如顶级包,那么所有导入路径都要改。

# 3 相对导入

相对导入解决了绝对导入的一些问题:

  1. 同一个包下的模块可以很方便的相互引用,使用像 from . import xxx 的语句就行。
  2. 顶层包的报名改了,包下的模块的相对导入的语句基本不用改。

# 3.1 用法

相对导入只能使用 from <> import <> 这种语法,并且使用 . 作为前导点。

比如,在 subpackage1/moduleX.py 或者 subpackage1/__init__.py 模块里面,我们可以使用相对导入的方式导入其它模块。

from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo
1
2
3
4
5
6

# 3.2 实现机制

PEP 328 (opens new window) 中有一段介绍了使用相对导入时查找导入模块机制:

Relative imports use a module's __name__attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to '__main__') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.

简单来说,就是在相对导入里面,一个模块的位置是由该模块的 __name__ 属性来确定,如果该属性值不包含任何包的信息,则把当前模块视为顶层模块。比如,在 moduleA、moduleZ 中分别加入一条可以输出__name__ 、__package__ 的值的语句。

moduleA.py 的代码:

# moduleA.py
print('__file__={0:<35} | __name__={1:<20} | __package__={2:<20}'.format(__file__,__name__,str(__package__)))

from subpackage2 import moduleZ
1
2
3
4

moduleZ.py 的代码:

# moduleZ.py
print('__file__={0:<35} | __name__={1:<20} | __package__={2:<20}'.format(__file__,__name__,str(__package__)))
1
2

执行命令 python moduleA.py 将会输出:

__file__=D:/top/package/moduleA.py | __name__=__main__             | __package__=None                
__file__=D:\top\package\subpackage2\moduleZ.py | __name__=subpackage2.moduleZ  | __package__=subpackage2         
1
2

我们发现 moduleA.py 的 __name__ 是 '__main__',即 moduleA.py 作为顶层模块。moduleZ.py 的 __name__ 是 subpackage2.moduleZ,所以模块 moduleZ.py 的顶级包是 subpackage2 而不是 package 。

# 3.3 限制条件

# 相对导入只适用于顶级包内的模块

在模块里可通过属性 __package__ 获取自身的包信息,即该模块所在包的结构,诸如 XXX、XXX.YYY.ZZZ 等形式,第一个节点就是顶层包,如果 __package__ 为 None 表示该模块是顶层模块。

在模块 moduleY 中引入 subpackage2 包下的 moduleZ 模块:

# moduleY.py

from ..subpackage2 import moduleZ
1
2
3

执行文件 moduleA.py 的代码:

# moduleA.py

import subpackage1.moduleY
1
2
3

执行 python moduleA.py 会引发 ValueError: attempted relative import beyond top-level package 异常。

因为模块 moduleY 所在的顶级包是 subpackage1,而 subpackage1 包下不存在子包 subpackage2。

# 使用了相对导入的模块文件不能作为顶层执行文件

对于下面moduleX.py 的代码,执行 python moduleX.py 将会引发 ModuleNotFoundError: No module named '__main__.moduleY'; '__main__' is not a package 异常。

# moduleX.py

from .moduleY import *
1
2
3
上次更新: 2023/10/10, 14:48:21
Python 数据规范化(归一化)及Z-score标准化
Python 中有 3 个不可思议的返回功能

← Python 数据规范化(归一化)及Z-score标准化 Python 中有 3 个不可思议的返回功能→

最近更新
01
置身事内
10-10
02
基础知识|八卦五行天干地支
10-08
03
基础知识|推算年月日时的干支
10-08
更多文章>
Theme by Vdoing | Copyright © 2021-2023 Daotoyi | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式