当前位置:首页>python>【Python】:导入import的底层原理解析,深刻理解导入过程

【Python】:导入import的底层原理解析,深刻理解导入过程

  • 2026-10-11 07:28:24
【Python】:导入import的底层原理解析,深刻理解导入过程
一、模块与包
import的操作对象就是模块与包,我们先了解这两个核心概念
模块(Module):是python将.py文件import导入到内存中生成的python object(对象)。当然其他类型文件也可以,但是常用形式还是.py文件
核心特点:
    1. 模块是代码的组织单位,自己拥有独立的命名空间,不用担心命名冲突
    2. 本身是个python object
    3. 模块的概念是python 运行时的概念,存在于内存中。
    4. .py文件是个文件,是操作系统级别的概念
包(Package):本质是特殊模块,常规源码包由文件夹 + __init__.py 组成;相比普通模块,额外拥有__path__属性,用于检索子模块 / 子包。
核心特点:
    1. 跟模块比多了一个属性__path__,__path__保存了包所在的文件夹路径,Python 靠它去这个目录里找子模块 / 子包
    2. 从操作系统的概念来理解,包对应文件夹,执行import 后会将整个文件夹加载为包对象
    3. 可以内嵌子包、模块等
import作用
import是把操作系统中的文件/文件夹,加载为Python运行时的 Module/Package 的过程

二、模块导入的执行流程
以import test为例,执行步骤:
1. 检查缓存:先查看内存缓存中是否已有名为test的模块,有则直接复用,没有则重新加载
2. 查找模块:无缓存则开始检索
  •   先判断是否为built-in Module(python内置模块),如os,sys等
  •  非内置模块,则按sys.path 路径列表顺序 查找对应文件,找到即停止检索。
3. 加载运行:在独立命名空间中执行 .py 文件,生成 Module 对象。
4. 更新缓存:将新模块存入缓存,避免重复加载。
5. 赋值变量:把 Module 对象赋值给当前作用域的同名变量(示例中即test)

补充特性:

  • 模块代码仅在首次导入时执行一次,多次导入只会读取缓存

三、常用导入语法解析
1. 别名导入
import test as t
先查缓存,若有加载缓存里的test模块;
若没有,则判断是否为内置,非内置,继续在sys.path找到 test 模块并加载运行,将其赋值给新变量 t,原变量名 test 不再生效
2. 按需导入(只导入模块内对象)
from test import A
  • 仍会完整加载并缓存test模块;
  • 不会创建 test 变量,直接把模块内的对象 A 导入到当前命名空间,并将其赋值给新变量A。
3. 按需导入 + 别名
from test import A as ma
导入模块内指定对象A,并将其赋值给新变量ma。

四、包的导入规则
1. 直接导入整个包
import mypackage
  • 检索逻辑和普通模块一致;
  • 仅会执行包内的 __init__.py(无该文件则不执行任何代码);
  • 不会自动加载包内的子模块 / 子包,无法直接使用包内普通模块,使用会报错,需显式导入子模块,或在 __init__.py 预加载,才能使用。
2. 导入包内子模块(层级导入)
import mypackage.mymodule
  • 逐层加载:先加载外层包 mypackage,再加载子模块 mymodule,均会更新缓存;
  • 自动关联:子模块会成为外层包的一个属性;
  • 变量赋值:最终仅将最外层包名生成为全局变量,使用方式:mypackage.mymodule。
3. 层级导入 + 别名
import mypackage.mymodule as m

执行过程:

  1. Python 先查找并加载 mypackage(外层包),存入全局模块缓存 sys.modules;
  2. 再顺着包的__path__ 加载 mymodule(子模块),同样存入缓存;
  3. 最后只把末尾的mymodule 对象绑定到变量 m。

综上所述,得到所有import通用流程(无论模块、包,亦或是哪种写法)

