當前位置:
首頁 > 知識 > Mozilla的Python3使用情況

Mozilla的Python3使用情況

(此處已添加圈子卡片,請到今日頭條客戶端查看)

Mozilla使用了很多Python。我們的大多數構建系統、CI配置、測試工具、命令行工具和無數其他腳本、工具或Github項目都是由Python處理的。在mozilla-central中有3500多個Python文件(不包括第三方文件),大約包含230萬行代碼。此外,在Github上的Mozilla org中有462個帶有Python標籤的存儲庫(儘管其中許多並不活躍)。這些存儲庫中有很多Python項目,其中大部分是Python2。

隨著Python 2停止支持的日期的臨近,這是一個很好的時間來評估當前的情況並提出一些問題。在Python3的遷移中,Mozilla已經走了多遠?哪些大型工作項位於關鍵路徑上?我們是否有一個計劃能在2020年1月1日Python 2停止支持之前達到一個好的狀態?

種樹(進行遷移)的第二個最佳的時間

但是在處理這些問題之前,我想先解決另一個經常出現的問題: 隨著Python2停止支持,我們是否需要實現100%的遷移?

從技術上講,不需要。我們仍然可以安裝Python 2,並且仍然可以從PyPi上安裝包。但繼續堅守Python2將是一個巨大的錯誤,原因如下:

  1. Python 2將不會再收到安全修復。雖然一個發現的問題對Mozilla的不利影響可能很小,但考慮到Python在Mozilla的所有關鍵任務中的使用(例如,簽名構建),即使是輕微的風險也要認真關注。2018年有三個針對Python 2.7的CVE(漏洞)被發現。再想一下如果我是一個擁有一個Python2漏洞的攻擊者,事先知道EOL(停止支持)日期,那麼我將把這個漏洞收藏起來直到那個日期之後再進行利用。
  2. 也許更重要的是,我們所依賴的所有第三方包(有很多)也將停止支持(假設它們還沒有放棄支持)。在這些更廣泛的包生態系統中,出現漏洞和bug的可能性要大得多,因此很難保證安全性。在2020年後將Python 2及其包生態系統用於關鍵任務應用程序就是在自找麻煩。
  3. 延遲意味著需要遷移更多的代碼。當你想要與大型Python2代碼庫進行介面時,你需要編寫與Python2兼容的代碼。雖然可以編寫同時兼容2和3的Python代碼,但是這樣做的動機並不總是顯而易見的。或者缺少技術訣竅。到目前為止,我們在Mozilla上編寫的大多數Python代碼只兼容Python 2。我們延遲遷移的時間越長,任務就變得越龐大。
  4. 這是一個持續的機會成本。Python 3於2008年首次發布,在此期間,Python 2中有大量的特性和改進是不可用的。比如非同步/等待、異常鏈接、類型提示和unicode處理。有了Mozilla開發人員單獨處理最後一項的時間,我們可能已經完成了整個遷移。

那麼,2020年1月1日是一個硬性的最後期限嗎?不。但這不會阻止我們以最快的速度進行遷移。不是因為這個最後期限,而是因為這將給Mozilla帶來最好的成功機會。

認真考慮遷移到Python3的最佳時機是五年前。第二次最佳的時間是現在。

當前狀況

現在,既然我們已經確定了遷移到python3是重要且有價值的,讓我們進入細節。本文的其餘部分只關注mozilla-central。不是因為我們外部repos中的Python不重要,而是因為我有資格討論mozilla-central。以下是我們目前取得的一些進展:

  • 我們增加了在CI中使用Python3運行python測試的能力。這為我們提供了一個保障,一旦模塊的單元測試在Python3下通過,我們就可以相對自信地認為,該模塊將來在Python3中依然會運行良好(假設有足夠的測試覆蓋率)。
  • 我們設置了一些linter。一個linter可以確保Python文件至少可以在Python3中導入而不會失敗,另一個linter可以確保Python2文件使用合適的的__future__語句,使將來遷移該文件稍微容易一些。儘管這些linter還沒有在所有應該啟用它的文件上啟用。
  • 最後,我們開始移植mozbase。它是在我們的構建、測試和CI基礎設施中到處使用的一組包。對這些模塊進行完全遷移是進行幾乎其他所有事情的先決條件。

