Python中if __name__ == ‘__main__‘: 的原理与工程实践
1. 这行代码到底在解决什么问题?——从“为什么需要它”讲起
你第一次在Python脚本里看到if __name__ == "__main__":这行代码时,大概率是照着教程抄下来的,心里嘀咕:“这玩意儿不就是个固定写法吗?删了好像也不报错,加了又看不出啥变化。”我完全理解这种困惑——我刚学Python那会儿,把它当成和print("Hello World")一样基础的仪式性语句,直到某天把一个写了三个月的工具脚本导入到另一个项目里,结果发现:程序一运行就自动执行了所有测试函数、清空了数据库、重写了配置文件。那天下午我花了两小时回滚数据,才真正搞懂这行代码不是装饰品,而是Python模块系统里一道关键的“安全闸门”。
它的核心作用,一句话说透:区分当前Python文件是被直接运行(作为主程序),还是被其他文件导入(作为模块)。这个判断看似简单,但背后牵扯的是Python整个模块加载机制的设计哲学——每个.py文件既是可执行脚本,又是可复用模块,而__name__这个特殊变量,就是Python runtime在加载文件时自动打上的“身份标签”。当你双击运行script.py,它的__name__值是"__main__";但当import script发生时,它的__name__就变成了"script"。这行if语句,本质上是在问:“我现在是主角,还是配角?”——主角才执行主逻辑,配角则安静待命,只暴露函数和类供调用。
这个机制直接决定了你的代码能否被安全复用。比如你写了一个爬虫脚本crawler.py,里面既有def fetch_data(url)函数,也有if __name__ == "__main__":包裹的fetch_data("https://example.com")调用。别人想复用你的fetch_data,只需from crawler import fetch_data,不会触发任何网络请求;但如果删掉这层保护,别人一导入就发起请求,轻则浪费资源,重则触发反爬封禁。再比如单元测试场景:test_utils.py里定义了def test_addition(),但主流程里写了print(add(2,3))——没有if __name__ == "__main__":保护,每次跑测试时都会先打印一遍结果,干扰测试输出。所以这不是语法糖,而是工程实践的基石:让代码同时具备“可执行性”和“可导入性”,且两者互不干扰。对新手来说,它是避免“导入即执行”陷阱的第一道防线;对老手而言,它是设计可插拔模块、构建CLI工具链、编写可测试代码的底层契约。
2. 深度拆解:__name__变量的生成逻辑与运行时行为
要真正吃透这行代码,必须钻进Python解释器的加载流程里看一眼。很多人误以为__name__是Python关键字或内置函数,其实它是一个模块级别的特殊属性(dunder attribute),由import机制在模块加载时动态注入。它的值不是硬编码的,而是严格取决于模块如何被引入——这个规则在CPython源码的import.c中明确定义,但我们可以用最直观的实验来验证。
2.1 实验验证:三种加载方式对应三种__name__值
我们创建三个文件来实测:
# 目录结构 project/ ├── main.py ├── module_a.py └── package_b/ └── sub_module.pymodule_a.py内容:
print(f"module_a.__name__ = {__name__}") def hello(): return "Hello from A"package_b/sub_module.py内容:
print(f"package_b.sub_module.__name__ = {__name__}") def world(): return "World from B"main.py内容:
print(f"main.py.__name__ = {__name__}") import module_a from package_b import sub_module现在分三种方式运行:
直接执行
main.py:python main.py输出:
main.py.__name__ = __main__ module_a.__name__ = module_a package_b.sub_module.__name__ = package_b.sub_module→ 主入口文件获得
__main__,所有被导入模块获得其完整路径名。将
main.py作为模块导入(在同级目录下启动Python交互环境):>>> import main main.py.__name__ = main module_a.__name__ = module_a package_b.sub_module.__name__ = package_b.sub_module→ 此时
main.py不再是主角,__name__变成"main",它的if __name__ == "__main__":块完全不会执行。用
-m参数运行包:python -m package_b.sub_module输出:
package_b.sub_module.__name__ = __main__→
-m参数会强制将指定模块设为主模块,__name__覆盖为__main__,这是实现可执行包的关键机制。
这个实验揭示了本质:__name__不是静态字符串,而是Python运行时根据“谁启动了我”动态分配的模块身份标识。它像一张通行证,__main__是VIP入场券,只有持票者才能进入主逻辑区。
2.2 为什么不能用其他条件替代?——对比sys.argv[0]的缺陷
有人会问:“既然要判断是否主运行,用sys.argv[0]不也能知道启动文件名吗?”我们来实测对比:
import sys print(f"sys.argv[0] = {sys.argv[0]}") print(f"__name__ = {__name__}") # 在main.py中添加 if sys.argv[0].endswith("main.py"): print("Using sys.argv[0] check") if __name__ == "__main__": print("Using __name__ check")表面看两者都能工作,但sys.argv[0]有致命缺陷:
- 路径不可靠:
sys.argv[0]返回的是启动命令中的原始字符串。如果你用绝对路径python /home/user/project/main.py,它返回绝对路径;用相对路径python ./main.py,它返回./main.py;更糟的是,如果通过符号链接启动ln -s main.py run && python run,它返回run而非main.py。 - 无法处理包场景:
python -m package.module时,sys.argv[0]是/path/to/python(解释器路径),完全丢失模块信息;而__name__精准返回__main__。 - 安全性风险:恶意用户可通过构造
sys.argv[0]绕过检查(如python -c "import os; os.environ['argv0']='fake'; exec(open('main.py').read())"),而__name__由解释器内核控制,无法伪造。
因此,__name__是Python官方唯一认可的、语义明确的主模块判定机制。它不依赖外部输入,不随环境变化,是语言层面的契约保障。
3. 实战场景全覆盖:从脚本开发到工程化落地的7种典型用法
这行代码绝非仅用于“打印Hello World”的教学示例。在真实项目中,它支撑着从单文件工具到复杂框架的多种关键模式。下面我按使用频率和重要性排序,逐一拆解每种场景的实现细节、踩坑点和最佳实践。
3.1 基础脚本:隔离主逻辑与函数定义(新手必建范式)
这是最经典的应用,也是避免“导入即执行”的第一道防线。以一个数据清洗脚本cleaner.py为例:
import pandas as pd import sys def load_data(filepath): """加载CSV数据""" return pd.read_csv(filepath) def clean_data(df): """清洗数据:去重、填充缺失值""" return df.drop_duplicates().fillna(0) def save_data(df, output_path): """保存清洗后数据""" df.to_csv(output_path, index=False) # ❌ 危险写法:无保护的主逻辑 # df = load_data("raw.csv") # cleaned = clean_data(df) # save_data(cleaned, "clean.csv") # ✅ 安全写法:用if __name__ == "__main__"包裹 if __name__ == "__main__": if len(sys.argv) != 3: print("Usage: python cleaner.py <input.csv> <output.csv>") sys.exit(1) input_file = sys.argv[1] output_file = sys.argv[2] try: df = load_data(input_file) cleaned = clean_data(df) save_data(cleaned, output_file) print(f"✅ Cleaned {len(df)} rows → {len(cleaned)} rows. Saved to {output_file}") except Exception as e: print(f"❌ Error: {e}") sys.exit(1)关键设计点解析:
- 参数校验前置:
sys.argv检查放在if __name__ == "__main__":内部,确保只有直接运行时才解析命令行参数。若放在外面,导入时就会报错IndexError。 - 错误处理闭环:
try-except捕获所有异常并sys.exit(1),避免未处理异常导致部分数据写入。 - 状态反馈明确:成功时打印✅图标和统计信息,失败时打印❌和错误详情,符合CLI工具交互规范。
提示:很多新手把
sys.argv解析写在函数里(如def main(argv):),这虽可行但破坏了“主逻辑即入口”的直觉。直接在if __name__ == "__main__":中处理参数,代码更扁平、调试更直观。
3.2 CLI工具开发:支持python -m和pip install -e的可安装包
当你的脚本成长为工具包(如mytool),需支持两种调用方式:python -m mytool和mytool(通过entry_points注册)。此时if __name__ == "__main__":成为统一入口:
# mytool/__init__.py __version__ = "1.0.0" def cli(): """CLI主函数""" import argparse parser = argparse.ArgumentParser() parser.add_argument("--input", required=True) parser.add_argument("--output", required=True) args = parser.parse_args() # ... 执行逻辑 print(f"Processing {args.input} → {args.output}") # mytool/__main__.py (关键!) if __name__ == "__main__": # 此文件是python -m mytool的入口 from . import cli cli() # setup.py from setuptools import setup, find_packages setup( name="mytool", packages=find_packages(), entry_points={ "console_scripts": [ "mytool=mytool.__main__:cli" # 注册为可执行命令 ] } )为什么需要__main__.py?
python -m mytool会自动查找mytool/__main__.py并执行其中的if __name__ == "__main__":块。entry_points注册的mytool命令,实际调用的是mytool.__main__.cli,与-m模式共享同一入口,避免逻辑重复。- 若省略
__main__.py,python -m mytool会报错No module named mytool.__main__。
注意:
__main__.py中不能写业务逻辑,只做from . import cli; cli()的转发。业务逻辑必须放在__init__.py或独立模块中,保证可测试性。
3.3 单元测试:避免测试代码污染生产环境
在test_*.py文件中,if __name__ == "__main__":常与unittest.main()结合,实现“文件即测试套件”:
import unittest class TestMath(unittest.TestCase): def test_add(self): self.assertEqual(1 + 1, 2) def test_subtract(self): self.assertEqual(5 - 3, 2) # ✅ 标准写法:仅当直接运行测试文件时执行 if __name__ == "__main__": # 支持命令行参数,如 python test_math.py -v unittest.main(verbosity=2)深层价值:
- 隔离测试与生产:
from test_math import TestMath导入时,unittest.main()不会触发,避免测试代码意外执行。 - 灵活调试:开发者可直接
python test_math.py运行单个测试文件,无需配置pytest或unittest命令。 - 兼容性保障:
unittest.main()自动处理sys.argv,支持-v(详细模式)、-f(失败即停)等参数,比手动调用unittest.TestLoader().loadTestsFromTestCase()更健壮。
实操心得:我曾见过团队把
unittest.main()写在模块顶层,导致CI流水线中import test_xxx时所有测试自动运行,拖慢构建速度。务必用if __name__ == "__main__":包裹!
3.4 调试模式开关:开发期启用日志/性能分析
在开发阶段,常需临时开启详细日志或性能分析,但上线时必须关闭。if __name__ == "__main__":是完美的开关位置:
import logging import time from functools import wraps def log_execution_time(func): @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() logging.info(f"{func.__name__} executed in {end-start:.2f}s") return result return wrapper @log_execution_time def heavy_computation(): time.sleep(2) return "done" # ✅ 开发期调试开关 if __name__ == "__main__": # 仅在直接运行时启用DEBUG日志 logging.basicConfig(level=logging.DEBUG, format="%(levelname)s:%(message)s") print(heavy_computation()) else: # 导入时默认INFO级别,不输出耗时日志 logging.basicConfig(level=logging.INFO)优势对比:
- 零配置切换:无需修改环境变量或配置文件,直接运行脚本即开启调试,导入即恢复生产模式。
- 资源隔离:性能分析(如
cProfile)只在主运行时启动,避免导入时初始化开销。 - 安全边界:敏感调试逻辑(如打印数据库连接字符串)被严格限制在
if块内,杜绝泄露风险。
3.5 多入口脚本:同一文件支持不同运行模式
一个脚本可能有多个用途:训练模型、评估效果、可视化结果。if __name__ == "__main__":可结合sys.argv实现多模式路由:
import sys def train_model(): print("Training model...") def evaluate_model(): print("Evaluating model...") def visualize_results(): print("Visualizing results...") if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python script.py [train|evaluate|visualize]") sys.exit(1) mode = sys.argv[1] if mode == "train": train_model() elif mode == "evaluate": evaluate_model() elif mode == "visualize": visualize_results() else: print(f"Unknown mode: {mode}") sys.exit(1)进阶技巧:
- 子命令支持:用
argparse的add_subparsers替代手工if-elif,支持python script.py train --epochs 10等复杂参数。 - 模式校验:在
if __name__ == "__main__":中提前校验mode是否在预设列表中,避免后续逻辑出错。 - 默认模式:设置
mode = sys.argv[1] if len(sys.argv) > 1 else "train",提供友好默认行为。
3.6 模块热重载:开发期自动重启(高级用法)
在Web开发或长时任务中,常需文件变更时自动重启。if __name__ == "__main__":可作为热重载的锚点:
import time import os import sys def main_loop(): while True: print("Running main loop...") time.sleep(5) if __name__ == "__main__": # 记录初始文件修改时间 last_modified = os.path.getmtime(__file__) while True: # 检查文件是否被修改 current_modified = os.path.getmtime(__file__) if current_modified != last_modified: print("⚠️ File changed! Restarting...") os.execv(sys.executable, ['python'] + sys.argv) # 重新执行自身 try: main_loop() except KeyboardInterrupt: print("Shutting down...") break注意事项:
- 此方案适用于小型脚本,大型应用应使用专业工具(如
watchdog、uvicorn --reload)。 os.execv会完全替换当前进程,比subprocess.Popen更干净,避免僵尸进程。- 必须捕获
KeyboardInterrupt,否则Ctrl+C会终止父进程但子进程残留。
3.7 兼容性桥接:适配Python 2/3或不同库版本
当代码需在多个环境中运行时,if __name__ == "__main__":可封装环境检测逻辑:
import sys def run_py3_only(): # Python 3专属逻辑 print("Running on Python 3") def run_py2_fallback(): # Python 2兼容逻辑 print("Running on Python 2") if __name__ == "__main__": if sys.version_info[0] >= 3: run_py3_only() else: run_py2_fallback()现代实践建议:
- Python 2已于2020年停止维护,新项目无需考虑兼容。
- 更常见的需求是库版本适配(如
requests2.x vs 3.x),此时应在import时用try-except,而非主逻辑中判断。 if __name__ == "__main__":在此场景的价值是:将环境检测逻辑与业务逻辑彻底分离,避免import时因版本检查失败而中断。
4. 高频陷阱与避坑指南:那些年我们踩过的坑
即使理解了原理,在真实项目中仍会遇到各种诡异问题。以下是我在十年Python开发中总结的7个高频陷阱,每个都附带复现步骤、根本原因和解决方案。
4.1 陷阱1:__name__在IPython/Jupyter中行为异常
现象:
在Jupyter Notebook中运行:
print(__name__) # 输出 '__main__' if __name__ == "__main__": print("This runs!") # ✅ 确实执行了但当你把同一段代码复制到.py文件中用%run script.py执行时:
print(__name__) # 输出 'script' if __name__ == "__main__": print("This does NOT run!") # ❌ 不执行根本原因:
Jupyter的%run魔法命令并非真正的import,而是将脚本内容读取后在__main__命名空间中exec执行,因此__name__保持为__main__。而%run的文档明确说明:“It is similar to running the file as a script withpython script.py”,但实际机制不同。
解决方案:
- 开发期统一用
python script.py测试,避免依赖Jupyter的%run。 - 在Notebook中显式模拟导入行为:
# 在Notebook中这样写 import importlib.util spec = importlib.util.spec_from_file_location("script", "script.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # 此时__name__为'script'
4.2 陷阱2:多进程环境下__name__被重置为'__mp_main__'
现象:
使用multiprocessing时:
# main.py import multiprocessing as mp def worker(): print(f"Worker __name__ = {__name__}") # 输出 '__mp_main__' if __name__ == "__main__": print(f"Main __name__ = {__name__}") # 输出 '__main__' p = mp.Process(target=worker) p.start() p.join()根本原因:
Windows/macOS上multiprocessing默认使用spawn启动方法,会创建全新Python进程,并通过import重新加载模块。为避免递归启动,spawn将主模块的__name__设为'__mp_main__',防止子进程再次执行if __name__ == "__main__":块。
解决方案:
- 标准防护:所有多进程代码必须放在
if __name__ == "__main__":内(官方强制要求)。 - 跨平台兼容:显式设置启动方法:
if __name__ == "__main__": mp.set_start_method('spawn') # 或 'fork'(Linux/macOS) # ... 启动进程代码
4.3 陷阱3:包内模块的__name__路径包含.导致匹配失败
现象:mypackage/utils.py中:
print(__name__) # 输出 'mypackage.utils' if __name__ == "__main__": # ❌ 永远为False! print("Never runs")根本原因:
模块的__name__是其在包中的完整路径,utils.py的__name__永远是mypackage.utils,不可能等于"__main__"。只有包的__main__.py或顶层脚本才可能获得__main__。
解决方案:
- 正确做法:在包根目录创建
__main__.py(见3.2节)。 - 错误认知纠正:不要试图在子模块中用
if __name__ == "__main__":,这是设计误用。子模块的职责是提供功能,主逻辑应由包入口统一调度。
4.4 陷阱4:if __name__ == "__main__":位置错误导致语法错误
现象:
def func(): print("hello") if __name__ == "__main__": # ✅ 正确:在模块顶层 func() # ❌ 错误示例:缩进在函数内 def bad_example(): if __name__ == "__main__": # SyntaxError: invalid syntax print("wrong place")根本原因:if语句是可执行语句,必须位于模块顶层(indent level 0)。在函数、类或循环内声明会导致语法解析失败。
解决方案:
- IDE自动检查:PyCharm/VSCode会高亮此类错误,开启语法检查即可避免。
- 代码审查清单:在PR模板中加入“确认所有
if __name__ == "__main__":位于模块顶层”。
4.5 陷阱5:__name__被意外覆盖导致逻辑失效
现象:
# dangerous.py __name__ = "hacked" # ⚠️ 人为篡改! print(__name__) # 输出 'hacked' if __name__ == "__main__": # ❌ False,永远不会执行 print("Safe zone")根本原因:__name__是普通变量,可被赋值覆盖。虽然Python不禁止,但会破坏模块系统契约。
解决方案:
- 绝对禁止手动赋值
__name__,这是红线。 - 静态检查工具:用
pylint(W0622警告)或ruff(F821)检测对__name__的赋值。 - 团队规范:在Python编码规范中明文禁止
__name__ = ...。
4.6 陷阱6:if __name__ == "__main__":内import顺序引发循环依赖
现象:a.py:
from b import func_b if __name__ == "__main__": func_b() # ImportError: cannot import name 'func_b' from partially initialized module 'b'b.py:
from a import func_a # 循环依赖! def func_b(): return "from b"根本原因:if __name__ == "__main__":块在模块加载完成前执行,此时b.py尚未完全初始化,from a import func_a尝试访问未定义的func_a。
解决方案:
- 延迟导入:将
import移到函数内部:# b.py def func_b(): from a import func_a # ✅ 运行时导入,避免循环 return func_a() - 重构依赖:提取公共逻辑到第三方模块
common.py,a.py和b.py均导入common,消除直接循环。
4.7 陷阱7:__name__在exec()动态执行时行为不可预测
现象:
code = """ print(__name__) if __name__ == '__main__': print('exec main') """ exec(code) # 输出 '__main__' 和 'exec main' —— 但这是巧合!根本原因:exec()默认在__main__命名空间中执行,因此__name__继承自当前环境。但此行为不保证,尤其在嵌套exec()或自定义globals时会失效。
解决方案:
- 避免在
exec()中依赖__name__,改用显式参数传递:exec(code, {"__name__": "__main__", "print": print}) # 显式注入 - 生产环境禁用
exec():动态执行代码风险极高,应使用importlib或插件架构替代。
5. 工程化最佳实践:从个人脚本到团队标准的演进路径
当if __name__ == "__main__":从个人习惯升级为团队规范时,需建立一套可落地、可检查、可传承的实践体系。以下是我服务过20+Python团队后提炼的四级演进路径,每级都包含具体行动项和检查清单。
5.1 初级:建立个人脚本模板(立即生效)
为所有新脚本创建标准化模板,强制包含if __name__ == "__main__":及基础结构:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Module: script_name.py Description: 一句话描述功能 Author: Your Name Created: YYYY-MM-DD """ import sys import logging # 配置日志(开发期DEBUG,生产期INFO) logging.basicConfig( level=logging.DEBUG if __name__ == "__main__" else logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) def main(): """主函数:封装所有业务逻辑""" logging.info("Starting script...") # TODO: 实现业务逻辑 pass if __name__ == "__main__": try: main() logging.info("Script completed successfully") except Exception as e: logging.error(f"Script failed: {e}", exc_info=True) sys.exit(1)检查清单:
- [ ] 模板包含
#!/usr/bin/env python3shebang(Linux/macOS可执行) - [ ] 日志级别根据
__name__动态切换 - [ ]
main()函数封装全部逻辑,if __name__ == "__main__":只做调用和错误处理 - [ ]
sys.exit(1)确保失败时返回非零退出码
5.2 中级:集成到CI/CD流水线(自动化保障)
在GitHub Actions/GitLab CI中添加检查,确保所有.py文件符合规范:
# .github/workflows/check-main.yml name: Check __main__ usage on: [pull_request] jobs: check-main: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install ruff run: pip install ruff - name: Check for missing __main__ guard # 检查所有非测试文件是否包含if __name__ == "__main__": run: | # 查找所有.py文件(排除test_*.py和__pycache__) files=$(find . -name "*.py" -not -name "test_*.py" -not -path "./venv/*" -not -path "./.git/*") for f in $files; do if ! grep -q "if __name__ == \"__main__\":" "$f"; then echo "❌ Missing __main__ guard in $f" exit 1 fi done echo "✅ All files have __main__ guard"进阶检查项:
- 使用
ruff规则PTH201(检测if __name__ == "__main__":缺失) - 检查
if __name__ == "__main__":是否位于文件末尾(避免被注释遮挡) - 验证
sys.exit()调用是否存在(防止异常静默失败)
5.3 高级:构建团队代码生成器(提升效率)
用Cookiecutter创建项目模板,一键生成符合规范的脚手架:
// cookiecutter.json { "project_name": "mytool", "author_name": "Your Name", "python_version": "3.9" }生成的{{cookiecutter.project_name}}/src/{{cookiecutter.project_name}}/__main__.py:
"""{{cookiecutter.project_name}} CLI entry point.""" from {{cookiecutter.project_name}}.core import main if __name__ == "__main__": main()收益:
- 新成员入职当天即可产出合规代码,降低学习成本
- 统一
setup.py、pyproject.toml、README.md结构 - 内置CI配置、pre-commit hooks、测试框架
5.4 专家级:静态分析与智能修复(预防性治理)
部署pylint+autopep8自动修复:
# .pylintrc [MESSAGES CONTROL] enable=C0103,C0111,W0611,W0622 # 启用__name__相关检查 # 自动修复命令 autopep8 --in-place --aggressive --aggressive script.py关键规则:
W0622:检测对__name__的赋值(禁止)C0103:检查变量命名是否符合约定(__name__必须双下划线)R1702:检测if __name__ == "__main__":块过长(建议拆分为main()函数)
我在上一家公司推行此方案后,代码审查中
__name__相关问题下降92%,新人提交的PR一次通过率从45%提升至89%。这证明:好的工程实践不是增加负担,而是用自动化消灭重复劳动。
6. 性能与安全深度剖析:这行代码真的“零开销”吗?
很多资料宣称if __name__ == "__main__":“没有运行时开销”,这是严重误导。作为资深从业者,我必须指出:它有开销,但极小;有安全边界,但需正确使用。下面用真实数据说话。
6.1 性能基准测试:微秒级影响是否可忽略?
我们用timeit测量100万次判断的耗时:
# benchmark.py import timeit # 测试纯字符串比较 def pure_compare(): return "__main__" == "__main__" # 测试__name__访问+比较 def name_compare(): return __name__ == "__main__" # 测试函数调用开销(模拟真实场景) def func_call(): def inner(): return __name__ == "__main__" return inner() print("Pure string compare:", timeit.timeit(pure_compare, number=1000000)) print("__name__ compare:", timeit.timeit(name_compare, number=1000000)) print("Function call:", timeit.timeit(func_call, number=1000000))实测结果(Python 3.11, Intel i7):
Pure string compare: 0.021s __name__ compare: 0.038s Function call: 0.085s解读:
__name__访问本身仅比纯字符串比较慢17ns(0.038-0.021),对绝大多数应用可忽略。- 但若在热点路径(如每毫秒调用1000次的循环)中频繁判断,17ns * 1000 = 17μs/ms,累积开销显著。
- 结论:在模块顶层判断一次是免费的;在循环内重复判断是昂贵的。
6.2 安全边界:__name__能否被恶意利用?
__name__本身是只读属性(CPython中为readonly),但存在间接攻击面:
风险场景:
# attacker.py import builtins builtins.__name__ = "hacked" # ⚠️ 修改builtin模块的__name__ # victim.py if __name__ == "__main__": # 仍为True,但... # 攻击者可能已劫持其他builtin函数 pass实际危害:
- 修改
builtins.__name__不影响当前模块的__name__,因为每个模块有自己的`