Python 中 -m 的典型用法、原理解析与发展演变


Posted in Python onNovember 11, 2019

在命令行中使用 Python 时,它可以接收大约 20 个选项(option),语法格式如下:

python [-bBdEhiIOqsSuvVWx?] [-c command | -m module-name | script | - ] [args]

本文想要聊聊比较特殊的“-m”选项: 关于它的典型用法、原理解析与发展演变的过程。

首先,让我们用“--help”来看看它的解释:

Python 中 -m 的典型用法、原理解析与发展演变

-m mod run library module as a script (terminates option list)

"mod"是“module”的缩写,即“-m”选项后面的内容是 module(模块),其作用是把模块当成脚本来运行。

“terminates option list”意味着“-m”之后的其它选项不起作用,在这点上它跟“-c”是一样的,都是“终极选项”。官方把它们定义为“接口选项”(Interface options),需要区别于其它的普通选项或通用选项。

-m 选项的五个典型用法

Python 中有很多使用 -m 选项的场景,相信大家可能会用到或者看见过,我在这里想分享 5 个。

在 Python3 中,只需一行命令就能实现一个简单的 HTTP 服务:

python -m http.server 8000

# 注:在 Python2 中是这样
python -m SimpleHTTPServer 8000

Python 中 -m 的典型用法、原理解析与发展演变

执行后,在本机打开“ http://localhost:8000  ”,或者在局域网内的其它机器上打开“  http://本机ip:8000   ”,就能访问到执行目录下的内容,例如下图就是我本机的内容:

Python 中 -m 的典型用法、原理解析与发展演变

与此类似,我们只需要一行命令“python -m pydoc -p xxx”,就能生成 HTML 格式的官方帮助文档,可以在浏览器中访问。

Python 中 -m 的典型用法、原理解析与发展演变

上面的命令执行了 pydoc 模块,会在 9000 端口启动一个 http 服务,在浏览器中打开,我的结果如下:

Python 中 -m 的典型用法、原理解析与发展演变

它的第三个常见用法是执行 pdb 的调试命令“python -m pdb xxx.py”,以调试模式来执行“xxx.py”脚本:

Python 中 -m 的典型用法、原理解析与发展演变

第四个同样挺有用的场景是用 timeit 在命令行中测试一小段代码的运行时间。以下的 3 段代码,用不同的方式拼接 “0-1-2-……-99” 数字串。可以直观地看出它们的效率差异:

Python 中 -m 的典型用法、原理解析与发展演变

最后,还有一种常常被人忽略的场景:“python -m pip install xxx”。我们可能会习惯性地使用“pip install xxx”,或者做了版本区分时用“pip3 install xxx”,总之不在前面用“python -m”做指定。但这种写法可能会出问题。

很巧合的是,在本月初(2019.11.01),Python 的核心开发者、第一届指导委员会 五人成员之一的 Brett Cannon 专门写了一篇博客《 Why you should use "python -m pip" 》,提出应该使用“python -m pip”的方式,并做了详细的解释。

他的主要观点是:在存在多个 Python 版本的环境中,这种写法可以精确地控制三方库的安装位置。例如用“python3.8 -m pip”,可以明确指定给 3.8 版本安装,而不会混淆成其它的版本。

(延伸阅读:关于 Brett 的文章,这有一篇简短的归纳《 原来我一直安装 Python 库的姿势都不对呀! 》)

-m 选项的两种原理解析

看了前面的几种典型用法,你是否开始好奇: “-m”是怎么运作的?它是怎么实现的?

对于“python -m name”,一句话解释: Python 会检索 sys.path ,查找名字为“name”的模块或者包(含命名空间包),并将其内容当成“__main__”模块来执行。

1、对于普通模块

以“.py”为后缀的文件就是一个模块,在“-m”之后使用时,只需要使用模块名,不需要写出后缀,但前提是该模块名是有效的,且不能是用 C 语言写成的模块。

在“-m”之后,如果是一个无效的模块名,则会报错“No module named xxx”。

