如果__name__ ==“__main__”:该怎么办?简答题长答案

这有什么作用,为什么应该包括该语句?if

if __name__ == "__main__":
    print("Hello, World!")

答案 1

简答题

它是样板代码,可防止用户在无意中意外调用脚本。以下是从脚本中省略防护时的一些常见问题:

  • 如果将无防护脚本导入另一个脚本(例如 ),则后一个脚本将触发前者在导入时使用第二个脚本的命令行参数运行。这几乎总是一个错误。import my_script_without_a_name_eq_main_guard

  • 如果在无防护脚本中有一个自定义类并将其保存到 pickle 文件中,则在另一个脚本中取消选择它将触发无防护脚本的导入,并出现与上一个项目符号中概述的相同问题。

长答案

为了更好地理解为什么以及这很重要,我们需要退后一步,了解Python如何初始化脚本,以及它如何与其模块导入机制进行交互。

每当Python解释器读取源文件时,它就会做两件事:

  • 它设置一些特殊的变量,如 ,然后__name__

  • 它执行文件中的所有代码。

让我们看看这是如何工作的,以及它与你关于我们总是在Python脚本中看到的检查的问题有什么关系。__name__

代码示例

让我们使用稍微不同的代码示例来探索导入和脚本的工作原理。假设以下内容位于名为 的文件中。foo.py

# Suppose this is foo.py.

print("before import")
import math

print("before function_a")
def function_a():
    print("Function A")

print("before function_b")
def function_b():
    print("Function B {}".format(math.sqrt(100)))

print("before __name__ guard")
if __name__ == '__main__':
    function_a()
    function_b()
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

解释器将搜索您的文件(以及搜索其他一些变体),并且在执行该模块之前,它将从import语句中的名称分配给变量,即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"

执行模块代码

设置特殊变量后,解释器执行模块中的所有代码,一次一个语句。您可能希望在代码示例的一侧打开另一个窗口,以便可以按照此说明进行操作。

总是

  1. 它打印字符串(不带引号)。"before import"

  2. 它加载模块并将其分配给名为 的变量。这相当于替换为以下内容(请注意,这是Python中的一个低级函数,它接受一个字符串并触发实际的导入):mathmathimport math__import__

# Find and load a module given its string name, "math",
# then assign it to a local variable called math.
math = __import__("math")
  1. 它打印字符串 。"before function_a"

  2. 它执行块,创建一个函数对象,然后将该函数对象分配给一个名为 的变量。deffunction_a

  3. 它打印字符串 。"before function_b"

  4. 它执行第二个块,创建另一个函数对象,然后将其分配给一个名为 的变量。deffunction_b

  5. 它打印字符串 。"before __name__ guard"

仅当模块是主程序时

  1. 如果您的模块是主程序,那么它将看到它确实已设置为并调用两个函数,打印字符串和.__name__"__main__""Function A""Function B 10.0"

仅当您的模块被另一个模块导入时

  1. (相反)如果您的模块不是主程序,而是由另一个程序导入的,则将是 ,而不是 ,并且它将跳过语句的正文。__name__"foo""__main__"if

总是

  1. 在这两种情况下,它都会打印字符串。"after __name__ guard"

总结

总之,以下是在两种情况下打印的内容:

# What gets printed if foo is the main program
before import
before function_a
before function_b
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 function_a
before function_b
before __name__ guard
after __name__ guard

为什么它以这种方式工作?

你可能会自然而然地想知道为什么有人会想要这个。好吧,有时你想写一个文件,它既可以被其他程序和/或模块用作模块,也可以作为主程序本身运行。例子:.py

  • 您的模块是一个库,但您希望具有一个脚本模式,在该模式下,它运行一些单元测试或演示。

  • 您的模块仅用作主程序,但它具有一些单元测试,并且测试框架通过导入脚本等文件并运行特殊测试函数来工作。您不希望它仅仅因为导入模块而尝试运行脚本。.py

  • 您的模块主要用作主程序,但它也为高级用户提供了程序员友好的API。

除了这些示例之外,在Python中运行脚本只是设置一些神奇的变量并导入脚本,这真是太好了。“运行”脚本是导入脚本模块的副作用。

发人深思的东西

  • 问:我可以有多个检查块吗?答:这样做很奇怪,但语言不会阻止你。__name__

  • 假设以下内容位于 中。如果你在命令行上说,会发生什么?为什么?foo2.pypython foo2.py

# Suppose this is foo2.py.
import os, sys; sys.path.insert(0, os.path.dirname(__file__)) # needed for some interpreters

def function_a():
    print("a1")
    from foo2 import function_b
    print("a2")
    function_b()
    print("a3")

def function_b():
    print("b")

print("t1")
if __name__ == "__main__":
    print("m1")
    function_a()
    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 function_a():
    print("a1")
    from foo3 import function_b
    print("a2")
    function_b()
    print("a3")

def function_b():
    print("b")

print("t1")
print("m1")
function_a()
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")

答案 2

当您的脚本通过将其作为命令传递给Python解释器来运行时,

python myscript.py

将执行所有处于缩进级别 0 的代码。定义的函数和类是定义的,但是它们的代码都不会运行。与其他语言不同,没有自动运行的函数 - 该函数是顶层的所有代码隐式的。main()main()

在这种情况下,顶级代码是一个块。 是一个内置变量,其计算结果为当前模块的名称。但是,如果模块直接运行(如上所示),则改为设置为字符串 。因此,您可以通过测试来测试脚本是直接运行还是由其他内容导入if__name__myscript.py__name__"__main__"

if __name__ == "__main__":
    ...

如果将脚本导入到另一个模块中,则将导入其各种函数和类定义并执行其顶级代码,但由于不满足条件,上述子句的 then-body 中的代码将无法运行。作为基本示例,请考虑以下两个脚本:if

# file one.py
def func():
    print("func() in one.py")

print("top-level in one.py")

if __name__ == "__main__":
    print("one.py is being run directly")
else:
    print("one.py is being imported into another module")
# file two.py
import one

print("top-level in two.py")
one.func()

if __name__ == "__main__":
    print("two.py is being run directly")
else:
    print("two.py is being imported into another module")

现在,如果您调用解释器作为

python one.py

输出将为

top-level in one.py
one.py is being run directly

如果您改为运行:two.py

python two.py

你得到

top-level in one.py
one.py is being imported into another module
top-level in two.py
func() in one.py
two.py is being run directly

因此,当加载模块时,它的等于而不是 。one__name__"one""__main__"