掘金 人工智能 21小时前
【不背八股】1.if __name__ == "__main__" 有什么作用?
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文旨在深入探讨Python多进程编程中一个常见但关键的细节:为何在Windows和macOS平台上启动多进程时,需要使用`if __name__ == '__main__':`结构。文章从一个实际的RuntimeError出发,解释了`__name__`变量在Python模块中的作用,以及直接运行与导入模块时其值的不同。重点阐述了Windows和macOS默认采用`spawn`方式启动子进程,会重新导入主模块,若无`if __name__ == '__main__':`保护,会导致子进程重复执行主代码,引发无限递归错误。而Linux的`fork`方式则不会引发此问题。通过此文,帮助开发者清晰理解这一“八股文”背后的原理,实现更健壮的多进程应用。

💡 `__name__`变量是Python内置的,用于标识模块的身份。当文件被直接运行时,`__name__`的值为`'__main__'`;当文件被其他模块导入时,`__name__`的值则为该模块的名称。这一特性是理解后续多进程问题的基础。

🚀 在Windows和macOS操作系统中,Python的`multiprocessing`模块默认采用`spawn`方式创建子进程。这意味着会启动一个新的Python解释器进程,并重新加载(导入)主模块的代码。这是一个与Linux的`fork`方式(直接复制父进程内存状态)根本不同的机制。

⚠️ 当子进程通过`spawn`方式启动并重新导入主模块时,如果主模块中的代码(包括创建子进程的代码)没有被`if __name__ == '__main__':`保护起来,那么子进程会再次执行这部分代码,导致子进程创建子进程,形成无限递归,最终引发`RuntimeError: An attempt has been made to start a new process before the current process has finished its bootstrapping phase.`错误。

✅ 因此,将多进程创建的代码块(如`p = Process(...)`和`p.start()`)放在`if __name__ == '__main__':`语句块内,可以确保这部分代码只在脚本作为主程序直接运行时执行,而在被子进程导入时不会被重复执行,从而有效避免了因重复启动子进程而产生的错误。这是一种保证代码在不同场景下行为一致性的重要编程实践。

起名动机

最近开始准备秋招,技术圈惯例会把常见的面试问答题戏称为“八股文”,网上流传不少这样的材料,写得晦涩难懂,以至于让好多求职者去背诵

在我看来,用文科思维学工科毫无意义,八股实际就是一些基础计算机科学知识,如何用清晰地方式去理解,实践,应用才更有价值。

因此,打算开个新的系列文章[不背八股],用一种全新的表述方式,重新理解八股面试题。

从一个报错开始

在项目实践中,遇到报错:

RuntimeError:        An attempt has been made to start a new process before the        current process has finished its bootstrapping phase.        This probably means that you are not using fork to start your        child processes and you have forgotten to use the proper idiom        in the main module:            if __name__ == '__main__':                freeze_support()                ...        The "freeze_support()" line can be omitted if the program        is not going to be frozen to produce an executable.        To fix this issue, refer to the "Safe importing of main module"        section in https://docs.python.org/3/library/multiprocessing.htm

DeepSeek**给出的解释是:

若主模块未保护入口点(即缺少 if name == 'main':),会导致子进程重复执行主模块代码。

将主代码移至if __name__ == '__main__':下之后,果然问题解决了。

这不由让我进一步思考:多数情况下,把这个操作作为一种惯例,没有思考过为什么要这么做,这么做为什么有效。

正好看到一道面试题:if __name__ == "__main__"有什么作用?借此把这个问题再探究一下。

__name__是什么?

首先要理解__name__的作用。

__name__ 是 Python 在加载模块(文件)时,解释器自动设置的一个全局变量,用来标识这个模块的“名称”。

举个简单的例子,创建一个hello.py

print("我是:", __name__)

运行输出结果:

我是: __main__

再建立一个新的文件test.py,去导入这个文件

import hello

运行输出结果:

我是: hello

因此可以得到结论:

当运行一个 Python 文件,或者通过 import 导入一个模块,解释器隐式去做这件事:

# 如果是直接运行import typesmodule = types.ModuleType("__main__")  # 创建模块对象module.__name__"__main__"exec(open("foo.py").read(), module.__dict__)  # 执行代码# 如果是模块导入import importlibspec = importlib.util.find_spec("模块名称")module = importlib.util.module_from_spec(spec)spec.loader.exec_module(module)

因此,在一些模块中,往往会看到用if __name__ == "__main__"包裹的代码块,用来执行单元测试(cell test)。

多进程启动问题

下面回到开篇遇到的问题,为什么会出现这个error。

可以用以下代码,最小程度复现出该问题:

from multiprocessing import Processdef worker():    print("子进程")p = Process(target=worker)p.start()

实际上,该问题只会在WindowsmacOS平台上发生。

对于Python 多进程(multiprocessing),Windows 和 macOS 默认采用spawn的方式进行启动。

它会新开一个空白的 Python 解释器进程,去重新导入主模块,因为主模块没放置在if __name__ == "__main__"之中,它被导入时,又会被再次执行一下,这就导致无限递归执行,出现此问题。

Linux 采用 fork 的方式,操作系统会直接复制当前进程的内存和运行状态,不会重新执行主模块,因此不会出现该问题。

项目LinuxWindows/macOS
启动方式forkspawn
主模块会不会重新执行❌ 否✅ 是
是否必须加 if __name__ == '__main__'推荐✅ 必须
报错可能性较低较高(会崩)

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Python 多进程 if __name__ == '__main__' 编程实践
相关文章