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

Python篇--- Python 的加载、缓存、覆盖机制

要理解 import 与 if __name__ == "__main__": 的关系,以及 Python 的加载、缓存、覆盖机制,我们可以从 “模块的两种身份” 和 “导入的全过程” 入手,用通俗的例子一步步拆解。

一、核心:模块的 “双重身份” 与 __name__ 的作用

每个 .py 文件(模块)都有两种可能的 “身份”:

  • 作为 “主程序” 直接运行(比如 python script.py);
  • 作为 “模块” 被其他程序导入(比如 import script)。

__name__ 这个内置变量就是用来区分这两种身份的 “身份证”

  • 当模块直接运行时,__name__ 的值是 "__main__"
  • 当模块被导入时,__name__ 的值是模块名(比如 script

而 if __name__ == "__main__": 就像一个 “身份检查站”:只有当模块是 “主程序” 时,才会执行缩进内的代码(通常是程序的入口逻辑,比如 main() 函数)。

二、import 与 if __name__ 的核心关系:导入时 “不执行主程序”

import 的作用是 “加载模块并执行其顶层代码”,但 if __name__ == "__main__": 块内的代码是 “主程序专属逻辑”,导入时不会执行

我们用一个例子说清楚:

假设我们有两个文件:

  • tool.py(工具模块,可能被导入)
  • main.py(主程序,会导入 tool.py
1. tool.py 的内容:
# tool.py
print("tool.py 的顶层代码执行了")  # 顶层代码(未缩进)def add(a, b):return a + b# 主程序逻辑:只有直接运行 tool.py 时才执行
if __name__ == "__main__":print("tool.py 被直接运行了(主程序模式)")print(add(1, 2))  # 输出 3
2. main.py 的内容(导入 tool.py):
# main.py
print("开始导入 tool.py...")
import tool  # 导入 tool 模块
print("tool.py 导入完成")print(tool.add(3, 4))  # 使用 tool 中的函数
3. 两种运行场景的对比:
  • 场景 1:直接运行 tool.py
    命令:python tool.py
    输出:

    tool.py 的顶层代码执行了
    tool.py 被直接运行了(主程序模式)
    3
    

    解释:__name__ 是 "__main__",所以 if 块内的代码被执行。

  • 场景 2:运行 main.py(导入 tool.py
    命令:python main.py
    输出:

    开始导入 tool.py...
    tool.py 的顶层代码执行了  # 导入时执行了 tool.py 的顶层代码
    tool.py 导入完成
    7  # 成功使用 tool.add
    

    关键:导入 tool.py 时,只执行了它的顶层代码print 和 add 函数定义),但 if __name__ == "__main__": 块内的代码没有执行(因为此时 tool.py 的 __name__ 是 "tool",不是 "__main__")。

总结关系
import 会触发模块的 “顶层代码” 执行(定义函数、类、变量等),但 if __name__ == "__main__": 块内的代码是 “主程序专属逻辑”,仅在模块被直接运行时执行,导入时不执行。这确保了模块被导入时,不会意外执行其 “主程序代码”(比如测试逻辑、命令行交互等)。

三、加载机制:import 时模块是如何被 “读入” 的?

import 一个模块的过程,就像 “找文件→读内容→执行代码→存起来” 的流程,具体分 4 步:

  1. 检查缓存:先看这个模块是否已经导入过(存在 sys.modules 字典中)。如果有,直接用缓存的模块,不重复加载。
  2. 查找模块:如果没缓存,Python 会按 sys.path 列表中的路径(当前目录、标准库目录等)查找模块文件(.py.pyc 等)。
  3. 加载并执行:找到文件后,Python 会读取文件内容,执行其中的顶层代码(定义函数、类、变量,以及未缩进的语句),并生成一个 “模块对象”。
  4. 存入缓存:将生成的模块对象存入 sys.modules,方便下次导入时直接使用。
加载机制与 if __name__ 的互动:

在 “执行顶层代码” 这一步,模块中的所有未缩进代码都会被执行(包括 import 其他模块、定义函数等),但 if __name__ == "__main__": 块内的代码是否执行,取决于模块的 “身份”:

  • 若模块是被导入的(__name__ = 模块名):if 条件不成立,块内代码不执行。
  • 若模块是直接运行的(__name__ = "__main__"):if 条件成立,块内代码会被执行(属于顶层代码的一部分,只是被条件判断包裹了)。

四、缓存机制:为什么模块不会被重复加载?

Python 有一个 “模块缓存”(sys.modules 字典),用来存储已经导入的模块对象。第一次导入模块时会执行其代码并缓存,后续导入直接用缓存,不会重复执行代码

举例说明:

# main.py
import sys
print("第一次导入 tool:")
import tool  # 第一次导入,执行 tool 的顶层代码print("\n第二次导入 tool:")
import tool  # 第二次导入,直接用缓存,不执行代码# 查看缓存中是否有 tool
print("\n缓存中是否有 tool?", "tool" in sys.modules)  # 输出 True

运行 main.py 的输出:

第一次导入 tool:
tool.py 的顶层代码执行了  # 第一次导入时执行
第二次导入 tool:  # 第二次导入,无输出(未执行代码)
缓存中是否有 tool? True

缓存的意义

  • 提高效率:避免重复读取文件和执行代码,节省时间。
  • 保证一致性:多次导入的是同一个模块对象,模块内的全局变量状态会被保留(比如计数器不会重置)。

五、覆盖机制:命名冲突时谁会 “胜出”?

当导入的模块 / 成员与当前作用域的变量、函数重名时,会发生 “覆盖”(后定义的会覆盖先定义的)。这与 import 的语法和执行顺序有关。

1. 导入的成员覆盖本地变量
# main.py
a = 10  # 本地变量 afrom tool import a  # 从 tool 导入 a(假设 tool.py 中 a=20)
print(a)  # 输出 20(被导入的 a 覆盖了本地 a)
2. 后导入的成员覆盖先导入的
# main.py
from tool import add  # 假设 tool.add 是 a+bfrom other_tool import add  # other_tool.add 是 a*b(后导入)
print(add(2, 3))  # 输出 6(被后导入的 add 覆盖)
3. 模块名覆盖问题

如果你的脚本名与标准库模块名相同(比如 json.py),导入时会优先加载你的脚本,覆盖标准库模块

# 假设你有一个 json.py 文件
# main.py
import json  # 会导入你的 json.py,而不是标准库的 json

这会导致标准库功能无法使用,所以不要用标准库模块名命名自己的脚本

六、总结

  1. import 与 __name__ 的关系
    import 会执行模块的顶层代码,但 if __name__ == "__main__": 块内的代码仅在模块直接运行时执行,导入时不执行,避免 “主程序逻辑” 被意外触发。

  2. 加载机制
    导入模块时,Python 会按 “查缓存→找文件→执行顶层代码→存缓存” 的流程处理,__name__ 决定主程序块是否执行。

  3. 缓存机制
    模块导入后会存入 sys.modules,后续导入直接用缓存,不重复执行代码,保证效率和状态一致性。

  4. 覆盖机制
    命名冲突时,后定义 / 导入的对象会覆盖先定义 / 导入的,需注意避免与标准库或本地变量重名。

理解这些机制,能帮你更清晰地控制代码的执行时机,避免导入时的意外行为(比如重复执行、命名冲突)。

http://www.xdnf.cn/news/1235485.html

相关文章:

  • (FD Conv)Frequency Dynamic Convolution for Dense Image Prediction论文精读(逐段解析)
  • vscode的Remote-SSH插件配置SSH主机方法
  • 构造类型--结构体,共同体联合体,枚举
  • 知识蒸馏 - 基于KL散度的知识蒸馏 HelloWorld 示例 采用PyTorch 内置函数F.kl_div的实现方式
  • 标记-清除算法中的可达性判定与Chrome DevTools内存分析实践
  • Rust: 获取 MAC 地址方法大全
  • webrtv弱网-QualityScalerResource 源码分析及算法原理
  • 集成电路学习:什么是USB HID人机接口设备
  • Hertzbeat如何配置redis?保存在redis的数据是可读数据
  • PostgreSQL面试题及详细答案120道(21-40)
  • 腾讯人脸识别
  • 14.Redis 哨兵 Sentinel
  • C++中多线程和互斥锁的基本使用
  • [硬件电路-148]:数字电路 - 什么是CMOS电平、TTL电平?还有哪些其他电平标准?发展历史?
  • 本地环境vue与springboot联调
  • 2025年6月电子学会青少年软件编程(C语言)等级考试试卷(四级)
  • [硬件电路-143]:模拟电路 - 开关电源与线性稳压电源的详细比较
  • Ubuntu22.4部署大模型前置安装
  • webrtc弱网-QualityScaler 源码分析与算法原理
  • ubuntu apt安装与dpkg安装相互之间的关系
  • (一)全栈(react配置/https支持/useState多组件传递/表单提交/React Query/axois封装/Router)
  • 自动驾驶中的传感器技术18——Camera(9)
  • GitLab 代码管理平台部署及使用
  • Java基本技术讲解
  • PPT自动化 python-pptx - 9: 图表(chart)
  • 决策树学习全解析:从理论到实战
  • 【LeetCode刷题指南】--二叉树的后序遍历,二叉树遍历
  • PPT写作五个境界--仅供学习交流使用
  • 【1】WPF界面开发入门—— 图书馆程序:登录界面设计
  • 业务系统跳转Nacos免登录方案实践