import的操作对象就是模块与包,我们先了解这两个核心概念模块(Module):是python将.py文件import导入到内存中生成的python object(对象)。当然其他类型文件也可以,但是常用形式还是.py文件 1. 模块是代码的组织单位,自己拥有独立的命名空间,不用担心命名冲突 3. 模块的概念是python 运行时的概念,存在于内存中。包(Package):本质是特殊模块,常规源码包由文件夹 + __init__.py 组成;相比普通模块,额外拥有__path__属性,用于检索子模块 / 子包。 1. 跟模块比多了一个属性__path__,__path__保存了包所在的文件夹路径,Python 靠它去这个目录里找子模块 / 子包 2. 从操作系统的概念来理解,包对应文件夹,执行import 后会将整个文件夹加载为包对象import是把操作系统中的文件/文件夹,加载为Python运行时的 Module/Package 的过程
1. 检查缓存:先查看内存缓存中是否已有名为test的模块,有则直接复用,没有则重新加载- 先判断是否为built-in Module(python内置模块),如os,sys等
- 非内置模块,则按
sys.path 路径列表顺序 查找对应文件,找到即停止检索。
3. 加载运行:在独立命名空间中执行 .py 文件,生成 Module 对象。5. 赋值变量:把 Module 对象赋值给当前作用域的同名变量(示例中即test)补充特性:
- 模块代码仅在首次导入时执行一次,多次导入只会读取缓存
若没有,则判断是否为内置,非内置,继续在sys.path找到 test 模块并加载运行,将其赋值给新变量 t,原变量名 test 不再生效- 不会创建
test 变量,直接把模块内的对象 A 导入到当前命名空间,并将其赋值给新变量A。
- 仅会执行包内的
__init__.py(无该文件则不执行任何代码); - 不会自动加载包内的子模块 / 子包,无法直接使用包内普通模块,使用会报错,需显式导入子模块,或在
__init__.py 预加载,才能使用。
import mypackage.mymodule
- 逐层加载:先加载外层包
mypackage,再加载子模块 mymodule,均会更新缓存; - 变量赋值:最终仅将最外层包名生成为全局变量,使用方式:
mypackage.mymodule。
import mypackage.mymodule as m
执行过程:
- Python 先查找并加载
mypackage(外层包),存入全局模块缓存 sys.modules; - 再顺着包的
__path__ 加载 mymodule(子模块),同样存入缓存; - 最后只把末尾的
mymodule 对象绑定到变量 m。
综上所述,得到所有import通用流程(无论模块、包,亦或是哪种写法)Python 执行任意导入语句,固定走 5 步:
查缓存(sys.modules)
先按导入名字去全局模块缓存查找:
检索目标位置
按sys.path路径从上到下依次查找:
- 再遍历
sys.path 目录,匹配 .py 文件(模块)/ 文件夹(包); - 找到即停止检索,路径靠前的文件会优先命中(易引发命名冲突)。
加载并运行代码
- 普通模块(
.py):在独立命名空间执行文件全部代码,生成模块对象; - 包(文件夹):仅执行
__init__.py(无则不执行代码),生成包对象(自带 __path__ 属性);
写入缓存
把本次加载成功的所有模块 / 包,全部存入 sys.modules,供后续导入复用。
绑定到当前作用域变量
根据导入语法规则,决定把哪个对象赋值给变量。
绝对导入,没什么好说的,前文全是绝对导入,即通过完整固定路径字符串查找模块。仅用于包内部模块之间互相引用,依托文件相对位置编写,包改名、迁移后代码无需改动。语法符号.
# mypackage/mymodule.pyfrom .util import f # 从同级 util 模块导入 f
相对导入的原理是依赖__package__属性,计算出绝对路径__package__是模块 / 包对象自带的内置属性,作用:标识当前模块隶属于哪个包,是 Python 实现相对导入的核心依据。
它的值是字符串:
- 模块是顶层脚本(直接运行)/ 不属于任何包 → 值为
None 或空字符串
相对导入的执行流程:
- 读取当前模块的
__package__,拿到所属包名;
和 __name__ 区分
- 模块被导入时:值为包名 + 模块名(如
mypackage.mod1),标识该文件是普通模块 - 文件直接运行:固定为字符串
__main__,标识该文件是主程序入口 最常用场景:if __name__ == "__main__": 区分「直接运行」和「被导入」,实现代码两用
# 导入 mypackage.mod1 后print(mypackage.mod1.__name__) # mypackage.mod1print(mypackage.mod1.__package__) # mypackage
通过 python 文件名.py命令,把单个 .py 文件当作顶层主程序启动特征:该文件__name__ == "__main__" 成立,__package__为 None;局限:不支持包内相对导入,仅适合单文件、简单脚本,大中型项目禁止用这种方式跑核心代码。Python 会从 sys.path 中按模块规则查找,保留包结构,__package__ 正常生效,支持相对导入为什么一定要把主入口放进包?
1) 支持相对导入,代码解耦更方便(最核心)
- 如果入口在根目录、零散文件:只能用绝对导入,路径写死,项目重构、文件夹改名 / 迁移时,到处改导入语句。
- 入口在规范包结构里:包内模块可以用
./.. 相对导入,模块之间只关心相对位置,整体目录搬迁、外层包改名,内部代码完全不用改。
2) 适配 Python 包安装、打包、发布(可编译 / 跨平台必备)多人协作时,有人手动 sys.path.append(xxx) 硬加路径。而不同人、不同环境路径不一样。注意:执行 python -m 时,终端当前目录必须是「项目根目录」,否则会报模块找不到