简答
它是样板代码,可防止用户在无意中意外调用脚本。以下是脚本中省略守卫时的一些常见问题:
长答案
为了更好地理解为什么以及如何重要,我们需要退后一步来了解 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 文件(同时搜索一些其他变体),并在执行该模块之前,将导入语句中的名称 "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"。
仅当您的模块被其他人导入时
- (instead) 如果你的模块不是主程序而是被另一个模块导入了,那么
__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")