python -m詳解
溫馨提示: 本篇演示環境是
Python 3.8
先python --help
看下python -m
參數的解釋:
-m mod : run library module as a script (terminates option list)
mod
是module
的縮寫,即-m
後面跟的是模組(module
)名,意思是把模組當成腳本來運行。
terminates option list
意味著-m
之後的其它選項不起作用,在這點上它跟-c
是類似,都是終極選項。
既然涉及到模組,這裡就多提幾句。 在Python中,一個.py
文件就稱之為一個模組(Module
)。
比如一個頂層包名bytesfly
,按照如下目錄存放:
bytesfly
├─ __init__.py
├─ __main__.py
└─ fly.py
上面fly.py
模組的名字就是bytesfly.fly
。
注意: 模組名是不帶.py
後綴的。
關於模組更詳細的講解見之前的部落格:
//www.cnblogs.com/bytesfly/p/python.html#模組
python -m 常見用法
- 使用
cProfile
模組分析程式函數調用鏈耗時
python -m cProfile -s cumulative bytesfly/fly.py
- 使用
pdb
模組以調試模式來執行Python腳本
python -m pdb bytesfly/fly.py
- 使用
http.server
模組實現一個簡單的HTTP服務
python -m http.server 9000
- 使用
pydoc
模組生成HTML格式的官方幫助文檔,可以在瀏覽器中訪問
python -m pydoc -p 9001
- python -m pip install xxx
在存在多個Python版本的環境中,這種寫法可以精確地控制三方庫的安裝位置。例如用python3.8 -m pip
,可以明確指定給3.8
版本安裝,而不會混淆成其它的版本。
當然現在我們大多使用conda
之類的虛擬環境管理器和包管理器,可能不會出現上面所說的這種混淆情況。這裡只是提一下。
- 使用
timeit
模組分析執行耗時
python -m timeit -n 3 -r 2 "import time;time.sleep(1)"
其實調用的是:
timeit.repeat("import time;time.sleep(1)", repeat=2, number=3)
看一眼timeit.py
中的源碼就能快速理解-n
-r
參數的意思:
def repeat(self, repeat=default_repeat, number=default_number):
"""Call timeit() a few times.
This is a convenience function that calls the timeit()
repeatedly, returning a list of results. The first argument
specifies how many times to call timeit(), defaulting to 5;
the second argument specifies the timer argument, defaulting
to one million.
"""
r = []
for i in range(repeat):
t = self.timeit(number)
r.append(t)
return r
-p/–process: use time.process_time() (default is time.perf_counter())
timeit
後面還能添加-p
參數,如下:
python -m timeit -p -n 3 -r 2 "import time;time.sleep(1)"
其實調用的是:
timeit.repeat("import time;time.sleep(1)", repeat=2, number=3, timer=time.process_time)
再看一眼time.process_time()
的程式碼注釋
def process_time(): # real signature unknown; restored from __doc__
"""
process_time() -> float
Process time for profiling: sum of the kernel and user-space CPU time.
"""
return 0.0
加上了-p
參數計算的是sum of the kernel and user-space CPU time
,講白了就是程式佔用CPU的時間,程式睡眠或者請求網路IO阻塞的時間不算。
python -m 原理解析
看了上面python -m
幾種常見用法,你是否好奇python -m
到底做了什麼事?
不賣關子,一句話解釋就是:
對於
python -m module_name
,Python會檢索sys.path
,查找名字為module_name
的模組或者包,並將其內容當成主程式入口來執行,換句話說在執行時,該腳本的__name__
是__main__
。
拿文章開篇的bytesfly.fly
模組來說,也就是bytesfly
包下的fly.py
文件內容如下:
import sys
print("----fly----")
if __name__ == '__main__':
print("fly_main")
print(sys.path)
在hello
項目路徑下執行python -m bytesfly.fly
,輸出如下:
----fly----
fly_main
['/home/bytesfly/py/hello', '/home/bytesfly/anaconda3/envs/test/lib/python38.zip']
如果直接執行呢? 即相同路徑下執行python bytesfly/fly.py
,輸出如下:
----fly----
fly_main
['/home/bytesfly/py/hello/bytesfly', '/home/bytesfly/anaconda3/envs/test/lib/python38.zip']
總結一下,python -m module_name
與python folder/file.py
,都會把定位到的Python腳本當成主程式入口來執行,即在執行時,該腳本的__name__
都是__main__
,與import
導入模組不同。
但是有注意到上面兩種調用方式的不同之處嗎?
fly.py
程式輸出了sys.path
,可以看到兩種調用方式的Python Path
有區別,這種區別有什麼影響呢?
再看一個例子。 比如一個頂層包名還是bytesfly
,按照如下目錄存放:
bytesfly
├─ __init__.py
├─ __main__.py
└─ fly.py
└─ a
├─ __init__.py
└─ run.py
└─ b
├─ __init__.py
└─ tool.py
其中tool.py
內容如下:
def add(a, b):
return a + b
其中run.py
內容如下:
import sys
print(sys.path)
if __name__ == '__main__':
from bytesfly.b import tool
print(tool.add(1, 2))
同樣在hello
項目路徑下執行python -m bytesfly.a.run
,輸出如下:
['/home/bytesfly/py/hello', '/home/bytesfly/anaconda3/envs/test/lib/python38.zip']
3
然後在hello
項目路徑下執行python bytesfly/a/run.py
,輸出如下:
['/home/bytesfly/py/hello/bytesfly/a', '/home/bytesfly/anaconda3/envs/test/lib/python38.zip']
Traceback (most recent call last):
File "bytesfly/a/run.py", line 6, in <module>
from bytesfly.b import tool
ModuleNotFoundError: No module named 'bytesfly'
這個地方能Get到這兩種調用方式在Python Path
上的區別?
除此之外,python -m module_name
與python folder/file.py
,在實現上有什麼不同呢?
- 使用
python -m module_name
,解釋器在不import
模組的情況下,在所有模組命名空間中查找,定位到腳本的路徑,然後執行。為了實現這個過程,解釋器會藉助兩個模組:pkgutil
和runpy
,前者用於獲取所有的模組列表,後者根據模組名來定位並執行腳本 - 直接運行腳本時,相當於給出了腳本的完整路徑(不管是絕對路徑還是相對路徑),解釋器根據文件系統的查找機制,定位到該腳本,然後執行
python -m 補充說明
python -m module_name
這裡的module_name
也可以是包名。
還是用上面的頂層包名bytesfly
舉例,按照如下目錄存放:
bytesfly
├─ __init__.py
├─ __main__.py
└─ fly.py
└─ a
├─ __init__.py
└─ run.py
└─ b
├─ __init__.py
└─ tool.py
其中__main__.py
內容如下:
import sys
print("---bytesfly---")
if __name__ == '__main__':
print(sys.path)
項目路徑下執行python -m bytesfly
,輸出如下:
---bytesfly---
['/home/bytesfly/py/hello', '/home/bytesfly/anaconda3/envs/test/lib/python38.zip']
如果執行python -m bytesfly.a
,輸出如下:
No module named bytesfly.a.__main__; 'bytesfly.a' is a package and cannot be directly executed
原來,python -m bytesfly
等效於python -m bytesfly.__main__
。
寫在最後
有了python -m module_name
選項,在命令行中使用內置模組、標準包與第三方模組更加方便。
參考:
//www.cnblogs.com/pythonista/p/11829632.html
//www.cnblogs.com/xueweihan/p/5118222.html