Python main函数

前记

今年4月份入学了东洋大学情报连携学部,学校的教材也比较新,目前大一入学之后直接开始学习Python,比起国内许多大学信息相关的学科还在学习连C99规范都不支持的VC6环境下面编写C程序,我觉得我们学校的东西简直太有干货了……

我学习和编写C/C++语言已经有大概七八年时间,也有一些拿得出手的作品,算是比较熟悉了。就我个人的理解而言,总的来说,C语言是直接编译型语言,可以直接编译成机器码,让CPU直接运行,方便写一些底层代码,并且因为没有转换过程,有其他语言无法媲美的运行效率,因此目前几乎所有平台都可以使用C来编写程序,许多单片机系统比如arduino也是以C作为开发语言。

然而,C作为一个非常成功的语言,也有他无法避免的缺点。由于C是一门直接编译语言,所以不可避免,内存管理要由程序员自行编写,稍有不慎就会导致内存级别的报错,如果代码写的再不堪一些,一个严重错误有可能把系统搞的崩溃。而这一点在脚本语言上可以基本完全避免,脚本语言的代码运行在解释器或虚拟机之上,一般情况不至于出现比本程序崩溃更加严重的错误。另一点,脚本可以编写后立刻运行,而不需要编译,这点C语言也很难做到。另外就是开发用时问题,脚本这种即开即用实在是不能更爽,让我这个多年写C代码的老程序员也改变了自己原来的习惯。

在目前这个CPU、GPU各种运算器性能爆炸的年代,其实代码的运行效率一般不构成瓶颈了,如果出问题一般都可以堆硬件解决,达到瓶颈的时候想必其实你的项目应该也足够大了,应该也不缺堆硬件那点经费哈哈哈。所以这个年代,脚本语言一跃而起,Python就是近几年最亮的一颗星。

学校的教材为了初学者而设计,并没有教怎么去写main函数。不像C语言里main作为程序入口,python可以直接顺序向下执行每行代码,所以很多同学甚至不知道python需要写main函数。

本文就是大概介绍一下python里的main函数的作用以及写法。

作用

本人学习和使用过的语言中:C/C++、C#语言、Java语言等语言都有main函数,main函数是程序执行的起点。Python中,其实也有类似的运行机制,但方式却截然不同:Python使用缩进来对代码组织并执行,所有没有缩进的代码(非函数定义、类定义),都会在载入时自动执行,这些代码,都可以认为是Python的main函数内的代码。

例如:

1
print('hello world!')

这句代码等价于

1
2
3
4
def main():
    print('hello world!')

main()

这样看来main函数是不是没有多大的作用呢?

每个文件(模块)都可以任意写一些没有缩进的代码,并且在载入时自动执行,为了区分主执行文件还是被调用的文件,Python引入了一个变量**name,当文件是被调用时,name的值为模块名,当文件被执行时,*name*main。这个特性,我们可以在每个模块中写上测试代码,这些测试代码仅当模块被Python直接执行时才会运行,代码和测试完美的结合在一起。例如:

1
2
3
4
5
def main():
    print('hello world!')

if __name__ == '__main__':
    main()

这样做和上面的区别在哪里呢?

假如我们新建一个myclass.py,内容如下

1
2
3
4
5
6
7
def main():
    print('main:hello world!')

if __name__ == '__main__':
    main()

print('hello world!')

直接运行代码,我们发现,输出结果为

1
2
main:hello world!
hello world!

然而,我们保存刚才的代码,在同目录新建一个test.py,内容如下

1
import myclass

会发现输出如下

1
hello world!

我们很容易发现,被定义在main函数中的print语句并没有执行,这其实就是main函数最重要的作用之一,防止代码被作为模块使用时,一句import直接影响了调用模块的程序。

完善

其实main函数的作用不止于此,它还可以用来接收执行的参数。保存下面的文件为test.py

1
2
3
4
5
6
7
8
9
import sys
def main(argv=sys.argv):
    if argv == None:
        print('hello world!')
    else:
        print(argv)

if __name__ == '__main__':
    main()

假如我们在终端或命令提示符运行

1
>> python test.py -test -my

会发现程序可以输出

1
['test.py', '-test', '-my']

这样,我们的python程序可以接收参数,灵活性就大大提升了。

然而这样做还是有些小缺陷,这个程序在python的idle里,无法正常main函数传参数。

我们修改尝试main函数,使其接受一个可选参数 argv,这样它就支持在交互式shell中调用该函数:

1
2
3
def main(argv=None):
    if argv is None:
        argv = sys.argv

这样,灵活性就进一步提升了,因为在调用函数时,sys.argv 的值可能会发生变化;可选参数的默认值都是在定义main函数时,就已经计算好的

但是现在sys.exit()函数调用会产生问题:当main函数调用sys.exit()时,交互式解释器就会崩溃退出!

解决办法是让main()函数的返回值指示退出状态(exit status)。

因此,最后面的那行代码就变成了这样:

1
2
if __name__ == "__main__":
    sys.exit(main())

并且,main函数中的sys.exit(n)调用需要全部变成return n。

另外,为了更加完善,可以定义Usage()异常。

Python之父Guido van Rossum提出了他自己在使用的模板,我将其改成了python3样式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import sys
import getopt

class Usage(Exception):
    def __init__(self, msg):
        self.msg = msg

def main(argv=None):
    if argv is None:
        argv = sys.argv
    try:
        try:
            opts, args = getopt.getopt(argv[1:], "h", ["help"])
        except getopt.error as msg:
            raise Usage(msg)
        # more code, unchanged
    except Usage as err:
        print(sys.stderr, err.msg)
        print(sys.stderr, "for help use --help")
        return 2

if __name__ == "__main__":
    sys.exit(main())

这可能就是目前考虑最周到的main函数了吧。

参考

https://www.jianshu.com/p/a632f1fc2b73

https://www.artima.com/weblogs/viewpost.jsp?thread=4829

http://codingpy.com/article/guido-shows-how-to-write-main-function/

Licensed under CC BY-NC-SA 4.0