雖然到目前為止所取得的進展並不明顯,但這只是完成全部工作的一小部分。那麼接下來會發生什麼呢?

下一個主要障礙

最初的重點是添加使用Python 3運行測試的能力,這已經完成了(儘管我們對用於此目的的機制並不完全滿意,稍後將詳細介紹)。但是,即使我們正在運行測試和linter來捕獲潛在的與Python3相關的問題,我們實際上並沒有在任何地方默認使用Python3。因此,下一個主要障礙是,用Python 3運行一個簡單的mach命令(比如mach google)。從表面上看,這聽起來很容易實現,畢竟所有的mach google命令只有四行代碼。但實際上,這是一個非常大的項目,我將把本文餘下的大部分時間用於這個項目。

使用Python3運行mach命令意味著不僅命令本身需要與Python3兼容,而且所有依賴項也需要兼容。幾乎每個命令(包括mach google)都依賴於兩個主要庫:python/mach和python/mozbuild。讓這些模塊(或者至少是大多數mach命令使用的那些模塊)與Python3一起工作是這裡的第一個主要困難。但是,儘管為Python3準備mach和mozbuild是一項繁重的任務,但同時又是一項簡單的任務。道路是明確的,我們只需要有人捲起袖子把工作做好。我估計這將不會超過一個星期的工作價值(這只是完成mach google所需的部分的時間)。

另一個阻礙是bootstrapping(引導)。我們驗證了開發人員在運行mach bootstrap時安裝了受支持的Python版本。我們需要就一個最低可行的版本達成一致(3.5似乎是可能的),然後修改我們的引導腳本,以確保開發人員同時擁有Python 2和Python 3的兼容版本。但這也不是一項非常困難的任務。

這個裡程碑的第三個也是最後一個部分是實際實現mach中的管道。要增強對命令進行內省的能力,並確定是否需要使用Python2或Python3運行命令。這就是複雜性所在。

讓我們深入研究一下,把需要解決的問題分解一下。這裡的基本假設是,這個tree(樹)太大了,不能一次全部轉換為Python3。代碼太多了,bitrot的潛力太大了,在不經意的情況下破壞其他東西的風險太大了。我們必須一次一個來慢慢地轉換命令。

調用問題

記住這一點,我們遇到的第一個問題是調用問題。在mach中,命令是通過裝飾器註冊在實際的Python類上的。如果你以前看過mach_commands.py文件,你可能已經注意到了@CommandProvider、@Command和@CommandArgument裝飾器。這為工具作者註冊他們的命令和使用的參數提供了一種非常方便的方法。但它也有一個很大的缺點: 每次調用mach時都會導入每一個mach_commands.py。這是mach獲得必需的命令元數據來確定它要執行什麼的唯一方法。

簡而言之,我們使用Python解析所有可用的命令,然後發送用戶指定的命令。但是現在我們要發送的命令可能需要一個不同於當前運行的Python。

選項1

如果我們不改變註冊的工作方式,這就意味著兩件事:

  1. 每個mach_commands.py和它們在頂層導入的所有東西至少都要在Python 3中可以進行解析(這很可能是很容易實現的)。
  2. 我們需要為使用了與運行mach_bootstrap迥然不同的Python版本的命令spawn(派生)兩個單獨的Python解釋器。例如,如果我們使用Python3來解析裝飾器,那麼我們將為需要Python2的命令派生第二個Python進程(反之亦然)。

儘管我們需要實現調用第二個Python進程的實際機制,但這是最簡單的解決方案。

選項2

或者,我們可以更改命令註冊的工作方式。代替(或者除此之外)使用裝飾器,我們可以將發送命令所必須的命令元數據(例如,名稱和模塊路徑)註冊在一些主要文件中。也許我們可以使用一個頂層的mach_commands.json文件,它看起來是這樣的:

Mozilla的Python3使用情況

