大約一個月前,我 tweet 了這條訊息:
想一想以下的情景:找到一群大型 Python 庫的維護人員,讓他們簽訂並宣告 2020 年不再對 Python 2.7 提供支援。
— Aaron Meurer (@asmeurer) 2016/3/22
編輯的話:有一些人已經開始這項工作,它在逐漸變成現實。欲知詳情,請參閱 https://python3statement.github.io/。
以防你們不知道,我要告訴你們,Python 2.7 將於 2020 年結束生命(原計劃在 2015 年結束,但因為遷移至新版本的難度過於巨大,在 2014 年得到延期)。“結束生命”意味著 Python 核心團隊不會再對其支援,甚至是安全性更新。
我寫下這篇博文是希望宣告我為什麼贊同這件事,並澄清一些誤解,其中最重要的一條誤解是,庫開發者想要與那些願意或必須使用 Python 2 的人作對。
我從一名庫開發者的角度,寫下這篇文章。我是 SymPy 的主要開發者,我對其他的庫開發者深表同情。1我這麼說是因為我的想法可能會讓“使用者”有些緊張(儘管我討厭這種“開發者/使用者”的區分)。
Python 2
有很多原因讓我認為庫應該在 2020 年放棄(並宣告將放棄)對 Python 2 的支援(實際上,這個時間點要提早一些,比如說 2018 或 2019 年,這取決於庫的重要程度)。
首先,這裡的庫開發者必須是主要開發者。從開始遷移至 Python 3 到現在來看,這點是顯而易見的。看看以下三類人(可能互有交集):CPython 核心開發者,庫開發者以及使用者。核心開發者必須是遷移至 Python 3 的領頭羊,因為他們是程式碼的編寫者。他們也提供了 Python 3 的相關資訊,這些資訊會隨著時間不斷變化。我認為,核心開發者應該是更有號召力的。2然後才是庫開發者和使用者。一個主要的區別在於,使用者可能只使用一個版本的 Python。為了讓他們從使用的版本轉移到 Python 3 上,他們使用的所有庫都必須支援它。這個過程需要時間,因為如果沒有人使用 Python 3,庫開發者就幾乎沒有動力去支援它(這看上去就像是一個悖論)。更糟糕的是,低於 Python 2.6 的版本中,單一程式碼庫的相容性簡直一團糟。
儘管如今幾乎所有的庫都支援 Python 3,同時那些不支援的庫也想要支援它,
但這一切都要在庫開發者遷移至 Python 3 後才會發生。我認為庫也需要率先逃離 Python 2,以下是一些重要的原因:
- Python 2.7 將於 2020 年結束生命。這代表了終止所有的更新,包括安全性更新。事實上到那時,Python 2.7 將成為一個不安全的語言。
- 要同時支援兩個主要版本的 Python,這對與每個專案而言都是技術負擔。儘管編寫跨版本相容性程式碼比以前要容易得多,你仍然得在每個檔案的頂部加入 __future__ imports,從你的相容檔案或庫中引入所有相關的 buildins,並在 Python 2/3 中執行所有的測試。對於庫開發者而言,同時支援兩個版本是一個巨大的認知上的負擔,因為他們必須瞭解兩種語言的重要區別。針對字串相關的庫開發者,需要知道其在 Python 2/3 的環境下如何運作。一般而言,隱晦的工作區需要進行協調工作(小測試:如何編寫 Unicode 字元以同時相容 Python 2/3?)。
- 一些 Python 3 的新語法特性(比如 Python 2 中無法使用的那些)只針對庫開發者。強制關鍵字引數 (keyword-only arguments) 就是一個極好的例子。站在 API 的角度,幾乎所有的關鍵字引數實體都應該以強制關鍵字引數執行。這避免了錯誤,由於未命名關鍵字而通過關鍵字引數的反模式所引起,並且允許將來在不破壞 API 的情況下,對函式的 argspec 進行擴充。3
第二個支援庫開發者在 2020 年前放棄 Python 2 的原因,則完全是個人因素。我聽到的對 tweet 的回覆中(包括別處),有一條是這樣說的,庫應該提供給人們蘿蔔(吸引人的事物,引申為好處),而非棍子。換言之,與其強迫人們放棄使用 Python 2,不如引誘他們使用 Python 3。這種言論本身具有一些爭議。首先,Python 3 已經具備了數不清的好處。平心而論,Python 3 自身對 Unicode 支援得還行就算是一個好處。4
如果你不處理字串,或者處理卻不關心帶著奇怪口音的愚蠢外國人的奇葩名字,它還有其他的好處。針對 SymPy,1/2 在Python 2 中的輸出為 0,這一點普遍讓新使用者感到困惑。想象寫下 1/2*x + x**(1/2)*y*z – 3*z**2,你會奇怪為什麼寫下的瞬間內容“消失”了一半(知足吧,這種情況在我們修復印表機前更加嚴重)。儘管 SymPy 中整數不能得出有理數是一個主要問題,但給出一個浮點數也絕對好過給出一個明顯錯誤的答案。不使用字串或整數?我這還有其他的好處。
坦誠得說,如果這些好處還沒有吸引到你,我敢打賭你一定不是那種能被好處所誘惑的人。
其次,有些好處必須依賴庫的執行才能獲得。有一些特性只能在同時相容 Python 2/3 的程式碼中,或只能在 Python 3 中實現(比如 @矩陣乘法),然而,還有一些特性比如強制關鍵字引數,只能在不支援 Python 2 的程式碼中實現。要在 Python 2 中支援它們會是一個技術負擔(比如,你可以想象試著支援強制關鍵字引數,通過人工使用 **kwargs,或利用一些可怕的超程式設計)。
第三點,就如我所說的,我是自私的。Python 3 確實有一些好處,這正是我想要的。只要我的程式碼支援 Python 2,我就無法使用強制關鍵字引數、擴充引數包、Async/Await、以及其他不支援跨版本相容程式碼的眾多特性。
可能有人要反駁,與其讓現有的庫阻礙使用者,開發者應當建立只支援 Python 3 的新庫,並充分利用 Python 3 那些激動人心的新特點。我也同意這樣的做法,但是,現有的庫也很棒。我不贊同開發者摒棄所有開發完善的庫,僅僅是為了使用他們感興趣的 Python 新功能。
傳統 Python
許多人將 Python 2 稱為“傳統 Python (Legacy Python)”,使用這個短語通常帶有幾分優越感,但它也令很多人惱火(這篇博文是我第一次使用這個詞)。然而,我認為確實如此,Python 2 是一個“傳統”系統。如果你想要使用它,不論出於什麼原因,都是可以的,但你卻無法使用 Python 最新的特性,更無法使用最新版本的庫。那些具備許多開發資源的庫可能會支援老舊的 Python 2,修復錯誤和/或安全性問題的相容版本。但 Python 2 自身的支援也只到 2020 年。而沒有資源的庫則不會支援(記住,開源的庫是不收費的)。
我理解一些人因為某些原因,必須使用 Python 2。但使用過時的軟體需要付出代價。迄今,大部分的庫都負擔著技術負擔,但這不是常態。這種負擔只會加重,特別是由於你不願意使用更新的、更好的 Python 3 版本,所帶來的技術機會成本的增長。在某個時間點,你必須轉移這種負擔。那些有財力資源的庫可能會將這種負擔轉移給別處,5比如說,通過將特性或錯誤修正移植到老舊的支援 Python 2 的庫(或通過幫助遷移程式碼到 Python 3)。
我想要指出,不論出於何種目的,如果你仍然在使用 Python 2,你可能會擔心如果庫突然只支援 Python 3,並開始使用新特性,是否會破壞你的程式碼?答案是不會。假設包維護者正確地標記了包上的原資料,類似 pip 和 conda 的工具將不會在 Python 2 上安裝非 Python 2 的相容版本。
如果你還沒有遷移至 Python 3,但想要多瞭解一點,官方文件是一個很好的開始。我同樣也強烈推薦你使用 conda 環境,它能輕易地將你的 Python 2 程式碼從 Python 3 程式碼中分離出來。
腳註
1、儘管這麼說,這完全是我個人的想法,並不代表其他人,也不代表 SymPy 的官方政策(現在論壇還沒有做出決定)。
2、感覺 Python 本身並沒有真正地想讓人使用 Python 3。拿小事來看,文件連結重導至 Python 2 或 PEP 394,這就像在說,Python 應該指的就是 Python 2。
3、Swift 作為蘋果針對 iOS 和 OS X 的新語言,函式引數名預設是“關鍵字限定”的。
4、以此為例,在 conda 中,如果你在根域環境中使用 Python 2,然後安裝到非 ASCII 字元的路徑中,這是不支援的。這在 Windows 中很常見,因為 Windows 預設使用使用者全名作為使用者名稱,而 conda 預設的安裝路徑為使用者目錄。
Python 3 則不會發生這種情況,因為為了修復這個問題,conda 中出現字串的每一個地方在 Python 2 中都必須改為 Unicode 字串。在 Python 2 中可能會出現類似 ‘π’ + u’i’ 得到 UnicodeDecodeError 這樣基本的情況(然而,’π’ + ‘i’, u’π’ + ‘i’ 和 u’π’ + u’i’ 就沒有問題)。你可以在這兒瞭解到對這個問題更詳細的描述。順帶一說,這也是我認為你永遠不應該在 Python 2 中使用 from __future__ import unicode_literals 的理由。
我已經不再用 conda 了,但是據我所知,這個問題仍未解決。當然,如果 conda 在 Python 3 中執行,它能正常工作。
5、如果你對此感興趣,我聽說 Continuum 也許能幫助到你。