2.14 把模块当做脚本来执行 7 种方法及原理

http://image.iswbm.com/20200804124133.png

1. 用法举例

前面的文章里其实分享过不少类似的用法。比如:

1、 快速搭建一个 HTTP 服务

# python2
$ python -m SimpleHTTPServer 8888

# python3
$ python3 -m http.server 8888

2、快速构建 HTML 帮助文档

$ python -m pydoc -p 5200

3、快速进入 pdb 调试模式

$ python -m pdb demo.py

4、最优雅且正确的包安装方法

$ python3 -m pip install requests

5、快速美化 JSON 字符串

$ echo '{"name": "MING"}' | python -m json.tool

6、快速打印包的搜索路径

$ python -m site

7、用于快速计算程序执行时长

$ python3 -m timeit '"-".join(map(str, range(100)))'

2. 原理剖析

上面的诸多命令,都有一个特点,在命令中都含有 -m 参数选项,而参数的值,SimpleHTTPServer, http.server, pydoc,pdb,pip, json.tool,site ,timeit这些都是模块或者包。

通常来说模块或者包,都是用做工具包由其他模块导入使用,而很少直接使用命令来执行(脚本除外)。

Python 给我们提供了一种方法,可以让我们将模块里的部分功能抽取出来,直接用于命令行式的调用。效果就是前面你所看到的。

那这是如何实现的呢?

最好的学习方式,莫过于模仿,直接以 pip 和 json 模块为学习对象,看看目录结构和代码都有什么特点。

先看一下 pip 的源码目录,发现在其下有一个 __main__.py 的文件,难道这是 -m 的入口?

http://image.iswbm.com/20200811155234.png

再看一下 json.tool 的源码文件,json 库下面却没有 __main__.py 的文件。

这就很奇怪了。

不对,再回过头看,我们调用的不是 json 库,而是 json 库下的 tool 模块。

查看 tool 模块的源代码,有一个名为 main 的函数

http://image.iswbm.com/20200811154945.png

但它这不是关键,main 函数是在模块中直接被调用的。

只有当 __name____main___ 时,main 函数才会被调用

if __name__ == '__main__':
    main()

当模块被导入时,__name__ 的值为模块名,

而当模块被直接执行,__name__ 的值就变成了 __main__

这下思路清晰了。

想要使用 -m 的方式执行模块,有两种方式:

  • 第一种:以 -m <package> 的方式执行,只要在 package 下写一个 __main__.py 的文件即可。

  • 第二种:以 -m <package.module> 的方式执行,只要在 module 的代码中,定义一个 main 函数,然后在最外层写入下面这段固定的代码

if __name__ == '__main__':
    main()

上面我将 -m 的使用情况分为两种,但是实际上,只有一种,对于第一种,你完全可以将 -m <package> 理解为 -m <package.__main__> 的简写形式。

3. 实践一下

先把当前路径设置追加到 PATH 的环境变量中

$ export PATH=${PATH}:`pwd`

先来验证一下第一种方法。

然后在当前目录下新建一个demo 文件夹,并且在 demo 目录下新建一个 __main__.py 的文件,随便打印点东西

# __main__.py
print("hello, world")

然后直接执行如下命令,立马就能看到效果。

$ python3 -m demo
hello,world

执行过程如下:

http://image.iswbm.com/20200811184733.png

再来验证一下使用第二种方法。

在 demo 目录下再新建一个 foobar.py 文件

# foobar.py
def main():
    print("hello, world")

if __name__ == "__main__":
    main()

最后执行一下如下命令,输出与预期相符

$ python3 -m demo.foobar
hello, foobar

4. -m 存在的意义

-m 实现的效果,无异于直接执行一个 Python 模块/脚本。

那么问题就来了,那我直接执行不就行啦,何必多此一举再加个 -m 呢?

这个问题很有意思,值得一提。

当我们使用一个模块的时候,往往只需要记住模块名,然后使用 import 去导入它就行了。

之所以能这么便利,这得益于 Python 完善的导入机制,你完全不需要知道这个模块文件存在哪个目录下,它的绝对路径是什么?因为 Python 的包导入机制会帮你做这些事情。

换句话说,如果你不使用 -m 的方式,当你要使用 python -m json.tool,你就得这样子写

$ echo '{"name": "MING"}' | python /usr/lib64/python2.7/json/tool.py
{
    "name": "MING"
}

如此一对比,哪个更方便?你心里应该有数了。