揭秘 Python 中最奇怪的一行代码
打开几乎任何一个 Python 文件,最后你都会在末尾看到这个:
if __name__ == "__main__":
main()
它看起来像一段神奇的样板代码。但一旦你知道它的作用,就会发现它其实是个非常实用的功能。它之所以存在,是因为 Python 不区分“脚本”和“库”。
前提:一个文件的两种用法
Python 中的每个 .py 文件都有两种不同的用法:
- 作为脚本:用
python3 file.py直接运行。 - 作为模块:另一个文件用
import file导入它。
Python 不要求你声明自己写的是哪一种。任何文件都可能两种用法都有。
来看一个名为 math_helpers.py 的文件:
# math_helpers.py
def mean(values):
return sum(values) / len(values)
def median(values):
s = sorted(values)
mid = len(s) // 2
if len(s) % 2 == 0:
return (s[mid - 1] + s[mid]) / 2
return s[mid]
# Quick smoke test while developing
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print("mean:", mean(numbers))
print("median:", median(numbers))
如果你直接运行这个文件,它会打印测试输出,很好。但如果其他文件执行了 import math_helpers,这些 print 语句会在导入时运行。每个使用这个模块的人都会看到你开发时的临时输出。这就不好了。
__name__ 是什么
每个 Python 模块都有一个名为 __name__ 的内置变量。Python 会自动设置它:
- 当文件被导入时,
__name__被设为模块的导入名,在上面的例子中就是"math_helpers"。 - 当文件被直接运行时,Python 把
__name__设为特殊字符串"__main__"。
这样你就能判断当前处于哪种模式。把“只在脚本模式下运行”的代码放进一个判断里:
# math_helpers.py
def mean(values):
return sum(values) / len(values)
def median(values):
s = sorted(values)
mid = len(s) // 2
if len(s) % 2 == 0:
return (s[mid - 1] + s[mid]) / 2
return s[mid]
if __name__ == "__main__":
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print("mean:", mean(numbers))
print("median:", median(numbers))
现在 python3 math_helpers.py 会打印测试输出。但在其他文件中 import math_helpers 会完全跳过这个代码块:函数照样可用,不想要的东西不会运行。
常见搭档:main() 函数
大多数实际的脚本都会有一个专门的 main() 函数,并让守卫代码块尽量简短:
# process_orders.py
import sys
def load_orders(path):
with open(path) as f:
return [line.strip() for line in f if line.strip()]
def process(orders):
for order in orders:
print(f"Processing {order}")
def main():
if len(sys.argv) < 2:
print("Usage: python3 process_orders.py <file>")
sys.exit(1)
orders = load_orders(sys.argv[1])
process(orders)
if __name__ == "__main__":
main()
为什么要专门写一个 main(),而不是把逻辑直接放在 if 守卫下面?
- 便于测试。你可以导入这个模块,在测试中调用
main()(或任何辅助函数)。 - 局部变量。在
main()中定义的东西都是局部变量,而不是模块级的名字。这能避免意外的命名冲突,也让模块对外暴露的内容更干净。 - 可读性。守卫代码块现在只有两行,是一个清晰的“脚本入口”,而不是一堆逻辑。
简单演示
你可以直接看到它的行为:
用 Python 运行上面的代码片段时,它会打印 __name__ is: __main__ 以及包含“being run as a script”的那一行。如果其他文件导入了这个文件,同样的判断会打印另一个分支。
什么时候不需要守卫
很多 Python 文件从来不需要它。没人会导入的简短脚本不写也完全没问题:
# one_off_rename.py
import os
for name in os.listdir("."):
if name.endswith(".txt"):
os.rename(name, name.lower())
这样写完全没问题。你并不是在写一个库,也没有人会导入它。但只要文件里出现了可复用的逻辑,而你又想把带副作用的代码和它放在一起,就该用上守卫了。
已安装包的入口点
当你把代码发布成真正的包时,还有一种更正式的机制:包元数据中的 entry_points,它把一个命令映射到一个函数。安装你的包的用户会得到一个直接调用该函数的 shell 命令。不过,对于在你自己电脑上某个文件夹里运行的脚本,__main__ 守卫就是标准做法。
本章小结
你现在已经掌握了:
- 用函数给逻辑命名。
- 用
*args/**kwargs实现灵活的调用。 - 用 lambda 写一次性函数。
- 用装饰器和类型提示来包装和说明这些函数。
- 用模块、pip 和虚拟环境把代码拆分到多个文件和项目中。
- 用
__main__守卫让脚本和模块共存。
下一步:类
到目前为止,你主要是把函数和普通数据(字典、列表、元组)搭配使用。下一章介绍类,这是 Python 把数据和行为绑定在一起的方式,self、__init__、继承和 dataclass 这些概念都在那里。
常见问题
if __name__ == '__main__' 有什么作用?
只有当文件被直接执行时(而不是作为模块被导入时),它下面的代码块才会运行。这样你写的 .py 文件既可以当作脚本运行,也可以被导入来使用其中的函数和类,而导入时不会执行脚本里的操作。
Python 需要 main 函数吗?
不需要。Python 文件会自动从上到下执行。但只要文件里有你希望也能被导入的逻辑,if __name__ == '__main__' 守卫就变得很重要。把脚本的顶层工作放进一个 main() 函数里,是与之搭配的常见习惯。
__name__ 和 __main__ 有什么区别?
__name__ 是 Python 在每个模块中设置的特殊变量。如果文件是被导入的,__name__ 就是模块名(比如 'util')。如果文件是被直接运行的,Python 会把 __name__ 设为字符串 '__main__'。if 判断就是在比较这两者。