
本文主要参考文章:
What Does if __name__ == "__main__" Do in Python? – Real Python有兴趣的可以参考原文。
学习一段时间Python后,在阅读别人的代码时,经常会看到
if __name__ == "__main__”这样的语句出现。这个语句是用来干什么的?本文中对这个进行一个探讨。
这是一个条件判断语句,如果条件满足,就进入下面的语句。简单来说,该语句用来当文件当作脚本运行时候,就执行代码;但是当文件被当做Module被import的时候,就不执行相关代码。
也就是说如果你需要把代码放在一个文件中,在
if __name__ == "__main__”里面放的代码,就只有在该文件被当作脚本文件执行的时候才会起作用。
比如下面的例子:
# print_hello.py
def printHello():
print(“hello”)
if __name__ == “__main__"
print(“in if __name__ == __main__ block”)
在上面的代码中,可以运行
>>>python print_hello.pyin if __name__ == __main__ block
如果用python来执行该文件,那么in if __name__ == “__main__” 条件就会满足,就会打印出
in if __name__ == __main__ block语句。
但是如果将print_hello.py文件当作module导入,情况如下:
>>>from print_hello import printHello>>>printHello()Hello
可以看到,在这种情况下,if __name__ == “__main__” 下面的代码是没有被激活执行的。
因此可以看到,我们可以将代码放到if __name__ == “__main__”,来适应不同的使用场景。
这段代码的工作原理
从上面的例子,可以看到if __name__ == “__main__”主要就是一个条件语句
-
当该条件语句为True的时候,该语句下的缩进语句代码,就会执行
-
当该条件语句为False的时候,该语句下的缩进语句代码,就会被Python解析器跳过
那么需要考虑的是什么时候if __name__ == “__main__”会为True,也就是__name__ 变量会等于“__main__”?__name__变量是一个Python的Module的全局变量,Python解析器会在执行过程中设置该变量。当Python解析器将代码当作顶层模块运行的时候,__name__变量就会被赋值为“__main__”。
所谓的顶层模块,就是指第一个运行的用户定义的Python的模块。这个模块由于是第一个执行的,因此在该模块运行中,将会由此模块导入imports所有其他的别的相关依赖模块。因此该模块就被当作顶层模块。
为了很好的理解这个,可以简单的用一个例子来说明。
# showname.pyprint(__name__, type(__name__))
该语句打印__name__
$ python show name.py__main__ <class ’str’>
可以看到如果当作脚本来执行就被设置成了__main__。
>>> import shownameshowname <class ’str’>
在这个例子中,python在import过程中,执行了showname。py文件全局命名空间中的代码,并且将结果打印在控制台中,也就是打印了print(__name__, type(__name__))
如果是当作module导入的时候,该名字就被设置成了showname,该模块的名字。
从上面的内容已经说明了,python总是把最顶层的模块的__name__设置成main,因此可以做如下试验:
>>> import shownameshowname <class ’str’>>>> __name__'__main__'>>> showname.__name__'showname'
可以看到和上面介绍的知识点是一致的。总结如下:
-
最顶层的__name__,将会被设置成了__main__
-
导入的模块中的__name__就被设置成了模块的名称
因此也可以用if __name__ == “__main__”来判断你的模块代码是不是被当作最顶层模块在使用。
知道了运行原理,再次回到在什么情况下需要使用if __name__ == "__main__”语句,该语句可以用来干什么?
根据这个特性,可以用来对脚本中的函数进行一些输入参数获取。比如在模块中,有一些函数需要输入一些参数,那么当该模块被当作顶层模块执行时候,那么可以通过if __name__ == "__main__”语句来获取输入,并且调用相关的函数,如果该函数被当作模块来调用的时候,函数的输入可以由其他导入的模块来进行输入给定。
什么时候需要避免使用if __name__ == "__main__”语句。
有些时候可以用来屏蔽掉执行测试的部分代码。比如下面的例子:
如果该模块被执行,可以用来执行测试用例,否则就当作一个模块导入,测试不会被执行。
# adder.pyimport unittestdef add(a: int, b: int) -> int:return a + bclass TestAdder(unittest.TestCase):def test_add_adds_two_numbers(self):self.assertEqual(add(1, 2), 3)if __name__ == "__main__":unittest.main()
但是这种方式不太建议,在比较规模项目的测试中,是可以这么做,但是如果代码量一大,这种将代码和测试混在一个文件的方式,不太方便代码管理。一般应该是将测试代码写在另外一个文件中。同时这也要求将
unittest模块在代码模块中导入,这也不是一个很好的实现。
另外一个不好的使用例子是用来包含一些模块使用信息,当用户导入该模块的时候,使用信息不会被执行,但是当当作脚本运行的时候,就可以打印出使用信息。这其实也不是一个很好的实践。如果是用来打印使用信息,可以用别的更好的方法(比如编写详细的docstrings)。
第三种情况是纯脚本情况:如果你要写的代码就是一个脚本,那么你就直接写代码在全局空间中即可,不要再放到if __name__ == “__main__”下,这样做意义不大。
最后讲一下使用if __name__ == “__main__"的最佳实践。
首先这个语句只是一个判断语句,因此该语句可以被放到文件的任何地方,也可以多次使用。但是比较好的建议是大部分情况,该语句只在文件中出现一次,并且放到文件的最后。这样可以似的代码变得比较整洁,逻辑也会清晰很多。该语句放到最后还有一个好处,就是确保所有需要使用的函数或者变量都已经在前面定义好了。
但是在某些情况,也可以多次的使用if __name__ == "__main__”语句。
比如分情况导入其他模块,见如下例子:
# echo.pyif __name__ == "__main__":import sysdef echo(text: str, repetitions: int = 3) -> str:"""Imitate a real-world echo."""echoed_text = ""for i in range(repetitions, 0, -1):echoed_text += f"{text[-i:]}\n"return f"{echoed_text.lower()}."if __name__ == "__main__":text = " ".join(sys.argv[1:])print(echo(text))
在该例子中,可以利用该条件语句,是的不必要的导入一些模块。过多的解释就不在赘述。
另外的一个好的代码风格就是if __name__ == "__main__”下面的代码应该尽可能的少,如果需要放比较多的代码,
那么就应该另外定义个main函数来包含这些代码。
比如如下代码:
# echo.pyimport sysdef echo(text: str, repetitions: int = 3) -> str:"""Imitate a real-world echo."""echoed_text = ""for i in range(repetitions, 0, -1):echoed_text += f"{text[-i:]}\n"return f"{echoed_text.lower()}."def main() -> None:text = " ".join(sys.argv[1:])print(echo(text))if __name__ == "__main__":main()
后记
很多讨论将python的if __name__ == “__main__"和其他语言的入口函数main()做比较,然后得出结论说python的语法太不简洁。其实事情并不是这样的,if __name__ == "__main__"只是一个判断语句,不是程序的入口点。
python在演化过程中,对该用法的讨论也进行过很多次,但是一直也没有对这个用法有太多改变,主要原因有如下几点:
-
这个语句已经很短了:很多其他的建议节约不了几行代码
-
使用场景不多:只建议在需要将一个脚本文件运行同时也能够当作模块导入的时候,需要用到,其他情况不建议经常使用。
-
暴露了复杂性:让python使用人员,能够了解python的背后的变量和函数,这样可以更好的理解python的实现。
-
可以向后兼容。