mach二進位文件的內部有一些巧妙的修改,它既是有效的Python,也是有效的shell。當你執行./mach時,它首先會作為shell腳本運行,找到合適的Python可執行文件,然後將自身作為一個Python腳本來重新執行。有了這個提議,mach 驅動程序的shell部分可替換為:

  1. 解析cli以確定所需的子命令。
  2. 解析mach_commands.json。
  3. 根據python鍵查找Python可執行文件。
  4. 用合適的Python重新執行自身。

這比選項1複雜得多,但它避免了這兩個警告。也就是說,我們不需要擔心所有的Python 3都是可導入的,也不需要運行兩個單獨的Python進程。

通常情況下,我認為這種方法的複雜性遠不及這兩個微小的好處。但這個選項更有吸引力,因為這是我們一直在討論要做的事情。這個選項還有第三個更大的好處,儘管它與Python 3遷移完全無關。我們不需要在每次mach調用時都載入每一個mach_commands.py。可以在不導入所有文件的情況下獲得發送和運行mach help所需的所有信息。這將大大地加快mach調用。

最終結果是,這兩種選項都是可行的。如果我們想把精力集中在Python3遷移上,我會選擇選項1。但是選項2仍然很有吸引力,因為它可能會給我們一個同時解決兩個實質性問題的理由。在寫這篇文章的時候,我不確定我應該選擇哪一個選項。

依賴項問題

當你運行並執行mach命令時,系統會創建一個virtualenv,其中包含一組分散在mozilla-central上的「基礎" 包。我們稱之為「initial(初始)」virtualenv。一些帶有更複雜需求的命令確實會在這個基礎層上創建它們自己的virtualenv,除此之外,這個「init」virtualenv在默認情況下會被激活。當然,我們在這個virtualenv中安裝的包集合是不同的,這取決於我們使用Python2還是Python3來運行命令。我們不能在Python3 virtualenv中安裝只適用於Python2的包(反之亦然)。

這裡的解決方案非常簡單。我們可以維護兩個單獨的清單來關聯這兩個必需的virtualenv。一個用於Python2,一個用於Python3。有些模塊(甚至大多數模塊)可能會同時出現在這兩種清單中。但這裡還有其他需要考慮的事情。在mozilla-central中,我們需要解決一個類似但無關緊要的問題: 依賴項鎖定。

依賴項鎖定會確保一個工具的所有使用者使用的版本與其他人完全相同。這使得事情可以保持重現性和明確性,通過驗證哈希值可以阻止mitm(中間人攻擊)攻擊,並且這樣做被廣泛認為是任何包生態系統中的最佳實踐。依賴項鎖定值得考慮的原因是,處理依賴項鎖定的工具也傾向於處理virtualenv管理。事實上,我們使用了一個這樣的工具(Pipenv)來處理當前的依賴項鎖定需求。由於無論如何我們都在使用這些類型的工具,因此花一些時間研究它們是否能夠幫助我們處理Python 3依賴關係是值得的,那我們就來看一看。

Pipenv

近幾年來,Pipenv一直是Python社區的寵兒,我們在很多地方都使用它:

  1. 當出售第三方軟體包時。
  2. 當運行mach python-test在Python 2 / 3之間切換時(它甚至可以幫助解決調用問題)。
  3. 對於需要在運行時安裝額外的外部包的命令(提供了依賴項鎖定)。

當我和Dave Hunt在實現這些事情的時候,這是城裡唯一的一款遊戲。我們把一切都完成好了,但道路比我們希望的要曲折一些。我們最終實現了一些「足夠好」的東西,但是沒有達到我們想要的水平。我個人之所以不在Pipenv上出售這些東西以便它們可以在像我們這樣的大型monorepo上使用,主要有幾個原因:

  1. 維護者沒有協調好外部的變化,我們關閉了幾個看起來合理的PR(Pull Request),沒有做任何解釋(許多潛在的Pipenv貢獻者已經注意到了這一趨勢)。
  2. 在我們開發這些系統時,引入了一些bug和向後不兼容的更改。這在Pipenv沒有穩定化之前我們使用它時就已經很成問題了,但是版本控制和文檔使我們假定存在某種程度的穩定性,而這種穩定性是不存在的。
  3. 感覺它有點反應遲緩。
  4. 它包含了許多假設,假設你正在處理一個單個的Python包(而不是用於大型monorepo的工具)。我們必須把它扭曲到它不想去的方向。

