作者 | Łukasz Langa
譯者 | MjSeven ? ? ? ? 共計翻譯:35 篇 貢獻時間:96 天
從理論上來說,可以。Zed Shaw 說過一句著名的話,如果不行,那麼 Python 3 一定不是圖靈完備的。但在實踐中,這是不現實的,我將透過給你們舉幾個例子來說明原因。
對於字典(dict)來說,這意味著什麼?
讓我們來想象一臺擁有 Python 6 的虛擬機器,它可以讀取 Python 3.6 編寫的 module3.py
。但是在這個模組中,它可以匯入 Python 2.7 編寫的 module2.py
,併成功使用它,沒有問題。這顯然是實驗程式碼,但假設 module2.py
包含以下的功能:
def update_config_from_dict(config_dict):
items = config_dict.items()
while items:
k, v = items.pop()
memcache.set(k, v)
def config_to_dict():
result = {}
for k, v in memcache.getall():
result[k] = v
return result
def update_in_place(config_dict):
for k, v in config_dict.items():
new_value = memcache.get(k)
if new_value is None:
del config_dict[k]
elif new_value != v:
config_dict[k] = v
現在,當我們想從 module3
中呼叫這些函式時,我們遇到了一個問題:Python 3.6 中的字典型別與 Python 2.7 中的字典型別不同。在 Python 2 中,字典是無序的,它們的 .keys()
, .values()
, .items()
方法傳回了正確的序列,這意味著呼叫 .items()
會在字典中建立狀態的副本。在 Python 3 中,這些方法傳回字典當前狀態的動態檢視。
這意味著如果 module3
呼叫 module2.update_config_from_dict(some_dictionary)
,它將無法執行,因為 Python 3 中 dict.items()
傳回的值不是一個串列,並且沒有 .pop()
方法。反過來也是如此。如果 module3
呼叫 module2.config_to_dict()
,它可能會傳回一個 Python 2 的字典。現在呼叫 .items()
突然傳回一個串列,所以這段程式碼無法正常工作(這對 Python 3 字典來說工作正常):
def main(cmdline_options):
d = module2.config_to_dict()
items = d.items()
for k, v in items:
print(f'Config from memcache: {k}={v}')
for k, v in cmdline_options:
d[k] = v
for k, v in items:
print(f'Config with cmdline overrides: {k}={v}')
最後,使用 module2.update_in_place()
會失敗,因為 Python 3 中 .items()
的值現在不允許在迭代過程中改變。
對於字典來說,還有很多問題。Python 2 的字典在 Python 3 上使用 isinstance(d, dict)
應該傳回 True
嗎?如果是的話,這將是一個謊言。如果沒有,程式碼將無法繼續。
Python 應該神奇地知道型別並會自動轉換!
為什麼我們的 Python 6 的虛擬機器無法識別 Python 3 的程式碼,在 Python 2 中呼叫 some_dict.keys()
時,我們還有別的意思嗎?好吧,Python 不知道程式碼的作者在編寫程式碼時,她所認為的 some_dict
應該是什麼。程式碼中沒有任何內容表明它是否是一個字典。在 Python 2 中沒有型別註釋,因為它們是可選的,即使在 Python 3 中,大多數程式碼也不會使用它們。
在執行時,當你呼叫 some_dict.keys()
的時候,Python 只是簡單地在物件上查詢一個屬性,該屬性恰好隱藏在 some_dict
名下,並試圖在該屬性上執行 __call__()
。這裡有一些關於方法系結,描述符,slots 等技術問題,但這是它的核心。我們稱這種行為為“鴨子型別”。
由於鴨子型別,Python 6 的虛擬機器將無法做出編譯時決定,以正確轉換呼叫和屬性查詢。
好的,讓我們在執行時做出這個決定
Python 6 的虛擬機器可以標記每個屬性,透過查詢“來自 py2 的呼叫”或“來自 py3 的呼叫”的資訊來實現這一點,並使物件傳送正確的屬性。這會讓它變得很慢,並且使用更多的記憶體。這將要求我們在記憶體中保留兩種版本的程式碼,並透過代理來使用它們。我們需要加倍付出努力,在使用者背後同步這些物件的狀態。畢竟,新字典的記憶體表示與 Python 2 不同。
如果你已經被字典問題繞暈了,那麼再想想 Python 3 中的 Unicode 字串和 Python 2 中的位元組(byte)字串的各種問題吧。
沒有辦法了嗎?Python 3 根本就不能執行舊程式碼嗎?
不會。每天都會有專案移植到 Python 3。將 Python 2 程式碼移植到兩個版本的 Python 上推薦方法是在你的程式碼上執行 Python-Modernize[1]。它會捕獲那些在 Python 3 上不起作用的程式碼,並使用 six[2] 庫將其替換,以便它在 Python 2 和 Python 3 上執行。這是 2to3
的一個改編版本,用於生成僅針對 Python 3 程式碼。Modernize
是首選,因為它提供了更多的增量遷移路線。所有的這些在 Python 檔案中的 Porting Python 2 Code to Python 3[3]檔案中都有很好的概述。
但是,等一等,你不是說 Python 6 的虛擬機器不能自動執行此操作嗎?對。Modernize
檢視你的程式碼,並試圖猜測哪些是安全的。它會做出一些不必要的改變,還會錯過其他必要的改變。但是,它不會幫助你處理字串。如果你的程式碼沒有在“來自外部的二進位制資料”和“流程中的文字資料”之間保持界限,那麼這種轉換就不會那麼輕易。
因此,大專案的遷移不能自動完成,並且需要人類進行測試,發現問題並修複它們。它工作嗎?是的,我曾幫助將一百萬行程式碼遷移到 Python 3[4],並且這種切換沒有造成事故。這一舉措讓我們重新獲得了 1/3 的伺服器記憶體,並使程式碼執行速度提高了 12%。那是在 Python 3.5 上,但是 Python 3.6 的速度要快得多,根據你的工作量,你甚至可以達到 4 倍加速[5]。
親愛的 Zed
hi,夥計,我關註你已經超過 10 年了。我一直在觀察,當你感到沮喪的時候,你對 Mongrel 沒有任何信任,儘管 Rails 生態系統幾乎全部都在上面執行。當你重新設計它並開始 Mongrel 2 專案時,我一直在觀察。我一直在關註你使用 Fossil 這一令人驚訝的舉動。隨著你釋出 “Rails 是一個貧民窟”的帖子,我看到你突然離開了 Ruby 社群。當你開始編寫《笨方法學 Python》並且開始推薦它時,我感到非常興奮。2013 年我在 DjangoCon Europe[6] 見過你,我們談了很多關於繪畫,唱歌和倦怠的內容。你的這張照片[7]是我在 Instagram 上的第一個帖子。
你幾乎把另一個“貧民區”的行動與 “反對 Python 3” 案例[8] 文章拉到一起。我認為你本意是好的,但是這篇文章引起了很多混淆,包括許多人覺得你認為 Python 3 不是圖靈完整的。我花了好幾個小時讓人們相信,你是在開玩笑。但是,鑒於你對《笨方法學 Python》的重大貢獻,我認為這是值得的。特別是你為 Python 3 更新了你的書。感謝你做這件事。如果我們社群中真的有人因你的帖子為由要求將你和你的書列入黑名單,而請他們出去。這是一個雙輸的局面,這是錯誤的。
說實話,沒有一個核心 Python 開發人員認為 Python 2 到 Python 3 的轉換過程會順利而且計劃得當,包括 Guido van Rossum[9]。真的,可以看那個影片,這有點事後諸葛亮的意思了。從這個意義上說,我們實際上是積極地相互認同的。如果我們再做一次,它會看起來不一樣。但在這一點上,在 2020 年 1 月 1 日,Python 2 將會到達終結[10]。大多數第三方庫已經支援 Python 3,甚至開始釋出只支援 Python 3 的版本(參見 Django[11] 或 科學專案關於 Python 3 的宣告[12])。
我們也積極地就另一件事達成一致。就像你於 Mongrel 一樣,Python 核心開發人員是志願者,他們的工作沒有得到報酬。我們大多數人在這個專案上投入了大量的時間和精力,因此我們自然而然敏感[13]於那些對他們的貢獻不屑一顧和激烈的評論。特別是如果這個資訊既攻擊目前的事態,又要求更多的自由貢獻。
我希望到 2018 年會讓你忘記 2016 釋出的帖子,有一堆好的反駁。我特別喜歡 eevee[14](LCTT 譯註:eevee 是一個為 Blender 設計的渲染器)。它特別針對“一起執行 Python 2 和 Python 3 ”的場景,這是不現實的,就像在同一個虛擬機器中執行 Ruby 1.8 和 Ruby 2.x 一樣,或者像 Lua 5.3 和 Lua 5.1 同時執行一樣。你甚至不能用 libc.so.6 執行針對 libc.so.5 編譯的 C 二進位制檔案。然而,我發現最令人驚訝的是,你聲稱 Python 核心開發者是“有目的地”創造諸如 2to3 之類的破壞工具,這些由 Guido 建立,其最大利益就是讓每個人盡可能順利,快速地遷移。我很高興你在之後的帖子中放棄了這個說法,但是你必須意識到你會激怒那些閱讀了原始版本的人。對蓄意傷害的指控最好有強有力的證據支援。
但看起來你仍然會這樣做。就在今天[15]你說 Python 核心開發者“忽略”嘗試解決 API 的問題,特別是 six
。正如我上面寫的那樣,Python 檔案中的官方移植指南涵蓋了 six
。更重要的是,six
是由 Python 2.7 的釋出管理者 Benjamin Peterson 編寫。很多人學會了程式設計,這要歸功於你,而且由於你在網上有大量的粉絲,人們會閱讀這樣的推文,他們會相信它的價值,這是有害的。
我有一個建議,讓我們把 “Python 3 管理不善”的爭議擱置起來。Python 2 正在死亡,這個過程會很慢,並且它是醜陋而血腥的,但它是一條單行道。爭論那些沒有用。相反,讓我們專註於我們現在可以做什麼來使 Python 3.8 比其他任何 Python 版本更好。也許你更喜歡看外面的角色,但作為這個社群的成員,你會更有影響力。請說“我們”而不是“他們”。
via: http://lukasz.langa.pl/13/could-we-run-python-2-and-python-3-code-same-vm/
作者:Łukasz Langa[17] 選題:lujun9972 譯者:MjSeven 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出