漲見識了,在終端執行 Python 代碼的 6 種方式!
為了我們推出的 VS Code 的 Python 插件 [1],我寫了一個簡單的腳本來生成變更日志 [2](類似于Towncrier [3],但簡單些,支援 Markdown,符合我們的需求)。在釋出過程中,有一個步驟是運作python news ,它會将 Python 指向我們代碼中的"news"目錄。
前幾天,一位合作者問這是如何工作的,似乎我們團隊中的每個人都知道如何使用-m ?(請參閱我的有關帶 -m 使用 pip 的文章 [4],了解原因)(譯注:關于此話題,我也寫過一篇更為詳細的文章 )
這使我意識到其他人可能不知道有五花八門的方法可以将 Python 指向要執行的代碼,是以有了這篇文章。
1、通過标準輸入和管道
因為如何用管道傳東西給一個程序是屬于 shell 的内容,我不打算深入解釋。毋庸置疑,你可以将代碼傳遞到 Python 中。
管道傳内容給 python
echo "print('hi')" | python
如果将檔案重定向到 Python,這顯然也可以。
重定向一個檔案給 python
python < spam.py
歸功于 Python 的 UNIX 傳統,這些都不太令人感到意外。
2、通過-c 指定的字元串
如果你隻需要快速地檢查某些内容,則可以在指令行中将代碼作為字元串傳遞。
使用 python 的 -c 參數
python -c "print('hi')"
當需要檢查僅一行或兩行代碼時,我個人會使用它,而不是啟動 REPL(譯注:Read Eval Print Loop,即互動式解釋器,例如在 windows 控制台中輸入python, 就會進入互動式解釋器。-c 參數用法可以省去進入解釋器界面的過程) 。
3、檔案的路徑
最衆所周知的傳代碼給 python 的方法很可能是通過檔案路徑。
指定 python 的檔案路徑
python spam.py
要實作這一點的關鍵是将包含該檔案的目錄放到sys.path 裡。這樣你的所有導入都可以繼續使用。但這也是為什麼你不能/不應該傳入包含在一個包裡的子產品路徑。因為sys.path 可能不包含該包的目錄,是以所有的導入将相對于與你預期的包不同的目錄。
4、對包使用 -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 語句都不該寫。)
5、目錄
定義__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 子產品,還是以它為主子產品執行,也就不會破壞原來的依賴導入關系)
6、執行一個壓縮檔案
如果你确實有多個檔案和/或依賴子產品,并且希望将所有代碼作為一個單元釋出,你可以用一個__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貓”,閱讀更多優質的原創或譯作。)
原作:BRETT CANNON
譯者:豌豆花下貓@Python貓
英文:
https://snarky.ca/the-many-ways-to-pass-code-to-python-from-the-terminal轉載位址
https://www.cnblogs.com/pythonista/p/13055856.html