软件测试学习 之 Python 两种执行方式——python和python -m 的区别

笔者使用的操作系统为windows 7,python解释器版为python3.7,pycharm版本为pycharm2018.3
如无特殊说明,代码均在此环境执行

目录

一般结论

网上普遍得到的结论是这样的

python xxx.py # 直接运行
python -m xxx # 相当于import,叫做当做模块来启动
  • 两者主要区别在于sys.path不同:
    直接运行会将该脚本所在目录添加至sys.path
    当做模块启动则会将当前运行命令的路径添加至sys.path

这种解释虽然也没有错,但给人一个印象就是,执行程序的目的就是为了增加个目录?!

简单例子

先来看一个自己写的例子,编写两个py文件——test.pytemp.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 变量指定的路径目录搜索模块,该变量初始化时默认包含了输入脚本(或者当前目录)PYTHONPATHPYthon安装目录。这样就允许 Python程序了解如何修改或替换模块搜索目录。
 
需要注意的是由于这些目录中包含有搜索路径中运行的脚本,所以这些脚本不应该和标准模块重名,否则在导入模块时Python 会尝试把这些脚本当作模块来加载。这通常会引发错误。

注意上面这一段话

解释器由 sys.path 变量指定的路径目录搜索模块,该变量初始化时默认包含了输入脚本(或者当前目录)PYTHONPATHPYthon安装目录

这就解释了为什么用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包含三部分

  1. 当前目录:F:\Practice\demo\sub

  2. 自定义python路径:F:\Practice
    笔者在系统的环境变量中配置有PYTHONPATH,如图所示
    在这里插入图片描述

  3. 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方式执行
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的方式执行

尝试使用执行 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')
    

实践总结

  • 想要弄清楚 pythonpython-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文档以及网上查询的一些资料重新做一个简单的总结


参考文章
python 脚本的启动模式(python -m以模块方式启动)
作者:wonengguwozai
出处:CSDN

python -m 参数
作者:云上小悟
出处:麦新杰的独立博客

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值