- 原文地址:Moving a large and old codebase to Python3
- 原文做者:Anders Hovmöller
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:Starrier
- 校對者:LynnShaw,steinliber
一年半前,咱們就決定使用 Python 3 了。咱們已經討論了很長時間,如今是時候使用了!如今這個過程已經結束了,咱們已經把生產環境的最後部署都遷移到了 Python 3html
關於修改 Python 3 的一些基本統計數據,是基於對 git 提交歷史的粗略過濾產生的:前端
我發現有 109 個 jira 問題與這個項目相關。python
咱們的理念一直是 py2 →py2/py3 → py3 由於咱們實在沒法在實際生產中實現鉅變,這種直覺也以使人驚訝的方式被證實是正確的。這意味着 2 到 3 是不可能的,我認爲這很常見。咱們嘗試過使用 2 to 3 來檢測 Python 3 的兼容性問題,但很快這也被發現沒法成立。基本上,這樣的更改意味着在 Python 2 中的代碼將被破壞。這樣的改變不可行。android
結論是使用 six, 這是一個庫,能夠方便的構建一個在 Python 2 和 3 中都有效的代碼庫。ios
首當其衝的就是更新以前的依賴關係。這項工做須要馬上啓動,由於以後會有更多的內容要更新。git
Python-modernize 是咱們選擇進行遷移的工具。它是一個能夠自動將 Py 2 代碼庫轉換爲可兼容 six 代碼庫的工具。咱們首先引入一個測試,做爲 CI 的一部分,來檢查基於 modernize 的新代碼是否已經準備好兼容 py3 了。這樣作最大的效果的是讓那些仍使用 Py 2 語法的人意識到新的處理方法,但這顯然對將現有的 240 k 行代碼轉化到 six 做用不大。咱們都有使用舊語法的壞習慣,這能夠說是教學上的成功了,即便它對代碼行的計數沒有什麼不一樣,它也被咱們用於實驗分支:github
我新建了一個名爲「Python 3 」的分支,並作了如下操做:數據庫
這裏的想法是「run ahead」,即看看若是咱們沒有使用過期的依賴項,咱們會遇到什麼問題。這個分支容許我在超級中斷狀態下能夠很是快速地啓動應用程序,至少能夠運行一些單元測試。 這個分支有很大的不一樣,但我仍是找到了把它應用在適當場景的方法。我使用優秀的 GitUp 來拆分、組合和提交。當一個提交看起來不錯的時候,我會把它挑選到一個新的分支,而後發給代碼審查。後端
沒有人能夠在這個分支上工做,由於它被不斷地 rebase ,強制推送,濫用,可是它確實讓項目向前推動了,而不用等待全部的依賴項被更新。我強烈推薦使用這種方法!函數
咱們添加了預提交鉤子,因此若是您編輯了一個文件,就會收到建議將 Python 3 所有進行 modernize 更新的提示。
quote_plus
的手動靜態分析: 在處理 quote_plus 和 six 上有一些細微差異。最後,咱們建立了本身的包裝器,默認代碼強制執行使用這個包裝器,而不是使用標準庫中的包裝器,也不使用 six 中包裝器。咱們還靜態檢查了您從未給 quote_plus 發送過的字節。
咱們修復了每一個 diango 應用程序中全部的 python 3 問題,並在 CI 環境中使用一個白名單強制執行了這一點,因此您沒法破壞一個曾經修復過的應用程序。
對於咱們來講,解決依賴是最困難的部分。咱們有不少依賴,因此花了不少時間,其中有兩個依賴關係比較棘手:
咱們的代碼測試覆蓋率大約有 65% 包括:單元、集成, 以及 UI 合併。 咱們確實編寫了更多的測試,但整體數量並無發生太大的變化。考慮將覆蓋率從 65% 提升到 66% ,意味着編寫將近2000 行代碼的測試,這一點也不奇怪。
咱們必須跳過須要 Cassandra 的測試,同時修復這個依賴項。 我發明了一個有趣的小 hack 來使它發揮做用, 並寫了這方面的文章.
關於代碼更改的說明,在如何將 py2 遷移到 six 的文檔中並未說起 (也許是咱們錯過了):
咱們在代碼中大量使用 StringIO 。第一反應就是使用 six。但對於 StringIO 來講,這在幾乎全部狀況下 (但不是所有!)都被證實是錯。基本上,咱們必須很是仔細地考慮每個咱們使用 StringIO 的地方,並試圖弄清楚咱們是否應該用 io.StringIO, io.BytesIO 或者 six.StringIO 來替代它。這裏犯錯的表現一般爲看起來像兼容 py3 的代碼準備好了,在 py2 中能夠正常運行,卻實際上在 py3 中是失效的。
這是一件好壞參半的事情。您能夠經過將它添加到許多文件中來發現 bug,可是有時會在 py2 中引入 bug。 當日志忽然在奇怪的地方,好比在字符串前寫"u"時,它也會變得使人困擾。總的來講,這顯然不是我所指望的效果。
這在很大程度上是您所指望的。我感到驚訝的是,在 py2 和 py3 中須要 str 。若是未來您使用 unicode_literals 導入,那麼一些字符串須要從 'foo'
修改成 str('foo')
。
six.moves 的實現是一個很是奇怪的黑客行爲,所以它不像它僞裝的普通 Python 模塊那樣運行。 我也不一樣意他們在 six.moves 中不包含 mock
的選擇。咱們必須使用他們的 API 來本身添加它,但這讓咱們很難開始工做,並且它要求咱們將 from mock import patch
改成 from six.moves import mock
這也意味着 patch
如今變成了 mock.patch
。
若是你使用 csv 模塊,你須要瞭解 csv342。在我看來,這應該是 six 的一部分。不然就意味着你沒有意識到有問題。不過咱們在許多地方都沒有使用 csv342,因此您這裏要作的工做可能會有所不一樣。
咱們首先進行測試:
接下來就是產品自己了。咱們創建一臺擁有能一次性切換到 py3 的能力的批處理機器,而且相當重要地是將其切換回來。當在 py3 上發生中斷時,這一點就顯得很重要了。這對咱們來講是很好的,由於咱們能夠從新排隊那些中斷的任務,可是咱們不能中斷太多或者任何其實是很關鍵的任務。咱們使用 Sentry 來收集奔潰日誌,因此很容易查看遷移到 py3 時遇到的全部問題,並且當咱們修復了全部的問題時,咱們須要再次遷移到 py3,直到咱們獲得一些問題,如此反覆。
咱們有以下環境:
咱們按照如下順序將 Python 3 發佈到這些環境中:
負載機器暴露了與 Python 3 不兼容的客戶數據配置,所以咱們必須在 Python 2 中實現對這些狀況的警告,並確保再次打開 Python 3 以前已經修復了它們。這花了幾天時間,由於咱們天天都會收到客戶數據,因此每次都會有一個警告,這又讓咱們不得再也不等一天。
'ß'.upper()
在 py2 中是 'ß'
可是在 py3 中是 'SS'
。當產品的最後一部分遷移到 py3 時,最終致使了產品的崩潰!None
的時候。總的來講,這是一個勝利,由於咱們發現了至關多的 bug 。 None
在 py2 的列表中排在第一位,這可能會讓人感到驚訝(您可能會指望它被排序到接近於零的地方!), 如今咱們只須要來處理它們。'{}'.format(b'asd')
在 Python 2 中是 'asd'
, 可是在 Python 3 中是 "b'asd'"
。在 Python 3 中,這裏幾乎任何其餘行爲都會更好: 輸出爲十六進制 ( 結果明顯更不同 ) ,舊的行爲 (以前的代碼運行),或者拋出異常 (最好的行爲!)。int('1_0')
在 py 3 中結果是 10 , 可是在 py2 中無效。這甚至在切換到 py3 以前就困擾了咱們。由於這種錯配致使了另外一個在咱們以前使用 py3 的團隊給咱們發送了咱們認爲無效而他們認爲有效的有效值。我我的認爲這個決定是錯誤的:很是嚴格的解析是更好的默認方式,我擔憂這將在將來幾年會繼續以微妙的方式困擾咱們。最後,咱們以爲在這件事上咱們真的別無選擇: Python 2 的維護將在某個時刻中止,咱們的依賴項僅限於 py3,最明顯的就是 Django。可是,不管如何,咱們仍是想要進行這種轉換,由於咱們常常會被 bytes/Unicode 問題困擾,而且Python 3 僅僅是修復了 Python 2 中的許多小麻煩。此次遷移過程,咱們已經在生產過程當中發現了一些實際的漏洞/錯誤配置。咱們也期待在任何地方均可以使用 f-string 和有序字典。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。