← 返回题目列表

Python 中 if __name__ == "__main__" 有什么作用?模块导入和脚本执行有什么区别?

高频 中等 第 9 / 27 题 更新于 2026/07/31
__name____main__模块导入入口函数

简化版

if __name__ == "__main__" 用来判断当前文件是被直接执行,还是被其他模块导入。直接执行时 __name__ 等于 "__main__",导入时 __name__ 是模块名,所以这段判断常用来放命令行入口、调试代码或示例代码,避免导入模块时自动执行。

详细版

Python 文件既可以作为脚本运行,也可以作为模块导入。作为脚本运行时,解释器会把当前模块的 __name__ 设为 "__main__";作为模块导入时,__name__ 通常是包路径形式的模块名,例如 pkg.worker

常见写法是:

def main():
    print("run program")

if __name__ == "__main__":
    main()

这样别人 import this_module 时,只会加载函数和类定义,不会自动跑主流程。面试回答要强调:顶层代码在导入时也会执行,所以模块里不要把副作用很重的逻辑直接写在顶层;入口逻辑应该放进 main(),再用 __main__ 判断保护起来。

完整版教学

一、为什么同一个 .py 文件有两种身份

Python 的一个 .py 文件既可以是“可直接运行的脚本”,也可以是“可被复用的模块”。如果没有身份区分,写在文件底部的测试代码、命令行逻辑、启动服务逻辑,在别人导入这个模块时也会执行,造成非常隐蔽的副作用。

比如下面这个文件:

# tools.py
def add(a, b):
    return a + b

print(add(1, 2))

当你只是想 from tools import add 时,print(add(1, 2)) 也会执行。这显然不符合“导入只是复用函数”的直觉。if __name__ == "__main__" 就是用来把“被直接运行时才执行”的逻辑隔离出来。

二、__name__ 到底是什么

每个模块都有一组特殊属性,__name__ 是其中之一。模块被导入时,__name__ 通常等于它在导入系统里的名字;模块被当作入口脚本执行时,__name__ 被设置为 "__main__"

可以用两个场景对比:

python tools.py
  -> tools.py 的 __name__ == "__main__"

import tools
  -> tools.py 的 __name__ == "tools"

如果是包里的模块:

from app.jobs import worker
  -> worker.py 的 __name__ == "app.jobs.worker"

这个差异就是判断入口的依据。它不是魔法开关,而是解释器在创建模块对象时设置的普通模块属性。

三、为什么导入也会执行顶层代码

导入模块不是简单把文件内容“声明一下”。CPython 第一次导入模块时,会找到模块文件、创建模块对象、执行模块顶层代码、把模块放进 sys.modules 缓存。函数定义、类定义、赋值语句、打印语句都会在这个阶段执行。

import tools
  |
  v
查找模块文件
  |
创建模块对象
  |
执行 tools.py 顶层代码
  |
放入 sys.modules

这解释了为什么顶层副作用危险。假设一个模块顶层直接连接数据库、启动线程、删除临时文件,那么每个导入它的人都可能触发这些动作。更好的方式是只在顶层放定义,把启动逻辑放到 main() 或显式函数里。

模块顶层适合放“定义”,不适合放“重副作用执行”;入口逻辑要用 __main__ 保护。

四、main() 函数为什么也重要

有些人会直接把入口代码写在 if __name__ == "__main__" 下面,这可以工作,但更推荐再包一层 main()。原因是 main() 可以被测试调用,可以清晰返回退出码,也能让变量作用域更干净。

def main(argv=None) -> int:
    argv = argv or []
    print("args:", argv)
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

这个写法有两个好处:测试时可以直接测 main(["--dry-run"]),命令行执行时又能通过 SystemExit 把返回码交给系统。对于小脚本这可能显得正式,但对工程项目很有价值。

写法优点问题
顶层直接执行最短导入就触发副作用
只写 if __main__能防导入执行测试入口略不方便
main() + if __main__清晰、可测、可返回码多几行代码

五、和 python -m 有什么关系

python -m package.module 会把某个模块按脚本入口执行。此时模块的 __name__ 也会是 "__main__",但它仍然能保留包上下文,常用于运行包内模块,避免直接执行文件导致相对导入出问题。

例如:

python -m app.jobs.worker

比下面这种直接跑文件更稳:

python app/jobs/worker.py

直接运行包内文件时,模块可能不知道自己属于哪个包,相对导入容易失败。-m 让解释器按模块导入规则定位,再把它作为入口执行。很多命令行工具和测试工具都依赖这个机制。

六、和 import 缓存、重复执行的关系

普通导入会把模块缓存到 sys.modules,同一个解释器进程里再次导入通常不会重复执行顶层代码。但直接作为脚本运行时,它以 "__main__" 身份存在;如果这个脚本内部又被按模块名导入,可能出现同一个文件以两个模块身份加载的情况。

一个简化例子:

python app/worker.py
  -> 加载为 __main__

worker.py 内部或其他地方 import app.worker
  -> 可能再加载为 app.worker

这样模块级单例、全局状态、注册表可能出现两份。工程上避免这种问题的办法是用 python -m app.worker 运行包内模块,并减少顶层可变全局状态。

七、常见误区与追问

  • 误区:导入模块不会执行任何代码。 导入会执行模块顶层代码,只是函数体内部不会在定义时执行。
  • 误区:if __name__ == "__main__" 能提升性能。 它主要控制入口逻辑是否执行,不是性能优化工具。
  • 误区:所有脚本都必须写这个判断。 纯一次性脚本可以不写;可复用模块、包内入口、需要测试的脚本更应该写。
  • 追问:为什么推荐写 main() 入口逻辑更清晰、变量作用域更干净,也方便测试和返回退出码。
  • 追问:python file.pypython -m pkg.module 有什么区别? 后者按模块路径执行,包上下文更正确,相对导入更稳定。
  • 追问:导入后为什么第二次不再执行顶层代码? 模块会缓存到 sys.modules,重复导入通常直接复用模块对象。
  • 追问:脚本和模块双重身份会造成什么坑? 同一文件可能以 __main__ 和真实模块名加载两次,导致全局状态重复。

八、加强记忆

把这题记成“导入会执行顶层代码,所以入口要关门”。__name__ 是模块身份,直接运行时叫 "__main__",被导入时叫模块名;if __name__ == "__main__" 就是只在直接运行时打开入口。工程上再配合 main()python -m,既能避免导入副作用,又能让包内模块、测试和命令行入口更稳。