笔者使用的操作系统为
windows 7
,python解释器版为python3.7
,pycharm版本为pycharm2018.3
如无特殊说明,代码均在此环境执行
目录
一般结论
网上普遍得到的结论是这样的
python xxx.py # 直接运行 python -m xxx # 相当于import,叫做当做模块来启动
- 两者主要区别在于sys.path不同:
直接运行会将该脚本所在目录添加至sys.path
当做模块启动则会将当前运行命令的路径添加至sys.path
这种解释虽然也没有错,但给人一个印象就是,执行程序的目的就是为了增加个目录?!
简单例子
先来看一个自己写的例子,编写两个py文件——test.py
和temp.py
,目录结构如下:
Practice
└─ demo
├─ sub
│ ├─ init.py
│ └─ temp.py
└─ test.py
test.py文件的内容如下:
import sys
import sub.temp
print("test.py 开始执行...")
print("__name__:",__name__)
print()
print("系统路径(sys.path):")
print('\n'.join(sys.path))
print()
temp.py文件的内容如下:
import sys
print("temp.py 开始执行...")
print("__name__:",__name__)
print()
print("系统路径(sys.path):")
print('\n'.join(sys.path))
print()
两个文件内容基本相同,都是打印内置属性__name__
和当前系统路径sys.path
,不同的是,test.py中把temp.py作为模块导入
CMD控制台把目录切换到demo,执行test.py
F:\Practice\demo>
F:\Practice\demo>python test.py
结果如下
temp.py 开始执行...
__name__: sub.temp
系统路径(sys.path):
F:\Practice\demo
F:\Practice
D:\Programs\Python\Python 3.7.2\python37.zip
D:\Programs\Python\Python 3.7.2\DLLs
D:\Programs\Python\Python 3.7.2\lib
D:\Programs\Python\Python 3.7.2
D:\Programs\Python\Python 3.7.2\lib\site-packages
D:\Programs\Python\Python 3.7.2\lib\site-packages\setuptools-40.8.0-py3.7.egg
D:\Programs\Python\Python 3.7.2\lib\site-packages\pip-19.0.3-py3.7.egg
test.py 开始执行...
__name__: __main__
系统路径(sys.path):
F:\Practice\demo
F:\Practice
D:\Programs\Python\Python 3.7.2\python37.zip
D:\Programs\Python\Python 3.7.2\DLLs
D:\Programs\Python\Python 3.7.2\lib
D:\Programs\Python\Python 3.7.2
D:\Programs\Python\Python 3.7.2\lib\site-packages
D:\Programs\Python\Python 3.7.2\lib\site-packages\setuptools-40.8.0-py3.7.egg
D:\Programs\Python\Python 3.7.2\lib\site-packages\pip-19.0.3-py3.7.egg
可以看到两者有些许的不同,
__name__
:test作为程序入口
__name__的值为__main__
,temp作为模块
__name__的值为模块名sub.temp
sys.path
:两者完全相同,这一点下面再详细说明
通过上面的程序,可以简单了解作为主程序执行
和模块导入的执行
结果的区别,
而python -m
以模块方式启动
又是怎么一回事呢?
官方参考文档对于模块
以及模块的执行
有详细的阐述
http://www.pythondoc.com/pythontutorial3/modules.html
模块
如果你想要编写一些更大的程序,为准备解释器输入使用一个文本会更好,并以那个文件作为输入执行…
… (略去部分内容)
Python 提供了一个方法可以从文件中获取定义…这样的文件被称为模块
模块
是包括Python 定义
和声明
的文件。文件名
就是模块名
加上 .py 后缀
。模块的模块名
(做为一个字符串)可以由全局变量__name__
得到
比如上述test.py程序
如果在python控制台导入的话
F:\Practice\demo>python
...(略)
>>> import test
结果如下:
temp.py 开始执行...
__name__: sub.temp
系统路径(sys.path):
F:\Practice
D:\Programs\Python\Python 3.7.2\python37.zip
D:\Programs\Python\Python 3.7.2\DLLs
D:\Programs\Python\Python 3.7.2\lib
D:\Programs\Python\Python 3.7.2
D:\Programs\Python\Python 3.7.2\lib\site-packages
D:\Programs\Python\Python 3.7.2\lib\site-packages\setuptools-40.8.0-py3.7.egg
D:\Programs\Python\Python 3.7.2\lib\site-packages\pip-19.0.3-py3.7.egg
test.py 开始执行...
__name__: test
系统路径(sys.path):
F:\Practice
D:\Programs\Python\Python 3.7.2\python37.zip
D:\Programs\Python\Python 3.7.2\DLLs
D:\Programs\Python\Python 3.7.2\lib
D:\Programs\Python\Python 3.7.2
D:\Programs\Python\Python 3.7.2\lib\site-packages
D:\Programs\Python\Python 3.7.2\lib\site-packages\setuptools-40.8.0-py3.7.egg
D:\Programs\Python\Python 3.7.2\lib\site-packages\pip-19.0.3-py3.7.egg
观察两者的__name__
test.py 中,__name__
: test
temp.py中,__name__
: sub.temp
sys.path
虽然也发生了变化,少了当前执行python的路径F:\Practice\demo,
但是两者依然相同,亦可以说两者是在同一个环境下执行,共用一个系统路径
此时的主程序其实是python解释器
,test.py,temp.py都是其模块
/子模块
>>> __name__
'__main__'
>>>
作为脚本(源程序)来执行模块
当你使用以下方式运行 Python 模块时,模块中的代码便会被执行:
python fibo.py <arguments>
模块中的代码会被执行,就像导入它一样
,不过此时 name 被设置为 “main”(程序入口),
这相当于,如果你在模块后加入如下代码:if __name__ == "__main__": import sys fib(int(sys.argv[1]))
就可以让此文件像作为模块导入时一样作为脚本执行。此代码只有在模块作为 “main” 文件执行(python或者python -m)时才被调用…
…(省略部分关于模块函数的内容)
如果模块被导入,不会执行这段代码…
…
这通常用来为模块提供一个便于测试的用户接口
(将模块作为脚本执行测试需求)
换句话说,如果编写模块时包含一些对模块进行测试的代码,应该在代码块之前添加判断
if __name__ == "__main__":
避免其他程序导入此模块的时候测试代码被执行
模块的搜索路径
导入一个叫 spam 的模块时,解释器先在当前目录中搜索名为 spam.py 的文件。如果没有找到的话,接着会到 sys.path 变量中给出的目录列表中查找。 sys.path 变量的初始值来自如下:
输入脚本的目录(当前目录)
环境变量 PYTHONPATH 表示的目录列表中搜索
(这和 shell 变量 PATH 具有一样的语法,即一系列目录名的列表)。Python 默认安装路径中搜索。
Note(提示)
在支持符号连接的文件系统中,输入的脚本所在的目录是
符号连接指向的目录
。 换句话说也就是包含符号链接的目录不会被加到目录搜索路径中
。
实际上,解释器由sys.path
变量指定的路径目录搜索模块,该变量初始化时默认包含了输入脚本(或者当前目录)
,PYTHONPATH
和PYthon安装目录
。这样就允许 Python程序了解如何修改或替换模块搜索目录。
需要注意的是由于这些目录中包含有搜索路径中运行的脚本,所以这些脚本不应该和标准模块重名,否则在导入模块时Python 会尝试把这些脚本当作模块来加载。这通常会引发错误。
注意上面这一段话
解释器由 sys.path
变量指定的路径目录搜索模块,该变量初始化时默认包含了输入脚本(或者当前目录)
, PYTHONPATH
和PYthon安装目录
。
这就解释了为什么用python指令执行源文件时,sys.path会包含当前目录,
如果直接把 temp.py 作为主程序执行
F:\Practice\demo\sub>python temp.py
会得到如下结果:
__name__: __main__
系统路径(sys.path):
F:\Practice\demo\sub
F:\Practice
D:\Programs\Python\Python 3.7.2\python37.zip
D:\Programs\Python\Python 3.7.2\DLLs
D:\Programs\Python\Python 3.7.2\lib
D:\Programs\Python\Python 3.7.2
D:\Programs\Python\Python 3.7.2\lib\site-packages
D:\Programs\Python\Python 3.7.2\lib\site-packages\setuptools-40.8.0-py3.7.egg
D:\Programs\Python\Python 3.7.2\lib\site-packages\pip-19.0.3-py3.7.egg
内置变量__name__
变成了 __main__
系统路径sys.path包含三部分:
-
当前目录:
F:\Practice\demo\sub
, -
自定义python路径:
F:\Practice
笔者在系统的环境变量中配置有PYTHONPATH
,如图所示
-
PYthon安装目录:
D:\Programs\Python\Python 3.7.2\python37.zip
D:\Programs\Python\Python 3.7.2\DLLs
D:\Programs\Python\Python 3.7.2\lib
D:\Programs\Python\Python 3.7.2
…(略)
作为模块来执行脚本(源程序)
与上面以主程序的方式执行模块相反,以模块的方式—python -m xx
执行源程序与一般执行方式—python xx.py
很不同,更加类似于导入模块import xx
用python -help
可以查看python -m
的解释和用法
F:\Practice\demo>python -h
usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...
Options and arguments (and corresponding environment variables):
...(略)
-m mod : run library module as a script (terminates option list)
...(略)
-m mod : run library module as a script (terminates option list)
翻译过来就是——
·-m 模块名:运行library中的模块,如同脚本一样执行 (末尾选项列表)
实际上,不仅仅是Python主目录\lib下的模块
,sys.path路径下所有的模块,都可以被执行,只是Python -m
一般用途是执行python库中的模块
例如,当系统中同时安装有Python2 和 Python3,都自带安装有 pip模块,就可以采用python -m
方法安装其他模块:
Python2:
python2 -m pip install XXX
Python3:
python3 -m pip install XXX
由于test
也是Python自带的系统检测的模块
(与笔者自定义的 test.py 文件产生冲突,完全是始料未及的事)
F:\Practice\demo\sub>python -m test
== CPython 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64
bit (AMD64)]
== Windows-7-6.1.7601-SP1 little-endian
== cwd: C:\Users\ADMINI~1\AppData\Local\Temp\test_python_7656
== CPU count: 2
== encodings: locale=cp936, FS=utf-8
Run tests sequentially
0:00:00 [ 1/416] test_grammar
0:00:00 [ 2/416] test_opcodes
0:00:00 [ 3/416] test_dict
0:00:00 [ 4/416] test_builtin
0:00:01 [ 5/416] test_exceptions
0:00:04 [ 6/416] test_types
0:00:04 [ 7/416] test_unittest
0:00:10 [ 8/416] test_doctest
0:00:14 [ 9/416] test_doctest2
0:00:14 [ 10/416] test_support
...(略)
下面测试模块的部分改用temp.py来代替,除去模块导入的部分,temp.py其他语句与test.py完全一样,不影响最终的结论
F:\Practice\demo\sub>python -m temp
temp.py 开始执行...
__name__: __main__
系统路径(sys.path):
F:\Practice\demo\sub
F:\Practice
D:\Programs\Python\Python 3.7.2\python37.zip
D:\Programs\Python\Python 3.7.2\DLLs
D:\Programs\Python\Python 3.7.2\lib
D:\Programs\Python\Python 3.7.2
D:\Programs\Python\Python 3.7.2\lib\site-packages
D:\Programs\Python\Python 3.7.2\lib\site-packages\setuptools-40.8.0-py3.7.egg
D:\Programs\Python\Python 3.7.2\lib\site-packages\pip-19.0.3-py3.7.egg
乍一看,和上面以主程序的方式—python temp.py
,执行的结果一模一样
然而,如果切换一下目录执行,就会发现其中的差别
切换到外层目录对比
F:\Practice\demo\sub>cd ..
F:\Practice\demo>
使用
python
方式执行
python
方式执行
F:\Practice\demo>python .\sub\temp.py
结果如下:
F:\Practice\demo>python .\sub\temp.py
temp.py 开始执行...
__name__: __main__
系统路径(sys.path):
F:\Practice\demo\sub
F:\Practice
D:\Programs\Python\Python 3.7.2\python37.zip
D:\Programs\Python\Python 3.7.2\DLLs
D:\Programs\Python\Python 3.7.2\lib
D:\Programs\Python\Python 3.7.2
...(略)
源文件所在的路径—F:\Practice\demo\sub
,加入到了系统路径sys.path
中
使用
python -m
的方式执行
python -m
的方式执行
尝试使用执行 temp.py 文件的方式,通过文件路径执行
F:\Practice\demo>python -m temp
D:\Programs\Python\Python 3.7.2\python.exe: No module named temp
F:\Practice\demo>python -m .\sub\temp
D:\Programs\Python\Python 3.7.2\python.exe: Relative module names not supported
F:\Practice\demo>python -m sub\temp
D:\Programs\Python\Python 3.7.2\python.exe: No module named sub\temp
全部失败!!!
根本无法再使用一般的文件路径执行,只能使用与 import
类似的方式
F:\Practice\demo>python -m sub.temp
temp.py 开始执行...
__name__: __main__
系统路径(sys.path):
F:\Practice\demo
F:\Practice
D:\Programs\Python\Python 3.7.2\python37.zip
D:\Programs\Python\Python 3.7.2\DLLs
D:\Programs\Python\Python 3.7.2\lib
D:\Programs\Python\Python 3.7.2
(略)
并且当前目录,也就是执行python命令的目录—F:\Practice\demo
,被加入到了系统路径sys.path
中,这就出现了网上那个结论——两者主要区别在于sys.path不同
然而,从上面的执行的方式看,两者的区别也是非常明显
- 对于 源程序 temp.py 而言,显然,想要正确的执行,必须通过文件路径的方式(绝对路径/相对路径)才能访问到,这一点很容易理解
- 对于模块
temp
而言,作为一个模块,它必须通过模块的搜索路径去访问,也就是要在系统sys.path
下搜寻到才能执行,
而输入的文件路径只会被当做包名或者模块名
,例如上面的sub\temp
F:\Practice\demo>python -m sub\temp D:\Programs\Python\Python 3.7.2\python.exe: No module named sub\temp
- 至于
sub.temp
为什么能够执行,对比import
就不难理解了,两者都是以模块方式执行/导入模块,也就是说sub.temp
其实是包路径——
系统路径sys.path中的F:\Practice\demo目录下存在sub包,sub下存在temp模块
- 使用
python -m
时,千万不要写.py后缀,否则,程序虽然能执行,但会出现无法找到模块的错误F:\Practice\demo\sub>python -m temp.py ...(略) (ModuleNotFoundError: __path__ attribute not found on 'temp' while trying to find 'temp.py')
实践总结
- 想要弄清楚
python
和python-m
的区别,就必须先清楚文件路径
和模块搜索路径
这两个概念——
文件路径
(例如sub\temp.py
)用来访问pyhon源文件
模块搜索路径
(例如sub.temp
)用来搜索模块 - 并非
执行程序造成的结果是当前目录添加至sys.path
,相反,pyhon执行程序前将当前目录添加至了sys.path
,之后解释器再通过sys.path去访问导入的模块 - 所谓
当前目录
,
对于python xx.py
而言就是源文件所在的位置
对于python -m xx
而言就是命令所执行的位置 - 与使用
import
导入模块时,__name__
=模块名
不同,
无论是python xx.py
还是python -m xx
,两者都是将模块作为主程序入口执行,所以__name__
都是__main__
- 使用
python -m
一个显而易见的好处就是,可以随时随地(无论在那个目录下
)执行Python库
中的模块,
例如:- 启动Python自带的Http服务器
python -m http.server
可以通过通过属性__file__查看模块具体位置>>> import http.server >>> http.server.__file__ 'D:\\Programs\\Python\\Python 3.7.2\\lib\\http\\server.py'
- 启动pygame中样例,
python -m pygame.examples.aliens
查看模块具体位置>>> import pygame.examples.aliens pygame 1.9.5 Hello from the pygame community. https://www.pygame.org/contribute.html >>> pygame.examples.aliens.__file__ 'D:\\Programs\\Python\\Python 3.7.2\\lib\\site-packages\\pygame\\examples\\aliens.py'
- 启动Python自带的Http服务器
以上就是自己的实践,结合python文档以及网上查询的一些资料重新做一个简单的总结
参考文章
python 脚本的启动模式(python -m以模块方式启动)
作者:wonengguwozai
出处:CSDN
python -m 参数
作者:云上小悟
出处:麦新杰的独立博客