在命令行使用Python时,可以接收到20个左右的选项(option),语法格式如下:python[-bBdEhiIOqsSuvVWx?][-ccommand|-m模块名称|脚本|-][args]本文想说说特殊的“-m”选项:讲述它的典型用法、原理分析和开发过程。首先我们用“--help”看看它的解释:-mmodrunlibrarymoduleasascript(终止选项列表)“mod”是“module”的缩写,是“-m”选项后面的内容是一个模块(module),它的作用是将模块作为脚本运行。“终止选项列表”表示“-m”之后的其他选项无效。在这一点上,它和“-c”一样,都是“最终选项”。它们被官方定义为“接口选项”,需要与其他普通或通用选项区分开来。-m选项的五种典型用法Python中使用-m选项的场景有很多。我相信你可能已经使用或看到它。我想在这里分享其中的5个。在Python3中,只需要一行命令就可以实现一个简单的HTTP服务:python-mhttp.server8000#注意:Python2中就是这样。执行python-mSimpleHTTPServer8000后,打开“http://localhost:8000”,或者在局域网内其他机器上打开“http://localip:8000”,即可访问执行目录下的内容,对于例如,下图是我本地机器的内容:类似这样,我们只需要一行命令“python-mpydoc-pxxx”就可以生成一个HTML格式的官方帮助文档,可以在a中访问浏览器。上面的命令执行了pydoc模块,会在9000端口启动一个http服务,并在浏览器中打开。我的结果如下:它的第三种常见用法是执行pdb调试命令"python-mpdbxxx.py",以调试模式执行"xxx.py"脚本:第四种同样有用的场景是使用timeit在命令行上测试一小段代码的运行时间。以下3段代码将“0-1-2-……-99”数字串以不同方式拼接。效率上的差异可以直观看出:最后还有一个经常被忽略的场景:“python-mpipinstallxxx”。我们可能习惯使用“pipinstallxxx”,或者在区分版本时使用“pip3installxxx”,总之不要在前面加上“python-m”。但是这种写法可能会有问题。巧合的是,本月初(2019.11.01),Python的核心开发者、第一届指导委员会五名成员之一的BrettCannon写了一篇博客《Why you should use "python -m pip" 》,提出“python-mpip”和详细解释。他的要点是:在存在多个Python版本的环境下,这种写法可以精确控制三方库的安装位置。比如“python3.8-mpip”,可以明确指定安装3.8版本,不会和其他版本混淆。(延伸阅读:关于Brett的文章,这里做一个简短的总结《原来我一直安装 Python 库的姿势都不对呀!》)-m选项的两个原理分析看完前面的典型用法,你是不是开始疑惑:“-m”是如何工作的?它是如何实现的?对于“python-mname”,一句话解释:Python会搜索sys.path,寻找名为“name”的模块或包(包括命名空间包),并将其内容作为“__main__”模块执行。1、对于普通模块来说,后缀为“.py”的文件就是一个模块。在“-m”之后使用时,只需要使用模块名,不需要写后缀,但前提是模块名是有效的,并且不能是用C语言写的模块。“-m”后如果是无效的模块名,会报错“Nomodulenamedxxx”。如果是带后缀的模块,会先导入该模块,然后可能会报错:Errorwhilefindingmodulespecificationfor'xxx.py'(AttributeError:module'xxx'hasnoattribute'__path__'。对于一个普通的模块,有的时候,这两种写法表面上看起来是等价的:两种写法都会将定位到的模块脚本作为主程序入口执行,即执行时,脚本的__name__为"__main__”,这与import导入方法不同。但它的前提是:执行目录下有一个“test.py”,而且只有唯一的“test”模块。对于这个例子,如果你在另一个目录下执行,“pythontest.py”当然会报找不到文件的错误。但是“python-mtest”不会报错,因为解释器可以遍历sys.path找到同名的“test”模块并执行:从这个区别我们其实可以总结出“-m”的用法》:如果你知道一个模块的名字,但不知道它的文件路径,那么使用“-m”的意思是然后让解释器自己去找,如果找到了,就会作为脚本执行。以上面的“python-mhttp.server8000”为例,我们也可以找到“server”模块的绝对路径并执行,虽然这样会变得很麻烦。那么,“-m”方式和直接运行脚本在实现上有什么区别呢?直接运行脚本时,相当于给出了脚本的完整路径(无论是绝对路径还是相对路径),解释器根据文件系统的搜索机制定位到脚本,然后执行“-m”模式,解释器需要在所有没有导入的模块命名空间中搜索,找到脚本的路径,然后执行。为了实现这个过程,解释器会用到两个模块:pkgutil和runpy,前者用来获取所有模块的列表,后者根据模块名定位并执行脚本2,对于包中的模块,如果在"-m"之后执行的是一个包,解释器首先通过上面提到的搜索过程定位到这个包,然后执行它的"__main__"子模块,也就是说,一个"__main__.py"需要在包目录文件中实现。也就是说,假设有一个名为“pname”的包,那么“python-mpname”实际上就等同于“python-mpname.__main__”。还是以前面创建HTTP服务为例,“http”是Python中内置的一个包。它没有“__main__.py”文件,所以用“-m”方式执行时会报错:Nomodulenamedhttp.__main__;'http'是一个包,不能直接执行。作为对比,我们可以看看上面说的pip,它也是一个包。为什么可以使用“python-mpip”方式呢?当然是因为它有一个“__main__.py”文件:“python-mpip”实际执行的是这个“__main__.py”文件,主要作为调用入口,调用核心“pip._internal.main”.因为http包没有统一的入口模块,所以采用了“python-mpackage.module”的方式,而pip包有统一的入口模块,所以加了一个“__main__.py”文件,最后只需要写“python-mpackage”,简洁直观。-m选项的十年演变最早是在Python2.4版本(2004年)中引入的。当时功能相当有限,只能用于常用的内置模块(如pdb、profile)。随后,知名开发者NickCoghlan提出的《PEP 338 -- Executing modules as scripts》将其功能提升到了一个更高的层次。这个PEP于2004年提出,最终在2006年的2.5版本中实现。(插一句题外话:NickCoghlan是核心开发者之一,也是第一届指导委员会的五名成员之一。我记得看过资料,他被选为acoredeveloperin2005,这个时间与PEP-338的时间非常吻合)这个PEP的几个核心点是:结合PEP-302新的探测机制(newimporthooks),提高了解释器的能力在包中查找模块结合其他导入机制(如zipimport和frozenmodules)扩展了解释器查找模块的范围和精度。开发了一个新的runpy.run_module(modulename)来实现这个功能,无需修改CPython解释器,可以很容易地移植与其他解释器一样,-m选项使Python在所有命名空间中定位命令行给出的模块。2009年,在Python3.1版本中,给定一个包的名称,就可以找到并运行它的“__main__”子模块。2014年,-m得到扩展以支持命名空间包。至此,经过十年的发展和演变,-m选项功能完备,功能完善。最后,给个结尾吧:-m选项看似不起眼,但绝对是最特别的选项之一,它让你更容易、更方便地使用内置模块、标准包和第三方库。命令行。有机会就多使用它,体会它带来的愉悦体验。参考资料https://docs.python.org/3.7/using/cmdline.html#cmdoption-mhttps://snarky.ca/why-you-should-use-python-m-piphttps://www.python。org/dev/peps/pep-0338/https://blog.csdn.net/jian3x/article/details/89556592公众号【蟒猫】,本号连载一系列优质文章,包括喵星哲学Cat系列、Python进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等,欢迎关注。