如果是一个带后缀的模块,则首先会导入该模块,然后可能报错:Error while finding module specification for 'xxx.py' (AttributeError: module 'xxx' has no attribute '__path__'。

Python 中 -m 的典型用法、原理解析与发展演变

对于一个普通模块,有时候这两种写法表面看起来是等效的:

Python 中 -m 的典型用法、原理解析与发展演变

两种写法都会把定位到的模块脚本当成主程序入口来执行,即在执行时,该脚本的 __name__ 都是”__main__“,跟 import 导入方式是不同的。

但它的前提是:在执行目录中存在着“test.py”,且只有唯一的“test”模块。对于本例,如果换一个目录执行的话,“python test.py”当然会报找不到文件的错误,然而,“python -m test”却不会报错,因为解释器在遍历 sys.path 时可以找到同名的“test”模块,并且执行:

Python 中 -m 的典型用法、原理解析与发展演变

由此差异,我们其实可以总结出“-m”的用法: 已知一个模块的名字,但不知道它的文件路径,那么使用“-m”就意味着交给解释器自行查找,若找到,则当成脚本执行。

以前文的“python -m http.server 8000”为例,我们也可以找到“server”模块的绝对路径,然后执行,尽管这样会变得很麻烦。

Python 中 -m 的典型用法、原理解析与发展演变

那么,“-m”方式与直接运行脚本相比,在实现上有什么不同呢?

直接运行脚本时,相当于给出了脚本的完整路径(不管是绝对路径还是相对路径),解释器根据 文件系统的查找机制, 定位到该脚本,然后执行 使用“-m”方式时,解释器需要在不 import 的情况下,在 所有模块命名空间 中查找,定位到脚本的路径,然后执行。为了实现这个过程,解释器会借助两个模块: pkgutilrunpy ,前者用来获取所有的模块列表,后者根据模块名来定位并执行脚本 2、对于包内模块

如果“-m”之后要执行的是一个包,那么解释器经过前面提到的查找过程,先定位到该包,然后会去执行它的“__main__”子模块,也就是说,在包目录下需要实现一个“__main__.py”文件。

换句话说,假设有个包的名称是“pname”,那么, “python -m pname”,其实就等效于“python -m pname.__main__”。

仍以前文创建 HTTP 服务为例,“http”是 Python 内置的一个包,它没有“__main__.py”文件,所以使用“-m”方式执行时,就会报错:No module named http.__main__; 'http' is a package and cannot be directly executed。

Python 中 -m 的典型用法、原理解析与发展演变

作为对比,我们可以看看前文提到的 pip,它也是一个包,为什么“python -m pip”的方式可以使用呢?当然是因为它有“__main__.py”文件:

Python 中 -m 的典型用法、原理解析与发展演变

“python -m pip”实际上执行的就是这个“__main__.py”文件,它主要作为一个调用入口,调用了核心的"pip._internal.main"。

http 包因为没有一个统一的入口模块,所以采用了“python -m 包.模块”的方式,而 pip 包因为有统一的入口模块,所以加了一个“__main__.py”文件,最后只需要写“python -m 包”,简明直观。

-m 选项的十年演变过程

最早引入 -m 选项的是 Python 2.4 版本(2004年),当时功能还挺受限,只能作用于普通的内置模块(如 pdb 和 profile)。

随后,知名开发者 Nick Coghlan 提出的《PEP 338 -- Executing modules as scripts 》把它的功能提升了一个台阶。这个 PEP 在 2004 年提出,最终实现在 2006 年的 2.5 版本。

(插个题外话:Nick Coghlan 是核心开发者中的核心之一,也是第一届指导委员会的五人成员之一。记得当初看材料,他是在 2005 年被选为核心开发者的,这时间与 PEP-338 的时间紧密贴合)

Python 中 -m 的典型用法、原理解析与发展演变

这个 PEP 的几个核心点是:

  • 结合了 PEP-302 的新探针机制(new import hooks),提升了解释器查找包内模块的能力
  • 结合了其它的导入机制(例如 zipimport 和冻结模块(frozen modules)),拓展了解释器查找模块的范围与精度
  • 开发了新的 runpy.run_module(modulename) 来实现本功能,而不用修改 CPython 解释器,如此可方便移植到其它解释器

至此,-m 选项使得 Python 可以在所有的命名空间内定位到命令行中给定的模块。

2009 年,在 Python 3.1 版本中,只需给定包的名称,就能定位和运行它的“__main__”子模块。2014 年,-m 扩展到支持命名空间包。

至此,经过十年的发展演变,-m 选项变得功能齐全,羽翼丰满。

最后,我们来个 ending 吧:-m 选项可能看似不起眼,但它绝对是最特别的选项之一,它使得在命令行中,使用内置模块、标准包与三方库时变得更轻松便利。有机会就多用一下吧,体会它带来的愉悦体验。

参考材料

https://docs.python.org/3.7/using/cmdline.html#cmdoption-m

https://snarky.ca/why-you-should-use-python-m-pip

https://www.python.org/dev/peps/pep-0338/

总结

以上所述是小编给大家介绍的Python 中 -m 的典型用法、原理解析与发展演变,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Python 相关文章推荐
Python3中的2to3转换工具使用示例
Jun 12 Python
100行python代码实现跳一跳辅助程序
Jan 15 Python
详解python3中zipfile模块用法
Jun 18 Python
python生成1行四列全2矩阵的方法
Aug 04 Python
关于Python 的简单栅格图像边界提取方法
Jul 05 Python
python爬虫解决验证码的思路及示例
Aug 01 Python
python3的url编码和解码,自定义gbk、utf-8的例子
Aug 22 Python
Pandas DataFrame中的tuple元素遍历的实现
Oct 23 Python
python GUI框架pyqt5 对图片进行流式布局的方法(瀑布流flowlayout)
Mar 12 Python
pycharm 实现本地写代码,服务器运行的操作
Jun 08 Python
python selenium 获取接口数据的实现
Dec 07 Python
python通过cython加密代码
Dec 11 Python
原来我一直安装 Python 库的姿势都不对呀
Nov 11 #Python
Python帮你识破双11的套路
Nov 11 #Python
如何基于Python批量下载音乐
Nov 11 #Python
Python实现报警信息实时发送至邮箱功能(实例代码)
Nov 11 #Python
详解Anconda环境下载python包的教程(图形界面+命令行+pycharm安装)
Nov 11 #Python
Python序列化与反序列化pickle用法实例
Nov 11 #Python
详解Python可视化神器Yellowbrick使用
Nov 11 #Python
You might like
深入file_get_contents与curl函数的详解
2013/06/25 PHP
PHP示例演示发送邮件给某个邮箱
2019/04/03 PHP
PHP7.0连接DB操作实例分析【基于mysqli】
2019/09/26 PHP
php实现将数组或对象写入到文件的方法小结【三种方法】
2020/04/22 PHP
一个高效的JavaScript压缩工具下载集合
2007/03/06 Javascript
IE6,IE7,IE8下使用Javascript记录光标选中范围(已补全)
2011/08/28 Javascript
javascript的push使用指南
2014/12/05 Javascript
js查找节点的方法小结
2015/01/13 Javascript
Javascript节点关系实例分析
2015/05/15 Javascript
实例讲解JavaScript中instanceof运算符的用法
2016/06/08 Javascript
anime.js 实现带有描边动画效果的复选框(推荐)
2017/12/24 Javascript
利用Node.js批量抓取高清妹子图片实例教程
2018/08/02 Javascript
JS调用安卓手机摄像头扫描二维码
2018/10/16 Javascript
vue项目中实现图片预览的公用组件功能
2018/10/26 Javascript
微信 jssdk 签名错误invalid signature的解决方法
2019/01/14 Javascript
如何在Angular应用中创建包含组件方法示例
2019/03/23 Javascript
Vue基于vuex、axios拦截器实现loading效果及axios的安装配置
2019/04/26 Javascript
JQuery样式与属性设置方法分析
2019/12/07 jQuery
Vue 请求传公共参数的操作
2020/07/31 Javascript
[40:01]OG vs Winstrike 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
python封装对象实现时间效果
2020/04/23 Python
python 通过SSHTunnelForwarder隧道连接redis的方法
2019/02/19 Python
pycharm修改文件的默认打开方式的步骤
2019/07/29 Python
基于python二叉树的构造和打印例子
2019/08/09 Python
python 使用OpenCV进行简单的人像分割与合成
2021/02/02 Python
美国领先的户外服装与装备用品店:Moosejaw
2016/08/25 全球购物
惠普加拿大在线商店:HP加拿大
2017/09/15 全球购物
社区学雷锋活动策划方案
2014/01/30 职场文书
社团文化节策划书
2014/02/01 职场文书
承诺书范文
2014/06/03 职场文书
2014年涉外离婚协议书范本
2014/11/20 职场文书
2015年酒店工作总结
2015/04/28 职场文书
闪闪的红星观后感
2015/06/08 职场文书
pytest配置文件pytest.ini的详细使用
2021/04/17 Python
Linux安装apache服务器的配置过程
2021/11/27 Servers
Redis入门基础常用操作命令整理
2022/06/01 Redis