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

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

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

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

-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
  • 1
  • 2
  • 3
  • 4

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

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

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

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

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

最后,还有一种常常被人忽略的场景:“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__’。

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

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

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

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

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

那么,“-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。

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

“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 的时间紧密贴合)

这个 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/

https://blog.csdn.net/jian3x/article/details/89556592

公众号【Python猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。

文章知识点与官方知识档案匹配,可进一步学习相关知识
Python入门技能树首页概览289766 人正在系统学习中

[转贴]Python 中 -m 的典型用法、原理解析与发展演变的更多相关文章

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

    在命令行中使用 Python 时,它可以接收大约 20 个选项(option),语法格式如下: python [-bBdEhiIOqsSuvVWx?] [-c command | -m module- ...

  2. [转] C++ STL中map.erase(it++)用法原理解析

    总结一下map::erase的正确用法. 首先看一下在循环中使用vector::erase时我习惯的用法: for(vector<int>::iterator it = vecInt.be ...

  3. Python中 sys.argv[]的用法

    Python中 sys.argv[]的用法 因为是看书自学的python,开始后不久就遇到了这个引入的模块函数,且一直在IDLE上编辑了后运行,试图从结果发现它的用途,然而结果一直都是没结果,也在网上 ...

  4. 31 Python中 sys.argv[]的用法简明解释(转)

    Python中 sys.argv[]的用法简明解释 因为是看书自学的python,开始后不久就遇到了这个引入的模块函数,且一直在IDLE上编辑了后运行,试图从结果发现它的用途,然而结果一直都是没结果, ...

  5. Python中sorted()方法的用法

    Python中sorted()方法的用法 2012-12-24 22:01:14|  分类: Python |字号 订阅 1.先说一下iterable,中文意思是迭代器. Python的帮助文档中对i ...

  6. Python中int()函数的用法浅析

      int()是Python的一个内部函数 Python系统帮助里面是这么说的 >>> help(int)  Help on class int in module __builti ...

  7. Python中 sys.argv的用法简明解释

    Python中 sys.argv[]的用法简明解释 sys.argv[]说白了就是一个从程序外部获取参数的桥梁,这个“外部”很关键,所以那些试图从代码来说明它作用的解释一直没看明白.因为我们从外部取得 ...

  8. Python中sort和sorted函数代码解析

    Python中sort和sorted函数代码解析 本文研究的主要是Python中sort和sorted函数的相关内容,具体如下. 一.sort函数 sort函数是序列的内部函数 函数原型: L.sor ...

  9. python中super()的一些用法

    在看python高级编程这本书的时候,在讲到super的时候,产生了一些疑惑,super在python中的用法跟其他的语言有一些不一样的地方,在网上找了一些资料,发现基本上很少有文章能把我的疑惑讲明白 ...

  10. Python中return self的用法

      在Python中,有些开源项目中的方法返回结果为self. 对于不熟悉这种用法的读者来说,这无疑使人困扰,本文的目的就是给出这种语法的一个解释,并且给出几个例子.   在Python中,retur ...

随机推荐

  1. 文心一言 VS 讯飞星火 VS chatgpt (38)-- 算法导论5.4 2题

    二.假设我们将球投入到b个箱子里,直到某个箱子中有两个球.每一次投掷都是独立的并且每个球落入任何箱子的机会均等.请问投球次数期望是多少? 文心一言: 这是一个典型的鸽巢原理(Pigeonhole Pr ...

  2. Text Intelligence - TextIn.com AI时代下的智能文档识别、处理、转换

    一.智能文档处理介绍 在AI时代,智能文档处理技术正变得越来越重要.它包括了智能文字识别(OCR).智能文档信息抽取.文档图像处理以及文档转换等多个方面.这些技术共同构成了现代信息处理的核心,广泛应用 ...

  3. flutter BLoC框架

    BLoC(Business Logic Component)是一种在Flutter中用于管理状态和处理业务逻辑的设计模式和架构模式. BLoC 的核心思想是将应用程序的状态.业务逻辑和UI分离开来,以 ...

  4. 普通用户登录切换到root用户

    使用su命令: 在终端中输入以下命令并按Enter键: su - 输入root用户的密码,然后按Enter键. 如果密码正确,你将会切换为root用户,并且可以执行root用户的操作. 使用sudo命 ...

  5. 打通Rasa Action Server和LLM接口的尝试方法

      本文使用最简单的方法对打通 Rasa Action Server 和 LLM 接口进行了尝试,即当 Rasa 对话 intent 为 out_of_scope 时,调用 action_gpt_fa ...

  6. MyBatis batchInsert 批量插入数据

    mybatis 是一个 Java 的持久层框架,它支持定制化 SQL.存储过程以及高级映射.通过 MyBatis,开发者可以直接编写原生态 SQL,避免了 JDBC 代码的繁琐. 如何在 MyBati ...

  7. PPT 客户提案PPT应该怎么样改

    PPT 客户提案PPT应该怎么样改

  8. SSL 证书变革之时已至,这些变化你都清楚吗?

    现代社会最离不开的是"安全",无论是生命.财产.数据还是其他任何事物都需要各种手段来保证安全,互联网自然也无法免俗.HTTP 协议作为无法加密数据,让所有通信数据都在网络中明文&q ...

  9. 2024-01-17:lc的30. 串联所有单词的子串

    2024-01-17:用go语言,给定一个字符串 s 和一个字符串数组 words. words 中所有字符串 长度相同. s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接 ...

  10. Codeforce:455A. Boredom (DP)

    https://codeforces.com/problemset/problem/455/A 题意: 给出n个元素,让我们来挑选,如果选了 \(a_k\),获得\(a_k\)点数,同时与\(a_{k ...