Python if __name__ == “__main__“: 语句是做什么的

问:Python if __name__ == "__main__": 语句是做什么的,为什么应该包括 if 语句?

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

简短答案

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

● 如果你在另一个脚本中导入无防护脚本(例如:import my_script_without_a_name_eq_main_guard),则后一个脚本将触发前者在导入时使用后者的命令行参数运行。这几乎总是一个错误。
● 如果在无防护脚本中有一个自定义类,并将其保存到 pickle 文件中,那么在另一个脚本中解 pickle 它将触发对无防护脚本的导入,这与前面概述的问题相同。
 

长答案

为了更好地理解这一点的重要性,我们需要回过头来了解 Python 如何初始化脚本,以及它如何与模块导入机制交互。

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

● 它设置了一些特殊的变量,比如 __name__,然后
● 它执行在文件中找到的所有代码。

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

代码示例

让我们使用一个稍微不同的代码示例来研究导入和脚本是如何工作的。假设以下内容位于一个名为 foo.py 的文件中。

# 假设这是 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__ 变量,即

# 这就好像当作为主程序运行时,解释器将它插入到模块的顶部一样。
__name__ = "__main__" 

当你的模块被另一个模块导入时

另一方面,假设主程序是其他模块,它导入了你的模块。这意味着在主程序中,或者在主程序导入的其他模块中,都存在这样的语句:

# 假设这是在其他主程序中。
import foo

解释器将搜索 foo.py 文件(以及其他一些变量),在执行该模块之前,它将 import 语句中的名称 "foo" 赋值给 __name__ 变量,即:

# 这就好像从另一个模块导入它时,解释器会将它插入到模块的顶部。
__name__ = "foo"

执行模块的代码

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

通常

1. 它打印字符串"before import"(不带引号)。
2. 它加载 math 模块,并将其赋值给一个名为 math 的变量。这等价于用下面的代码替换 import math(注意 __import__ 是 Python 中的一个底层函数,它接受一个字符串并触发实际的导入):

# 找到并加载一个名为"math"的字符串模块,
# 然后将其赋值给一个名为math的局部变量。
math = __import__("math")

3. 它打印字符串"before function_a"。
4. 它执行 def 块,创建一个函数对象,然后将该函数对象赋值给一个名为 function_a 的变量。
5. 它打印字符串"before function_b"。
6. 它执行第二个 def 块,创建另一个函数对象,然后将其赋值给变量 function_b
7. 它打印字符串"before __name__ guard"。

只有当你的模块是主程序时
8. 如果你的模块是主程序,那么它会看到 __name__ 确实被设置为"__main__",然后它会调用这两个函数,打印字符串"Function A"和"Function B 10.0"。

只有当你的模块被另一个模块导入时
8. (相反)如果你的模块不是主程序,而是被另一个主程序导入的,那么 __name__ 将是 "foo",而不是 "__main__",并且它将跳过 if 语句的主体。

9. 在这两种情况下,它都会打印字符串"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.py 中包含如下代码。如果在命令行中输入 python foo2.py 会发生什么? 为什么?

# 假设这是 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")

● 现在,想想如果删除 foo3.py 中的 __name__ 检查,会发生什么:

# 假设这是 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")

● 当作为脚本使用时,它会做什么? 当作为模块导入时呢?

# 假设这是 in foo4.py
__name__ = "__main__"

def bar():
    print("bar")
    
print("before __name__ guard")
if __name__ == "__main__":
    bar()
print("after __name__ guard")

参考:stackoverflow question 419163

 PS:

本文首发于公众号: 程序熵, 更多相关文章请添加关注 code-shang

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值