简短回答
它是样板代码,可以保护用户在不打算时意外调用脚本。 以下是从脚本中省略guard时的一些常见问题:
如果您在另一个脚本中导入没有guardless脚本(例如import my_script_without_a_name_eq_main_guard
),那么第二个脚本将触发第一个脚本在导入时运行和使用第二个脚本的命令行参数。 这几乎总是一个错误。
如果您在guardless脚本中有一个自定义类并将其保存到pickle文件中,那么在另一个脚本中取消点击它将触发guardless脚本的导入,与前一个项目符号中概述的相同问题。
长答案
为了更好地理解这为什么以及如何重要,我们需要退一步了解Python如何初始化脚本以及它如何与其模块导入机制交互。
每当Python解释器读取源文件时,它都会做两件事:
让我们看看这是如何工作的,以及它如何与您关于我们在Python脚本中总是看到的__name__
检查的问题相关联。
代码示例
让我们使用一个稍微不同的代码示例来探索导入和脚本的工作方式。 假设以下内容在名为foo.py
的文件中。
# Suppose this is foo.py.
print("before import")
import math
print("before functionA")
def functionA():
print("Function A")
print("before functionB")
def functionB():
print("Function B {}".format(math.sqrt(100)))
print("before __name__ guard")
if __name__ == '__main__':
functionA()
functionB()
print("after __name__ guard")
特殊变量
Python解释器在读取源文件时,首先定义几个特殊变量。 在这种情况下,我们关心__name__
变量。
当你的模块是主程序时
如果您将模块(源文件)作为主程序运行,例如
python foo.py
解释器会将硬编码字符串"__main__"
赋值给__name__
变量,即
# It's as if the interpreter inserts this at the top
# of your module when run as the main program.
__name__ = "__main__"
当你的模块被另一个导入时
另一方面,假设其他一些模块是主程序,它导入您的模块。 这意味着在主程序中或在主程序导入的其他模块中有这样的语句:
# Suppose this is in some other main program.
import foo
解释器将搜索您的foo.py
文件(以及搜索其他一些变体),并且在执行该模块之前,它将将来自import语句的名称"foo"
分配给__name__
变量,即
# It's as if the interpreter inserts this at the top
# of your module when it's imported from another module.
__name__ = "foo"
执行模块的代码
设置特殊变量后,解释器执行模块中的所有代码,一次一条语句。 您可能希望在代码示例旁边打开另一个窗口,以便您可以遵循此解释。
始终
它打印字符串"before import"
(不带引号)。
它加载math
模块并将其分配给一个名为math
的变量。 这相当于将import math
替换为以下内容(请注意,__import__
是Python中的低级函数,它接受字符串并触发实际导入):
# Find and load a module given its string name, "math",
# then assign it to a local variable called math.
math = __import__("math")
它打印字符串"before functionA"
。
它执行def
块,创建一个函数对象,然后将该函数对象分配给一个名为functionA
的变量。
它打印字符串"before functionB"
。
它执行第二个def
块,创建另一个函数对象,然后将其分配给一个名为functionB
的变量。
它打印字符串"before __name__ guard"
。
只有当你的模块是主程序时
- 如果你的模块是主程序,那么它会看到
__name__
确实被设置为"__main__"
,它调用两个函数,打印字符串"Function A"
和"Function B 10.0"
。
只有当你的模块被另一个
导入时
如果你的模块不是主程序,而是由另一个程序导入的,那么__name__
将是"foo"
,而不是"__main__"
,它将跳过if
语句的主体。
始终
- 它将在这两种情况下打印字符串
"after __name__ guard"
。
摘要
总之,这是两种情况下打印的内容:
# What gets printed if foo is the main program
before import
before functionA
before functionB
before __name__ guard
Function A
Function B 10.0
after __name__ guard
# What gets printed if foo is imported as a regular module
before import
before functionA
before functionB
before __name__ guard
after __name__ guard
为什么这样工作?
你可能很自然地想知道为什么有人会想要这个。 那么,有时你想写一个.py
文件,既可以被其他程序和/或模块用作模块,也可以作为主程序本身运行。 例如:
您的模块是一个库,但您希望有一个脚本模式,它运行一些单元测试或演示。
您的模块仅用作主程序,但它有一些单元测试,测试框架通过像您的脚本一样导入.py
文件并运行特殊的测试函数来工作。 您不希望它尝试运行脚本,只是因为它正在导入模块。
您的模块主要用作主程序,但它也为高级用户提供了一个程序员友好的API。
除了这些例子之外,在Python中运行脚本只是设置一些魔术变量并导入脚本,这是优雅的。 "运行"脚本是导入脚本模块的副作用。
值得思考的食物
# Suppose this is foo2.py.
import os, sys; sys.path.insert(0, os.path.dirname(__file__)) # needed for some interpreters
def functionA():
print("a1")
from foo2 import functionB
print("a2")
functionB()
print("a3")
def functionB():
print("b")
print("t1")
if __name__ == "__main__":
print("m1")
functionA()
print("m2")
print("t2")
- 现在,弄清楚如果您删除
__name__
签入foo3.py
会发生什么:
# Suppose this is foo3.py.
import os, sys; sys.path.insert(0, os.path.dirname(__file__)) # needed for some interpreters
def functionA():
print("a1")
from foo3 import functionB
print("a2")
functionB()
print("a3")
def functionB():
print("b")
print("t1")
print("m1")
functionA()
print("m2")
print("t2")
# Suppose this is in foo4.py
__name__ = "__main__"
def bar():
print("bar")
print("before __name__ guard")
if __name__ == "__main__":
bar()
print("after __name__ guard")
当您的脚本通过将其作为命令传递给Python解释器来运行时,
所有处于缩进级别0的代码都会被执行。 定义的函数和类已经定义好,但它们的代码都不会运行。 与其他语言不同,没有自动运行的
main()
函数-main()
函数隐式地包含顶层的所有代码。在这种情况下,顶层代码是一个
来测试您的脚本是直接运行还是由其他东西导入if
块。__name__
是一个内置变量,它计算当前模块的名称。 但是,如果一个模块直接运行(如上面的myscript.py
),那么__name__
被设置为字符串"__main__"
。 因此,您可以通过测试如果你的脚本被导入到另一个模块中,它的各种函数和类定义将被导入,它的顶级代码将被执行,但是上面
if
子句的主体中的代码将不会运行,因为条件没有满足。 作为一个基本示例,请考虑以下两个脚本:现在,如果你调用解释器为
输出将是
如果改为运行
two.py
:你得到
因此,当模块
one
被加载时,它的__name__
等于"one"
而不是"__main__"
。