我不得不說,我們上一次研究這些系統已經有一年了,我們使用的Pipenv版本也同樣老舊。從那時起,情況有可能有所改善。儘管如此,我不建議使用Pipenv來幫助我們。然而....

Poetry

Poetry的創建是為了彌補前面提到的Pipenv的一些缺點。我個人在我自己的幾個項目中使用它,並且我認為它是一個非常棒的工具。它看起來更敏捷、更輕量級,至少維護者對提議的新特性是開放討論的,而且我在使用它時從未遇到過bug或向後不兼容的更改(儘管版本還沒有達到1.0,所以向後不兼容的更改還是會遇到的)。

Poetry包含了我在Pipenv中所希望的一切功能,但是它仍然有一個很大的缺點: 它也假設你使用的是單個Python包。它甚至比Pipenv更進一步,迫使你提供諸如包名稱和版本之類的元數據。這使得它無法作為大型monorepo的工具後端。那麼,我又何必費心提及它呢?

Jetty

Jetty是我一直在構建的一個小實驗品。它是一個圍繞著Poetry本身的非常微小的包裝器,並試圖使它在像mozilla-central這樣的monorepo中使用時更有用。它做了一些事情:

  1. 移除定義包元數據的要求。
  2. 移除包管理命令(例如,包版本衝突)只留下依賴項和virtualenv管理相關命令。
  3. 提供了用於調用各種命令的編程API(因此我們不必在子進程中運行它)。

它似乎運行得相當好。我的下一步計劃是試驗用Jetty替換我們的代碼樹中的 Pipenv用法。如果一切順利,這可能是處理我們的Python 3依賴關係的一種可行方法。

所有這些關於Pipenv/Poetry/Jetty的討論,都與當前的問題無關。我們可以在沒有它們的情況下解決我們需要的所有問題,這可能是目前最明智的做法。我只是想提到它們,因為它們確實試圖解決我們面臨的許多相同的問題。它們至少是值得考慮的。

結論及具體步驟

總而言之,下一個主要障礙是開始用Python 3運行特定的mach命令(除了用Python 2運行其他命令之外)。這裡有一些具體的步驟可以幫助我們解決這個障礙:

  1. 用Python 3運行python/mach和python/mozbuild單元測試。
  2. 儘可能多地啟用py3 linter(最好是所有內容)。
  3. 臨時破解mach二進位文件,使其指向Python3,並嘗試運行一個非常基本的命令(例如 mach google)。
  4. 將Python3添加到我們的引導過程中。

與此同時,還有一些更大的問題需要解決。即調用和依賴項問題。在這兩種情況下,都有一種快速而又隨性的解決方案,以及一種更長但可能更好的解決方案。這兩種情況都需要一定程度的規劃和協調。

最後,我想回答我在開始時問的一個問題。我們是否計劃在2020年1月1日Python 2的EOL之前達到一個好的狀態?我的回答是不。這篇文章可能是一個非常粗略的計劃大綱,但它只討論了下一步的主要步驟。在這一步之後,我們仍然有轉換所有東西的實際工作要做。另外,本文甚至沒有涉及Github中的Python。回答「不」的另一個原因是,儘管一些工程師和團隊確實認識到這項工作的重要性,但這並不是高層管理人員關注的事情。我們只是沒有必要的資源分配來修復它,我不知道它有沒有在其他人的正式計劃表上。

話雖如此,我樂觀地認為,如果我們按優先順序來做,這些工作是可以及時完成的。如果我們不這樣做,我仍然樂觀地認為最終也會完成。只是可能趕不上2020年1月1日。


英文原文:https://ahal.ca/blog/2019/python-3-at-mozilla/

譯者:好酒不上頭

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python部落 的精彩文章:

表名應該複數化嗎?是用Person, Persons, People還是People?
在Python中如何使用sorted()和sort()函數

TAG:Python部落 |