Python 里 if __name__ == "__main__",真没你想得那么神秘
昨天晚上十一点多,我在公司楼下蹲着吃宵夜(真的是蹲着,凳子都被人搬走了),我们组那个小李微信上发了张截图过来,就一行:
if __name__ == "__main__": main()然后一句话:哥,这到底是个啥玩意?我看每个教程都这么写,就是…看不懂但大受震撼。
我一边啃鸡腿一边跟他解释,发现好多新人都对这个东西是“似懂非懂”的,要么背概念,要么只知道“入口函数要这么写”,为啥要这么干、底层到底发生了啥,反而没人说清楚。那就顺手跟你们一起捋一下。
先搞清楚:__name__ 是个啥
你就把每个 .py 文件想成一个“模块身份证”。Python 解释器加载这个模块的时候,会给它贴一个标签,塞到变量 __name__ 里。
有两种情况:
在命令行敲:
python demo.pydemo.py 这个文件,此刻就是“老大哥”,Python 会给它一个特别的名字:
# demo.pyprint(__name__)直接跑一下:
python demo.py# 输出:__main__所以:**当前这个被直接运行的模块,它的 __name__ 就是 "__main__"**。
再搞一个文件:
# utils.pyprint("utils 里打印 __name__:", __name__)然后新建一个:
# app.pyimport utilsprint("app 里打印 __name__:", __name__)命令行跑:
python app.py你会看到类似输出:
utils 里打印 __name__: utilsapp 里打印 __name__: __main__也就是说:
__name__ == "__main__"import utils 进来的那个,__name__ == "utils"(就是模块名)到这基本能看懂那一行判断在干嘛了:就在问——我现在是被直接跑的,还是被别人当工具库引用的?
那为啥要写这么一坨 if?
你想象一个很常见的场景:写了个发短信脚本 sms_tool.py,顺手写成这样:
# sms_tool.pydefsend_marketing_sms(): print("开始给所有用户群发短信...")send_marketing_sms()你本来只是打算 python sms_tool.py 的时候它干活。结果隔壁同事觉得你这个发短信函数写得不错,在他项目里直接:
import sms_tool然后,呵呵了——他一启动服务,就自动给所有用户群发一遍短信…这要是在线上,基本能原地离职。
所以正常写法应该是:
# sms_tool.pydefsend_marketing_sms(): print("开始给所有用户群发短信...")if __name__ == "__main__":# 只有直接运行 sms_tool.py 时才会执行 send_marketing_sms()这样:
import sms_tool 只是来复用 send_marketing_sms 函数,不会自动乱发这个 if 就相当于一个“安全开关”:把“当脚本跑时要执行的逻辑”和“模块对外提供的函数/类”隔离开。
我之前写数据库压测脚本的时候也踩过类似坑,没加这玩意儿,结果一被别的工具 import,压测脚本直接在测试环境全速干活,把库打到 100%…后来才老实每个脚本都加这个判断。
再看一个最常见的套路:自测代码都塞这里
比如你写了个排序函数:
# sort_utils.pydefbubble_sort(nums): n = len(nums)for i in range(n):for j in range(0, n - i - 1):if nums[j] > nums[j + 1]: nums[j], nums[j + 1] = nums[j + 1], nums[j]return numsif __name__ == "__main__":# 简单自测 data = [5, 3, 1, 4, 2] print("原始数据:", data) print("排序结果:", bubble_sort(data))这里有两个点:
from sort_utils import bubble_sort,不会看到你那些打印日志python sort_utils.py 的时候,就能当小脚本跑一跑做单元测试很多人问“写算法题要不要建测试文件”,其实像这种简单工具类,直接在模块下面用 if __name__ == "__main__": 把测试写了就行,够用又直观。
稍微再深一点:整个项目跑起来的时候,它在哪?
比如你有个项目结构:
project/ app.py utils.pyapp.py 里写:
from utils import hellodefmain(): print("app main 里:") hello()if __name__ == "__main__": main()utils.py:
defhello(): print("hello from utils, __name__ =", __name__)if __name__ == "__main__": print("只在单独跑 utils.py 时才会出现这行")现在你在 project 目录下敲:
python app.py打印大概是:
app main 里:hello from utils, __name__ = utilsutils 下面那句“只在单独跑 utils.py 时才会出现这行”,不会被执行。
所以整套机制可以这么理解:
app.py),它会拿到 "__main__"有同学会问:那我用 python -m package.module 跑包里的模块时,这个判断还准吗?
简单说一句:对你日常写业务来说,可以继续当它是准的。用 -m 跑的时候,被当入口的那个模块,也会得到 __name__ == "__main__",它下面那段代码同样会执行,其他模块照样是自己名字。内部再有一点细节,比如 __spec__ 之类的,就先别管了,你真要造框架再去研究那一层。
我现在新建 Python 文件,基本肌肉记忆就是:
defmain():# 这里随便写点要干的事passif __name__ == "__main__": main()哪怕暂时只是一段小脚本,这么写也有好处:
行了,我鸡腿也啃完了,__name__ 这茬先聊到这,你要是还有那种“看了很多年但一直没搞懂”的 Python 细节,可以顺手发我一段代码截图,我有空一块给你拆开唠。