Python 执行任意导入语句,固定走 5 步:

  1. 查缓存(sys.modules)

    先按导入名字去全局模块缓存查找:

    • 存在:直接取出对象,不再执行加载逻辑;
    • 不存在:进入下一步加载。
  2. 检索目标位置

    按sys.path路径从上到下依次查找:

    1. 先判断是不是 Python 内置模块;
    2. 再遍历sys.path 目录,匹配 .py 文件(模块)/ 文件夹(包);
    3. 找到即停止检索,路径靠前的文件会优先命中(易引发命名冲突)。
  3. 加载并运行代码

    • 普通模块(.py):在独立命名空间执行文件全部代码,生成模块对象;
    • 包(文件夹):仅执行__init__.py(无则不执行代码),生成包对象(自带 __path__ 属性);
    • 层级导入 A.B:从左到右逐层加载 A → B。
  4. 写入缓存

    把本次加载成功的所有模块 / 包,全部存入 sys.modules,供后续导入复用。

  5. 绑定到当前作用域变量

    根据导入语法规则,决定把哪个对象赋值给变量。


五、绝对导入与相对导入
绝对导入,没什么好说的,前文全是绝对导入,即通过完整固定路径字符串查找模块。
相对导入
仅用于包内部模块之间互相引用,依托文件相对位置编写,包改名、迁移后代码无需改动。

语法符号.

 .:当前所在包(同级目录)
..:上一级包(上级目录)
示例:
# mypackage/mymodule.pyfrom .util import f  # 从同级 util 模块导入 f
相对导入的原理是依赖__package__属性,计算出绝对路径

__package__是模块 / 包对象自带的内置属性,作用:标识当前模块隶属于哪个包,是 Python 实现相对导入的核心依据。

它的值是字符串:

  • 模块属于某个包 → 值为包名
  • 模块是顶层脚本(直接运行)/ 不属于任何包 → 值为 None 或空字符串

相对导入的执行流程:

  1. 读取当前模块的 __package__,拿到所属包名;
  2. 结合 ./.. 拼接成完整绝对导入路径;
  3. 再按普通绝对导入规则加载模块。

和 __name__ 区分

1. __name__
    • 模块被导入时:值为包名 + 模块名(如 mypackage.mod1),标识该文件是普通模块
    • 文件直接运行:固定为字符串 __main__,标识该文件是主程序入口
    • 作用:标识当前代码的运行身份
    • 最常用场景:if __name__ == "__main__":   区分「直接运行」和「被导入」,实现代码两用
2. __package__
    • 只记录直属包名,不包含自身模块名
    • 顶层文件 / 独立运行:值为 None
    • 作用:标识当前模块隶属于哪个包
示例:
# 导入 mypackage.mod1 后print(mypackage.mod1.__name__)     # mypackage.mod1print(mypackage.mod1.__package__)  # mypackage

六、python运行方式
1. 文件直接运行方式
通过 python 文件名.py命令,把单个 .py 文件当作顶层主程序启动
特征:该文件__name__ == "__main__" 成立,__package__为 None;
局限:不支持包内相对导入,仅适合单文件、简单脚本,大中型项目禁止用这种方式跑核心代码。
2. 模块模式运行(-m 参数,标准包运行)
标准正规运行:python -m 包名.模块名
Python 会从 sys.path 中按模块规则查找,保留包结构,__package__ 正常生效,支持相对导入
大中型项目把主入口放进包内,是行业标准做法

为什么一定要把主入口放进包?

1) 支持相对导入,代码解耦更方便(最核心)

  • 如果入口在根目录、零散文件:只能用绝对导入,路径写死,项目重构、文件夹改名 / 迁移时,到处改导入语句。
  • 入口在规范包结构里:包内模块可以用 ./.. 相对导入,模块之间只关心相对位置,整体目录搬迁、外层包改名,内部代码完全不用改。
2) 适配 Python 包安装、打包、发布(可编译 / 跨平台必备)
3)目录分层清晰,代码、配置、资源彻底隔离
4)统一 sys.path,避免路径乱象.
多人协作时,有人手动 sys.path.append(xxx) 硬加路径。而不同人、不同环境路径不一样。
5).....
注意:执行 python -m 时,终端当前目录必须是「项目根目录」,否则会报模块找不到

最新文章

随机文章