很多人刚学 Python 的时候都会问一句:为啥这个语言这么火,居然连个 main 函数都没有?不科学啊,C 有、C++ 有、Java 也有,怎么到 Python 这就不要了。
我当时第一次写 Python,也是习惯性地在编辑器里敲了个:
int main() { // ...}然后一愣:诶?这语法直接红了。
咱先别急着吐槽 Python,先回忆下其他几门常见语言里,main 是个啥角色。
在 C / C++ / Java 这类“编译型 + 运行时”的语言里,大致流程是这样的:
main——你必须告诉运行时:“从这里开始跑”。比如 C:
intmain(int argc, char *argv[]){// 程序从这里开始执行}Java:
publicclassApp{publicstaticvoidmain(String[] args){// 程序从这里开始执行 }}对这几门语言来说,main 是语言规范里写死的入口,没有就启动不了,你写得再漂亮也白搭。
之前看数据库选型的时候就在一篇文章里看到类似的设计哲学:MySQL 和 Postgres 在架构上的一些选择,其实也是“先约定一个固定的入口、结构、层级”,用统一规范换取可控的行为。
重点来了:Python 根本没打算搞一个“固定函数入口”。
Python 的执行模型更像是:我就拿着这个文件,从上到下一行一行解释执行。
你在命令行敲:
python app.py解释器内部做的事,大致可以粗暴地理解成:
app.py 当成一个“模块对象”加载进来;也就是说:对 Python 来说,一个 .py 文件本身就是“入口”。 你在最外层写的任何语句(不是函数里的那种),都会在运行时被直接执行:
# app.pyprint("程序开始了")x = 10print("x =", x)运行:
python app.py终端上就会老老实实输出这两行,因为解释器真的就是“从头到尾”把这几句跑了一遍。
从这个角度看,Python 的“入口函数”其实就是:整个模块顶层代码,根本不需要一个专门的 main() 来兜底。
if __name__ == "__main__" 是干嘛的?很多人一看到这句就本能抵触感:这不还是 main 吗?只是换了个写法。
其实这句不是“语言强制的入口”,而是一种习惯用法,或者说“约定俗成的写法”。
先看一个最常见的模板:
# app.pydefmain(): print("hello python main")if __name__ == "__main__": main()你运行:
python app.py会执行 main(),没问题。
但关键在于:这句 if 是可以删掉的,语言不会因此报错;只是你就少了一个“只在直接运行时才执行的入口”。
要理解它的意义,得知道 __name__ 到底是啥。
.py 文件在被加载成模块时,都会有个 __name__;__name__ 等于模块名,比如 utils.math_utils;__name__ 就会被设置成 "__main__"。所以这句判断:
if __name__ == "__main__": main()翻译成人话就是:
“只有当这个文件是被用户直接运行的时候,我才调
main();如果只是被别的地方 import,就别动。”
为什么要这么干?为了一份代码两种用法:既能当脚本跑,也能当库被复用。
比如现在你写了个图片压缩小工具,既想:
python compress.py input.jpg output.jpg 命令行用;import compress 调你封装的函数。用刚才那个模式就很顺:
# compress.pyfrom PIL import Imageimport sysfrom pathlib import Pathdefcompress_image(src, dst, quality=60): img = Image.open(src) img.save(dst, optimize=True, quality=quality) print(f"压缩完成: {src} -> {dst}")defmain():if len(sys.argv) < 3: print("用法: python compress.py <源文件> <目标文件> [质量(0-100)]")return src = Path(sys.argv[1]) dst = Path(sys.argv[2]) quality = int(sys.argv[3]) if len(sys.argv) > 3else60 compress_image(src, dst, quality)if __name__ == "__main__": main()别人如果只想复用压缩逻辑:
# other_project.pyfrom compress import compress_imagecompress_image("a.jpg", "b.jpg", quality=80)这时候 compress.py 里的 main() 不会被执行,因为它被 import 进来的时候,__name__ 不是 "__main__"。
所以你会发现:
main() 里;main 完全是你自己取名,也可以叫 run、cli、start,语言不管;if __name__ == "__main__" 只是一个“开关”:决定“直接运行时多做一件事”。这和 C / Java 的那种“必须给我一个 main,不然我不启动”完全不是一个概念。
粗暴一点说,因为 Python 一开始定位就比较“脚本化、交互化”,它的哲学更偏:
“你写啥我就帮你从上到下跑啥,别让我多记一个固定名字。”
如果强行设一个固定入口函数,比如规定“必须有 def main()”,就会有几件事变得很累:
所有小脚本都得套一层模板: 原本一行 print("hello") 的事,非得写成:
defmain(): print("hello")if __name__ == "__main__": main()交互式编程体验会变差: Python 的一大优势是 REPL(解释器里一行一行试),你在那种场景下是没有 main 一说的; 有了固定 main,概念上会很割裂。
模块级代码就不好用了: 现在你可以在模块顶层做一些初始化、注册、日志配置等等,写完即生效; 如果强制 main,大家就会为了“入口要干净”把这些东西塞进 main(),反而绕了一圈。
更重要的一个点:Python 的“程序 = 若干模块的组合”这件事,被设计得非常彻底。
启动方式也很灵活,比如:
python app.py # 从文件开始python -m my_package # 从包里的 __main__.py 开始当你用 python -m my_package 时,其实是执行了包里的 __main__.py 这个模块,就相当于“这个包自己定义了一个入口脚本”。
my_package/__main__.py 里面爱写不写 main(),完全是你的自由。
说句实话,我自己写项目的时候,还是挺喜欢搞一个 main 的,但那真的只是“架构层面的约定”,跟语言无关。
比如稍微正式一点的项目,我会搞成这样:
# app/main.pyimport asynciofrom .config import settingsfrom .server import start_http_serverasyncdefmain(): print("启动配置:", settings.model_dump())await start_http_server(settings.host, settings.port)if __name__ == "__main__": asyncio.run(main())好处有:
main.py 里的那个 main;argparse 或者 click 也很好挂。比如稍微加个参数解析:
# app/main.pyimport argparsedefparse_args(): parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=8000) parser.add_argument("--debug", action="store_true")return parser.parse_args()defmain(): args = parse_args()if args.debug: print("调试模式开启") print(f"服务启动在端口 {args.port}")# TODO: 真正启动服务if __name__ == "__main__": main()你看,这种写法其实跟 C / Java 的 main 挺像的,但关键差别在:
__main__.py:项目“入口脚本”的另一种玩法还有个经常被忽略的点:Python 对包也提供了一种“入口约定”,就是 __main__.py。
假设你有结构:
my_app/ __init__.py __main__.py api.py models.py__main__.py 写成这样:
# my_app/__main__.pyfrom .api import run_serverdefmain(): print("通过 -m 启动 my_app") run_server()if __name__ == "__main__": main()这时候,你可以直接在命令行里:
python -m my_app解释器会自动去找 my_app.__main__ 模块来执行。 也就是说,包级别的“入口脚本”是通过 __main__.py 这个文件名约定好的,而不是通过某个函数名。
所以 Python 的思路一直是:用模块 / 文件名做入口约定,而不是函数名。
Python 不是“没有 main”,而是“不需要规定 main”
如果一定要把上面这堆话压成一句话,大概就是:
Python 不是做不到 main,而是它把“程序入口”这件事设计成了“模块级的、文件级的约定”,而不是“强制一个叫 main 的函数”。
你可以:
main(),在文件顶层直接写脚本逻辑,小工具写起来飞快;main(),加上 if __name__ == "__main__",让项目入口清晰;__main__.py 和 python -m,把整个包当成一个“可执行程序”。这些都是 Python 给你的灵活性。
所以如果以后还有人问你:“Python 怎么连个 main 函数都没有?” 你就可以很淡定地说一句:
“不是没有,是根本用不着强制一个。你想写就自己约定一个,不想写就从文件第一行开始跑,这就是 Python 的风格。”
行,先这样,等哪天你写 CLI 工具或者 Web 服务的时候,咱再一起聊聊怎么设计一个“好用的 main 入口函数”和项目结构。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html