https://www.toutiao.com/i6606214656228852232/
Python 2 vs Python 3,究竟誰(shuí)是性能之王?前段時(shí)間,Hackermoon 上一位叫 Anthony Shaw 的作者為我們做了一些測試,最終得出結論,雖然 Python 2 在加密和啟動(dòng)時(shí)間測試過(guò)程中,比 Python 3 的速度更勝一籌,但整體而言,Python 3 更快。而這是否就意味著(zhù)我們還是將項目代碼遷移到 Python 3.0 的好?接下來(lái),本文來(lái)自全球著(zhù)名的桌面應用之一的 Dropbox 將分享他們要棄用 Python 2.0 的真實(shí)原因,以及如何將百萬(wàn)行的代碼成功遷移至 Python 3。
Dropbox 是世界上流行的桌面應用之一,你可以安裝在 Windows、macOS 和部分的 Linux 發(fā)行版上。但你可能不知道,這個(gè)應用大部分是用 Python 寫(xiě)的。實(shí)際上,Drew 給 Dropbox 寫(xiě)下的第一行代碼就是用的 Windows 版 Python,用的是老牌的 pywin32 等庫。
雖然我們靠著(zhù) Python 2 支撐了這么多年(我們用過(guò)的最新版本是 Python 2.7),但我們從 2015 年就開(kāi)始向 Python 3 轉換了。今天我們終于完成了轉換,你現在再裝 Dropbox 的話(huà),那么它用的是 Dropbox 定制版本的 Python 3.5。本文將介紹這次史無(wú)前例的 Python 3 轉換的計劃、執行和發(fā)布過(guò)程。
為什么選擇 Python 3?
Python 3 的接受度在 Python 社區一直是熱門(mén)話(huà)題?,F在雖然 Python 3 已經(jīng)廣為接受(http://py3readiness.org/),一些非常流行的項目如 Django 甚至完全放棄了 Python 2 的支持,但這個(gè)話(huà)題的熱度依然存在。對于我們來(lái)說(shuō),影響我們決定進(jìn)行轉換的幾個(gè)關(guān)鍵因素有:
引人入勝的新功能
Python 3 的創(chuàng )新十分迅速。除了一長(cháng)列(http://whypy3.com/)正常的改進(jìn)(如 str 和 bytes 的討論),還有幾個(gè)功能吸引了我們的眼球:
過(guò)老的工具鏈
隨著(zhù) Python 2 日久年深,最初適合部署的工具鏈也大部分過(guò)時(shí)了。由于這些因素,繼續使用 Python 2 會(huì )帶來(lái)一系列的維護負擔:
凍結和腳本
當初,我們依靠“凍結”腳本為我們支持的每個(gè)平臺創(chuàng )建原生應用程序。但是,我們并沒(méi)有直接使用原生的工具鏈,如 macOS 的 Xcode,而是將創(chuàng )建各個(gè)平臺上的二進(jìn)制文件的任務(wù)交給其他程序去做,Windows 下是 py2exe,macOS 下是 py2app,Linux 下是 bbfreeze。這個(gè)完全面向 Python 的構建系統收到了 distutils 的啟發(fā),因為我們的應用最初只不過(guò)是個(gè) Python 包,所以只需要一個(gè)類(lèi)似于 setup.py 的腳本來(lái)構建。
隨著(zhù)時(shí)間的流逝,我們的代碼量越來(lái)越大?,F在,我們的開(kāi)發(fā)已經(jīng)不僅僅使用 Python 開(kāi)發(fā)了。實(shí)際上,我們的代碼現在由 TypeScript/HTML、Rust 和Python 混合組成,某些平臺上還用了 Objective-C 和 C++。為支持所有組件,setup.py 腳本(內部的名字為 build-all.py)越來(lái)越大,越來(lái)越難以管理。
導火索就是我們與各個(gè)操作系統集成的方式。首先,我們越來(lái)越多地引入高級的 OS 擴展,如 Smart Sync 的內核組件等,這些組件不能,通常也不會(huì )使用 Python 編寫(xiě)。其次,像微軟和蘋(píng)果等供應商對部署應用提出了新的需求,因此經(jīng)常需要用到新的、更復雜的工具,這些工具經(jīng)常是這些供應商獨有的(比如代碼簽名等)。
例如在 macOS 上,10.10 版本引入了新的應用擴展以便與 Finder 進(jìn)行集成,就是FinderSync(https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Finder.html)。它并不只是個(gè) API,而是個(gè)完整的應用程序包(.appex),有自己的生存中崛起規則(即它由 OS 啟動(dòng)),而且對于進(jìn)程間通信的要求更嚴格。換句話(huà)說(shuō),使用 Xcode 就很容易集成這些擴展,但 py2app 根本不支持它們。
因此,我們面臨著(zhù)兩個(gè)問(wèn)題:
當我們計劃轉換成 Python 3 時(shí),我們面臨著(zhù)兩個(gè)選擇:一是改進(jìn)凍結腳本中的依賴(lài),以支持 Python 3(從而支持現代編譯器)和平臺相關(guān)的功能(如應用程序擴展),二是不再使用以 Python 為中心的構建系統,完全放棄凍結腳本。我們選擇了后者。
關(guān)于 pyinstaller 的一點(diǎn):我們認真地思考過(guò)在項目早期使用 pyinstaller,但當時(shí)它不支持 Python 3,而且更重要的是,它和其他凍結腳本有類(lèi)似的限制。不管怎樣,這個(gè)項目本身很不錯,我們只是覺(jué)得不適合我們而已。
嵌入 Python
為了解決構建和部署的問(wèn)題,我們決定使用新的架構,在原生應用中嵌入 Python 運行時(shí)。我們不再將構建過(guò)程交給凍結腳本處理,而是使用各個(gè)平臺自己的工具鏈(比如 Windows 下使用 Visutal Studio)來(lái)構建各種入口點(diǎn)。進(jìn)一步,我們將 Python 代碼抽象到一個(gè)庫中,從而為多種語(yǔ)言“混合”的方式提供更直接的支持。
這樣我們就可以直接使用各個(gè)平臺的 IDE 和工具鏈了(例如可以直接添加原生的構建目標,如 macOS 上的 FinderSync),同時(shí)保留使用 Python 編寫(xiě)大部分應用程序邏輯的能力。
我們最后采用了下面的結構:
表面上,這個(gè)應用能夠更接近平臺的要求,而在各個(gè)庫的背后,我們可以有更大的靈活性來(lái)選擇自己喜歡的語(yǔ)言和工具。
這種架構能提高模塊性,同時(shí)還帶來(lái)一個(gè)關(guān)鍵的副作用:現在可以同時(shí)部署 Python 2 庫和 Python 3 庫了。聯(lián)系到 Python 3 轉換工作,我們的轉換過(guò)程就需要兩步:第一,給 Python 2 實(shí)現新的架構;第二,利用它將 Python 2 替換成 Python 3。

第一步:“解凍”
第一步就是停止使用凍結腳本。目前,bbfreeze 和 pywin32 都不支持 Python 3,所以我們別無(wú)選擇。我們從 2016 年開(kāi)始逐步進(jìn)行這項改變。
首先,我們將配置 Python 運行時(shí)的工作抽象化,將 Python 的東西放到一個(gè)新的庫中,名為 libdropbox_bootstrap。這個(gè)庫會(huì )代替一些凍結腳本提供的功能。盡管我們不再需要這些腳本,但它們仍然提供了一些運行 Python 代碼所需的最基本的東西:
打包代碼以便在設備上執行
這樣我們才能發(fā)布編譯好的 Python 字節碼,而不用發(fā)布 Python 源代碼。由于以前的每個(gè)凍結腳本在各個(gè)平臺上有各自的格式,我們利用這個(gè)機會(huì )引入了一種新的格式,用于在所有平臺上打包代碼使用:
隔離 Python 解釋器
這樣能阻止我們的應用程序在設備上運行其他的 Python 源代碼。有意思的是,Python 3 使得這種嵌入變得容易得多了。例如,新的 Py_SetPath 函數(https://docs.python.org/3/c-api/init.html#c.Py_SetPath)能夠讓我們將代碼隔離,不需要再像 Python 2 時(shí)代在凍結腳本中進(jìn)行某種復雜的隔離操作了。為了在 Python 2 中支持這一功能,我們在定制版本的 Python 2 中向下移植了這一功能。
其次,我們使用了平臺相關(guān)的入口點(diǎn)Dropbox.exe、Dropbox.app和dropboxd 來(lái)使用這個(gè)庫。這些入口點(diǎn)都是用各自平臺的“標準”工具編譯的,即 Visual Studio、Xcode 和 make,沒(méi)有使用 distutils。這樣我們就可以去掉凍結腳本帶來(lái)的大量修補工作了。例如,在 Windows 下,這一步大大簡(jiǎn)化,只需為 Dropbox.exe 配置 DEP/NX 即可,就能將應用程序裝箱單和資源嵌入了。
關(guān)于 Windows 的一點(diǎn)說(shuō)明:現在,繼續使用 Visual Studio 2008 的代價(jià)已經(jīng)非常高了。為了正確地轉換,我們需要一個(gè)能同時(shí)支持 Python 2 和 Python 3 的版本,最終我們采用了 Visual Studio 2013。為支持它,我們進(jìn)一步修改了定制版本的 Python 2,使之能正確在 Visual Studio 2013 下編譯。這些修改的代價(jià)進(jìn)一步證明,我們轉換到 Python 3 的決定是正確的。

第二步:混合
成功地轉換如此之大(包含大約 100 萬(wàn)行 Python 代碼)、安裝量如此之高(大約有幾億安裝)的應用程序需要逐步進(jìn)行。我們不能簡(jiǎn)單地在某次發(fā)布中“改變一個(gè)開(kāi)關(guān)”來(lái)實(shí)現轉換,特別是我們的發(fā)布過(guò)程要求每?jì)蓚€(gè)星期給所有用戶(hù)發(fā)布一個(gè)新版本。因此,必須找到一種辦法,將 Python 3 的部分轉換發(fā)布給一小部分用戶(hù),以便檢測并修改 Bug。
為達到這一點(diǎn),我們決定實(shí)現用 Python 2 和 Python 3 同時(shí)編譯 Dropbox。這要求做到以下兩點(diǎn):
我們采用上一步引入的嵌入式設計來(lái)實(shí)現:將 Python 代碼抽象到庫和包中,就能很容易地引入另一個(gè)版本。這樣入口點(diǎn)程序(即 Dropbox.exe)就可以在初始化的早期控制選擇哪個(gè) Python 版本了。
我們通過(guò)手動(dòng)連接入口點(diǎn)程序到 libdropbox_bootstrap 來(lái)實(shí)現這一點(diǎn)。例如在 macOS 和 Linux 下,我們在 Python 版本確定之后使用 dlopen/dlsym 來(lái)加載。在 Windows 下,使用 LoadLibrary 和 GetProcAddress。
對 Python 解釋器的選擇必須在 Python 加載之前完成,因此為了使之更順暢,我們實(shí)現了命令行參數 /py3 用于開(kāi)發(fā),和一個(gè)保存在硬盤(pán)上的永久設置,以便通過(guò)我們的功能切換系統Stormcrow(https://blogs.dropbox.com/tech/2017/03/introducing-stormcrow/)來(lái)控制。
有了這些,我們就能在啟動(dòng) Dropbox 客戶(hù)端時(shí)動(dòng)態(tài)選擇 Python 版本了。這樣就可以在 CI 基礎設施中設置額外的任務(wù)來(lái)針對 Python 3 運行單元測試和集成測試。我們還在提交隊列中增加了自動(dòng)檢查,以防止提交會(huì )破壞 Python 3 支持的改動(dòng)。
通過(guò)自動(dòng)測試確保沒(méi)問(wèn)題之后,我們就開(kāi)始將 Python 3 的改動(dòng)推送給真正的用戶(hù)。我們通過(guò)遠程的功能開(kāi)關(guān)來(lái)將新功能逐漸開(kāi)放給用戶(hù)。首先對 Dropbox 推送改動(dòng),這樣我們就能找出并改正大部分主要的底層問(wèn)題。然后將范圍擴大到 Beta 用戶(hù),他們的 OS 版本問(wèn)題更加蕪雜。然后最終擴展到穩定版。7 個(gè)月之后,所有的 Dropbox 都已經(jīng)在運行 Python 3 了。為了盡可能提高質(zhì)量,我們要求所有與轉換相關(guān)的 bug 必須進(jìn)行深入調查并徹底修復,才能擴大推送的范圍。

逐漸推送到 Beta 版

逐漸推送到穩定版
到了版本 52 時(shí),這個(gè)過(guò)程終于完成了。我們可以完全從 Dropbox 的桌面客戶(hù)端中刪掉 Python 2 了。

寫(xiě)在最后
一篇文章很難完整概括我們將代碼遷移至 Python 3.0 的完整過(guò)程,這其中還有許多可以討論的東西。接下來(lái),我們還會(huì )在以后的文章中討論:
敬請期待,也歡迎在下方留言分享你對遷移過(guò)程的看法。
原文:https://blogs.dropbox.com/tech/2018/09/how-we-rolled-out-one-of-the-largest-python-3-migrations-ever/作者:Max Bélanger和Damien DeVille譯者:彎月,責編:屠敏
“征稿啦”
CSDN 公眾號秉持著(zhù)「與千萬(wàn)技術(shù)人共成長(cháng)」理念,不僅以「極客頭條」、「暢言」欄目在第一時(shí)間以技術(shù)人的獨特視角描述技術(shù)人關(guān)心的行業(yè)焦點(diǎn)事件,更有「技術(shù)頭條」專(zhuān)欄,深度解讀行業(yè)內的熱門(mén)技術(shù)與場(chǎng)景應用,讓所有的開(kāi)發(fā)者緊跟技術(shù)潮流,保持警醒的技術(shù)嗅覺(jué),對行業(yè)趨勢、技術(shù)有更為全面的認知。如果你有優(yōu)質(zhì)的文章,或是行業(yè)熱點(diǎn)事件、技術(shù)趨勢的真知灼見(jiàn),或是深度的應用實(shí)踐、場(chǎng)景方案等的新見(jiàn)解,歡迎聯(lián)系 CSDN 投稿,聯(lián)系方式:微信(guorui_1118,請備注投稿+姓名+公司職位),郵箱(guorui@csdn.net)。
聯(lián)系客服