当前位置: 首页 > 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.lryc.cn/news/609057.html

相关文章:

  • 01.Redis 概述
  • 复现cacti的RCE
  • ELECTRICAL靶机
  • [硬件电路-145]:模拟电路 - 常见的电源芯片、用途、管脚定义
  • Spring+K8s+AI实战:3全栈开发指南
  • LeetCode Hot 100,快速学习,不断更
  • Steam饥荒联机版多人服务器搭建全解析 -- 阿里云Linux系统构建云服务器
  • kafka创建topic报错解决思路之一
  • 常⻅框架漏洞
  • 计算用户日活:从数据设计到可视化的全流程(高频场景题)
  • sqli-labs:Less-26关卡详细解析
  • C++-二叉树OJ题
  • 外设数据到昇腾310推理卡 之五 3403ATU
  • react native中markdown添加数学公式的支持
  • Java ++i 与 i++ 底层原理
  • 机器翻译入门:定义、发展简史与核心价值
  • [自动化Adapt] 录制引擎
  • MVCC:数据库事务隔离的 “时空魔法”
  • mysql管理
  • 【Linux系统】进程间通信:匿名管道
  • http://localhost:8080/photos/xxx.png的本地图片访问方案
  • 常见的框架漏洞(Thinkphp,spring,Shiro)
  • io_submit系统调用及示例
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-54,(知识点:硬件设计流程)
  • 知识随记-----MySQL 连接池健康检测与 RAII 资源管理技术
  • vulnhub-noob靶机攻略
  • ICT模拟零件测试方法--电位器测试
  • 【QT】常⽤控件详解(二)windowOpacitycursorfontsetToolTipfocusPolicystyleSheet
  • 8.1.3 TiDB集群方案雨Replication原理
  • git用户设置