漲見識了,在終端執行 Python 代碼的 6 種方式!

原做:BRETT CANNONhtml

譯者:豌豆花下貓@Python貓python

英文:snarky.ca/the-many-wa…linux

爲了咱們推出的 VS Code 的 Python 插件 [1],我寫了一個簡單的腳原本生成變動日誌 [2](相似於Towncrier [3],但簡單些,支持 Markdown,符合咱們的需求)。在發佈過程當中,有一個步驟是運行python news ,它會將 Python 指向咱們代碼中的"news"目錄。git

前幾天,一位合做者問這是如何工做的,彷佛咱們團隊中的每一個人都知道如何使用-m ?(請參閱個人有關帶 -m 使用 pip 的文章 [4],瞭解緣由)(譯註:關於此話題,我也寫過一篇更爲詳細的文章github

這使我意識到其餘人可能不知道有五花八門的方法能夠將 Python 指向要執行的代碼,所以有了這篇文章。shell

一、經過標準輸入和管道

由於如何用管道傳東西給一個進程是屬於 shell 的內容,我不打算深刻解釋。毋庸置疑,你能夠將代碼傳遞到 Python 中。windows

# 管道傳內容給 python
echo "print('hi')" | python
複製代碼

若是將文件重定向到 Python,這顯然也能夠。緩存

# 重定向一個文件給 python
python < spam.py
複製代碼

歸功於 Python 的 UNIX 傳統,這些都不太使人感到意外。bash

二、經過-c 指定的字符串

若是你只須要快速地檢查某些內容,則能夠在命令行中將代碼做爲字符串傳遞。app

# 使用 python 的 -c 參數
python -c "print('hi')"
複製代碼

當須要檢查僅一行或兩行代碼時,我我的會使用它,而不是啓動 REPL(譯註:Read Eval Print Loop,即交互式解釋器,例如在 windows 控制檯中輸入python, 就會進入交互式解釋器。-c 參數用法能夠省去進入解釋器界面的過程) 。

三、文件的路徑

最衆所周知的傳代碼給 python 的方法極可能是經過文件路徑。

# 指定 python 的文件路徑
python spam.py
複製代碼

要實現這一點的關鍵是將包含該文件的目錄放到sys.path 裏。這樣你的全部導入均可以繼續使用。但這也是爲何你不能/不該該傳入包含在一個包裏的模塊路徑。由於sys.path 可能不包含該包的目錄,所以全部的導入將相對於與你預期的包不一樣的目錄。

四、對包使用 -m

執行 Python 包的正確方法是使用 -m 並指定要運行的包名。

python -m spam
複製代碼

它在底層使用了runpy [5]。要在你的項目中作到這點,只須要在包裏指定一個__main__.py 文件,它將被當成__main__ 執行。並且子模塊能夠像任何其它模塊同樣導入,所以你能夠對其進行各類測試。

我知道有些人喜歡在一個包裏寫一個main 子模塊,而後將其__main__.py 寫成:

from . import main

if __name__ == "__main__":
    main.main()
複製代碼

就我我的而言,我不感冒於單獨的main 模塊,而是直接將全部相關的代碼放入__main__.py ,由於我感受這些模塊名是多餘的。

(譯註:即做者不關心做爲入口文件的"main"或者「__main__」模塊,由於執行時只需用它們的包名便可。我認爲這也暗示了入口模塊不應再被其它模塊 import。我上篇文章 [6]比做者的觀點激進,認爲連那句 if 語句都不應寫。)

五、目錄

定義__main__.py也能夠擴展到目錄。若是你看一下促成此博客文章的示例,python news 可執行,就是由於 news 目錄有一個 __main__.py 文件。該目錄就像一個文件路徑被 Python 執行了。

如今你可能會問:「爲何不直接指定文件路徑呢?」好吧,坦白說,關於文件路徑,有件事得說清楚。😄在發佈過程當中,我能夠簡單地寫上說明,讓運行python news/announce.py ,可是並無確切的理由說明這種機制什麼時候存在。

再加上我之後能夠更改文件名,並且沒人會注意到。再加上我知道代碼會帶有輔助文件,所以將其放在目錄中而不是單獨做爲單個文件是有意義的。

固然,我也能夠將它變爲一個使用 -m 的包,可是不必,由於 announce 腳本很簡單,我知道它要保持成爲一個單獨的自足的文件(少於 200 行,而且測試模塊也大約是相同的長度)。

何況,__main__.py 文件很是簡單。

import runpy
# Change 'announce' to whatever module you want to run.
runpy.run_module('announce', run_name='__main__', alter_sys=True)
複製代碼

如今顯然必需要處理依賴關係,可是若是你的腳本僅使用標準庫或將依賴模塊放在__main__.py 旁邊(譯註:即同級目錄),那麼就足夠了!

(譯註:我以爲做者在此有點「炫技」了,由於這種寫法的前提是得知道 runpy 的用法,可是就像前一條所寫的用 -m 參數運行一個包,在底層也是用了 runpy。不過炫技的好處也很是明顯,即__main__.py 裏不用導入 announce 模塊,仍是以它爲主模塊執行,也就不會破壞原來的依賴導入關係)

六、執行一個壓縮文件

若是你確實有多個文件和/或依賴模塊,而且但願將全部代碼做爲一個單元發佈,你能夠用一個__main__.py ,放置在一個壓縮文件中,並把壓縮文件所在目錄放在 sys.path 裏,Python 會替你運行__main__.py 文件。

# 將一個壓縮包傳給 Python
python app.pyz
複製代碼

人們如今習慣上用 .pyz 文件擴展名來命名此類壓縮文件,但這純粹是傳統,不會影響任何東西;你固然也能夠用 .zip 文件擴展名。

爲了簡化建立此類可執行的壓縮文件,標準庫提供了zipapp [7]模塊。它會爲你生成__main__.py並添加一條組織行(shebang line),所以你甚至不須要指定 python,若是你不想在 UNIX 上指定它的話。若是你想移動一堆純 Python 代碼,這是一種不錯的方法。

不幸的是,僅當壓縮文件包含的全部代碼都是純 Python 時,才能這樣運行壓縮文件。執行壓縮文件對擴展模塊無效(這就是爲何 setuptools 有一個 zip_safe [8]標誌的緣由)。(譯註:擴展模塊 extension module,即 C/C++ 之類的非 Python 文件)

要加載擴展模塊,Python 必須調用 dlopen() [9]函數,它要傳入一個文件路徑,但當該文件路徑就包含在壓縮文件內時,這顯然不起做用。

我知道至少有一我的與 glibc 團隊交談過,關於支持將內存緩衝區傳入壓縮文件,以便 Python 能夠將擴展模塊讀入內存,並將其傳給壓縮文件,可是若是內存爲此服務,glibc 團隊並不一樣意。

可是,並不是全部但願都喪失了!你可使用諸如shiv [10]之類的項目,它會捆綁(bundle)你的代碼,而後提供一個__main__.py 來處理壓縮文件的提取、緩存,而後爲你執行代碼。儘管不如純 Python 解決方案理想,但它確實可行,而且在這種狀況下算得上是優雅的。

(譯註:翻譯水平有限,不免誤差。我加註了部份內容,但願有助於閱讀。請搜索關注「Python貓」,閱讀更多優質的原創或譯做。)

參考連接

[0] snarky.ca/the-many-wa…

[1] marketplace.visualstudio.com/items?itemN…

[2] github.com/microsoft/v…

[3] pypi.org/project/tow…

[4] snarky.ca/why-you-sho…

[5] docs.python.org/3/library/r…

[6] mp.weixin.qq.com/s/1ehySR5NH…

[7] docs.python.org/3/library/z…

[8] setuptools.readthedocs.io/en/latest/s…

[9] linux.die.net/man/3/dlope…

[10] pypi.org/project/shi…

相關文章
相關標籤/搜索