python -m詳解

溫馨提示: 本篇演示環境是Python 3.8

python --help看下python -m參數的解釋:

-m mod : run library module as a script (terminates option list)

modmodule的縮寫,即-m後面跟的是模塊(module)名,意思是把模塊當成腳本來運行。

terminates option list意味着-m之後的其它選項不起作用,在這點上它跟-c是類似,都是終極選項。

既然涉及到模塊,這裏就多提幾句。 在Python中,一個.py文件就稱之爲一個模塊(Module)。

比如一個頂層包名bytesfly,按照如下目錄存放:

bytesfly
├─ __init__.py
├─ __main__.py
└─ fly.py

上面fly.py模塊的名字就是bytesfly.fly

注意: 模塊名是不帶.py後綴的。

關於模塊更詳細的講解見之前的博客:
https://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", __package__)
    print(sys.path)

bytesfly包下的__init__.py文件內容如下:

print("執行了__init__.py")

hello項目路徑下執行python -m bytesfly.fly,輸出如下:

執行了__init__.py
----fly----
fly_main bytesfly
['/home/bytesfly/py/hello', '/home/bytesfly/anaconda3/envs/test/lib/python38.zip']

如果直接執行呢? 即相同路徑下執行python bytesfly/fly.py,輸出如下:

----fly----
fly_main None
['/home/bytesfly/py/hello/bytesfly', '/home/bytesfly/anaconda3/envs/test/lib/python38.zip']

總結一下,python -m module_namepython folder/file.py,都會把定位到的Python腳本當成主程序入口來執行,即在執行時,該腳本的__name__都是__main__,與import導入模塊不同。

但是有注意到上面兩種調用方式的不同之處嗎?

  • 通過python -m module_name執行一個模塊會首先執行package下的__init__.py文件,並且__package__變量被賦上相應的值;而python folder/file.py不會執行__init__.py
  • 兩種執行方法的sys.path不同,Python中的sys.path是Python用來搜索包和模塊的路徑。通過python -m執行一個模塊時會將當前路徑加入到系統路徑中,而直接執行腳本則會將腳本所在文件夾加入到系統路徑中

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_namepython folder/file.py,在實現上有什麼不同呢?

  • 使用python -m module_name,解釋器在不import模塊的情況下,在所有模塊命名空間中查找,定位到腳本的路徑,然後執行。爲了實現這個過程,解釋器會藉助兩個模塊:pkgutilrunpy,前者用於獲取所有的模塊列表,後者根據模塊名來定位並執行腳本
  • 直接運行腳本時,相當於給出了腳本的完整路徑(不管是絕對路徑還是相對路徑),解釋器根據文件系統的查找機制,定位到該腳本,然後執行

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,輸出如下:

執行了__init__.py
---bytesfly---
['/home/bytesfly/py/hello', '/home/bytesfly/anaconda3/envs/test/lib/python38.zip']

如果執行python -m bytesfly.a,輸出如下:

執行了__init__.py
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選項,在命令行中使用內置模塊、標準包與第三方模塊更加方便。

參考:

https://www.cnblogs.com/pythonista/p/11829632.html

https://www.cnblogs.com/xueweihan/p/5118222.html

https://a7744hsc.github.io/python/2018/05/03/Run-python-script.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章