1. 程式人生 > >Python 為什麼要在 18 年前引入布林型別?且與 C、C++ 和 Java 都不同?

Python 為什麼要在 18 年前引入布林型別?且與 C、C++ 和 Java 都不同?

> 花下貓語:在上一篇《[Python 為什麼能支援任意的真值判斷?](https://mp.weixin.qq.com/s/g6jZX0IdH9xpM7BMV3-ToQ) 》文章中,我們分析了 Python 在真值判斷時的底層實現,可以看出 Python 在對待布林值時,採用了比較寬泛的態度。官方對此是怎麼考慮的呢? > > 下面的文章是我剛翻譯的 PEP-285,作者是 Python 之父 Guido van Rossum。這個 PEP 意義非常重大,Python 的 bool 型別就是從它開始引入的,而我在上篇文章中分析到的很多問題,都能在這篇十幾年前的文件中找到解釋!另外它還回應了比較典型的一些爭議,值得大家瞭解下。 **PEP原文 :** [https://www.python.org/dev/peps/pep-0285/](https://www.python.org/dev/peps/pep-0285/) **PEP標題:** PEP 285 -- Adding a bool type **PEP作者:** Guido van Rossum **建立日期:** 2002-03-08 **合入版本:** 2.3 **譯者** :豌豆花下貓@Python貓公眾號 **PEP翻譯計劃** :https://github.com/chinesehuazhou/peps-cn ## 概要 本 PEP 提議引入一個新的內建型別`bool`,它將包含兩個常量`False`和`True`。這個 bool 型別是 int 型別的直接子型別(在 C 中),並且在除了 repr() 和 str() 之外的大多數方面,它的值`False`和`True` 都將表現得像是 0 和 1(例如,False == 0 和 True == 1 都為真)。 所有在概念上需返回布林結果的內建操作,都將更改為返回 False 或 True,而不再是 0 或 1,例如,比較操作、“not”運算和 isinstance() 之類的斷言方法。 ## 評審 我已經收集了太多太多的反饋意見,因此我宣佈:評審階段(review period)正式**結束。** 我今天吃的是中國菜,我的籤語餅上寫著:“Strong and bitter words indicate a weak cause.” 它使我想起了一些反對本 PEP 的帖子... :-) *(譯註:1、籤語餅即 fortune cookies,這是一種美國文化特色。美國的中餐館在結賬的時候流行給客人一些寫了籤語的餅乾,一般都是祝福語。2、那句籤語出自維克多·雨果,意為:理虧者言辭激烈)* 無論如何,這些是我的 BDFL 宣告。(執行摘要(Executive summary):我不會更改任何內容;所有其它提議都會被拒絕。) **1、本 PEP 應該被接受嗎?** =>是的。 有很多反對本 PEP 的觀點。其中多數是出於誤解。我已嘗試在下面的 PEP 正文中澄清一些最常見的誤解。對我而言唯一值得考慮的問題是新手們傾向於寫“ if x == True”,但“if x”就足夠了。下面也有更多關於它的資訊。我認為這不足以拒絕本 PEP。 **2、`str(True)` 應該返回“True”還是“1”?“1”可能會減少向後相容性問題,但看起來很奇怪。(`repr(True)` 將始終返回“True”。)** =>“True”。 幾乎所有評審人都同意這一點。 **3、常量應該被命名為“True”和“False”(類似於 None)還是“true”和“false”(像 C++、Java 和 C99 那樣)?** =>True 和 False。 大多數評審人都認為 Python 內的一致性要比跟其它語言的一致性更為重要。 **4、是否應該通過適當的告警來消除對布林值的非布林運算,以便例如 True + 1 最終(在 Python 3000 中)變為非法的?** =>不該 有一小部分觀點響亮的人,希望看到“教科書式”的布林型別,即完全不支援算術運算,但大多數評審人都同意我,認為布林型別應該支援算術運算。 **5、`operator.truth(x)` 應該返回 int 還是 bool? ** =>bool。 Tim Peters 認為應該返回一個整數,但是幾乎所有其他評審人都認為應該返回一個布林值。我的理由:operator.truth() 意味著強制其引數使用布林型別上下文(它呼叫 C API `PyObject_IsTrue()`)。無論結果是 int 還是 bool,都是次要的;如果有 bool,則沒有理由不使用它。(在本 PEP 下,operator.truth() 成為了 bool() 的別名;這也可以。) **6、bool 應該繼承自 int 嗎?** =>是的。 在理想的情況下,bool 最好是實現為一種單獨的整數型別,且支援執行混合的算術操作。但是,從 int 繼承出 bool 將極大地簡化實現(部分原因是,所有呼叫`PyInt_Check()` 的 C 程式碼都可相容——它對於 int 的子類會返回 true)。 另外,我認為這符合可替換性(substitutability)概念:程式碼中需要 int 時,可以喂入 bool,它等同於 0 或 1。程式碼中需要 bool 時,若賦予 int,則可能不符合預期;例如,3&4 計算為 0,但是當 3 和 4 被視為真值時,卻都為真。 **7、是否應該改變“bool”的叫法?** =>不。 一些評審人主張使用 boolean 而不是 bool,因為這樣更容易理解(新手可能聽說過布林代數(Boolean algebra),但可能對 bool 無感),或者因為他們討厭縮寫。 我的觀點:Python 明智地運用縮寫(例如'def'、'int'、'dict'),我不認為這會造成理解的負擔。對於新手來說,無論它被叫作 waffle 還是 bool 都沒關係;這只是一個新詞,他們很快就能掌握它的含義。 *(譯註:waffle,我們一般熟知的意思是“華夫餅乾”,但它還有個意思是“無意義的、無關緊要的、胡亂的話”)* 一位評審人認為可以叫“truth”。我覺得這個叫法沒有吸引力,實際上更傾向於保留該術語(在文件中),以指代在 Python 中已經存在的具體的真值概念。例如:“當將一個容器解釋為一個 truth 值時,空容器會被視為假,而非空容器則被視為真”。 **8、將來是否應該要求布林運算子(例如“if”、“and”和“not”)使用一個布林值作為引數,例如令“if []:”變為非法的,要求必須寫成“ if bool([]):” ???** =>不!!! 有些人認為,這就是一門有教科書式布林型別的語言應該的做法。因為它被提起了,所以其他人擔心我可能會同意這一做法。 我來明確闡述對此的立場:這不是本 PEP 的動機,我也無意進行更改。(另請參見下面的“澄清”部分。) ## 基本原理 大多數語言最終都會發展出一個布林型別,甚至 C99(新的改進版 C 標準,尚未廣泛採用)也有一個。*(譯註:C99 標準誕生於 1999 年,本 PEP 寫於 2002 年,時過境遷,如今 C99 標準基本上已是落伍的了)* 許多程式設計師都覺得需要一種布林型別,大多數 Python 文件因缺少布林型別而含有歉意。我看過很多模組,它們在頂部定義了常量“False = 0”和“True = 1”(或類似的常量),並使用它們。 問題是每個人的做法都不一樣。例如,你應該使用“FALSE”、“false”、“False”、“F”還是“f”呢?另外,假值應該為 0 或 None,或是一個其它的布林型別打印出“true”或“false”呢?在語言中新增一個標準的布林型別可以解決這些問題。 一些外部庫(例如資料庫和 RPC 相關的包)需要能夠區分佈爾值和整數值,儘管通常可以制定出解決方案,但如果語言本身提供了標準的布林型別,則會更容易。這也適用於 Jython:某些 Java 類具有分別用於 int 和 boolean 引數的過載方法或建構函式。布林型別可用於選擇布林變數。(顯然,某些 COM 介面也是如此。) 標準的布林型別(bool type)也可以作為強制將值解釋為布林值(Boolean)的方法,該方法可用於標準化布林值。當一個布林值需要歸一化為兩個值之一時,bool(x) 比“not not x”更清晰,也比這種寫法更簡潔: ```python if x: return 1 else: return 0 ``` 這是從傳授 Python 中得出的一些經驗。當向人們在互動式終端中展示比較運算子時,我認為這有點難看: ```python >>> a = 13 >>> b = 12 >>> a > b 1 >>> ``` 如果是這樣的話: ```python >>> a > b True >>> ``` 每次會少花一毫秒的時間思考打印出的 0 或 1。 還有一個問題(它甚至困擾了曾經經驗豐富但遠離了 Python 一段時間的人): ```python >>> cmp(a, b) 1 >>> cmp(a, a) 0 >>> ``` 你可能會傾向於認為 cmp() 也返回一個布林值,但實際上它可以返回三個不同的值(-1、0、1)。如果整數沒有(通常)被用於表示布林值結果,則這可以更加明顯地表達出其它的含義。*(譯註:即只用 True/False 表示布林值,則整數表達其它含義時就不會有歧義)* ## 規範 以下 Python 程式碼詳細列舉了新型別的大多數屬性: ```python class bool(int): def __new__(cls, val=0): # This constructor always returns an existing instance if val: return True else: return False def __repr__(self): if self: return "True" else: return "False" __str__ = __repr__ def __and__(self, other): if isinstance(other, bool): return bool(int(self) & int(other)) else: return int.__and__(self, other) __rand__ = __and__ def __or__(self, other): if isinstance(other, bool): return bool(int(self) | int(other)) else: return int.__or__(self, other) __ror__ = __or__ def __xor__(self, other): if isinstance(other, bool): return bool(int(self) ^ int(other)) else: return int.__xor__(self, other) __rxor__ = __xor__ # Bootstrap truth values through sheer willpower False = int.__new__(bool, 0) True = int.__new__(bool, 1) ``` False 和 True 將是單例的(singletons),像 None 一樣。因為這種型別有兩個值,也許應該將它們稱為“doubletons”?實際的實現將不允許建立 bool 的其它例項。 True 與 False 會被正確地序列化和打包,例如 pickle.loads(pickle.dumps(True)) 將返回 True, 而marshal.loads(marshal.dumps(True)) 也一樣。 所有在定義上需返回布林結果的內建操作,都將更改為返回 False 或 True,而不再是 0 或 1。 具體而言,這會影響比較操作(<、<=、==、!=、>、>=、is、is not、in、not in),一元運算子'not',內建函式 callable()、hasattr()、isinstance() 和issubclass() ,字典方法 has_key() ,字串和 unicode 方法 endswith()、isalnum()、isalpha()、isdigit()、islower()、isspace()、istitle()、isupper() 和startswith(),unicode方法 isdecimal() 和 isnumeric(),以及檔案物件的“closed”屬性。operator 模組中的斷言方法也被改為返回布林值,包括operator.truth()。 由於 bool 繼承自 int,因此 True + 1有效且等於 2,依此類推。這對於向後相容性很重要:因為比較之類的操作當前返回整數值,所以無法確定現有應用程式怎麼使用這些值。 預計隨著時間的推移,標準庫將在適當的時候更新為使用 False 和 True (但在以前允許使用 int 的場合,則不需要使用 bool 引數型別)。此更改不應引起在本 PEP 中未詳細說明的其它問題。 ## C API “boolobject.h”標頭檔案為布林型別定義了 C API。它包含在“Python.h”中,因此不需要再 include 它。 現有的名稱 Py_False 和 Py_True 引用獨一無二的布林物件 False 和 True (之前,它們分別引用了值為 0 和 1 的靜態整數物件,是眾多整數之一)。 一個新的 API,即`PyObject *PyBool_FromLong(long)` ,會接收一個 C 長整型引數,並返回對 Py_False (當引數為零時)或 Py_True (當非零時)的新引用。 要檢查物件是否為布林物件,可以使用巨集 PyBool_Check()。 布林例項的型別是 PyBoolObject *。 布林型別物件可作為 PyBool_Type 使用。 ## 澄清 本 PEP 沒有改變一個事實,即幾乎所有型別的物件都可以用作真假值。例如,在 if 語句中使用時,一個空列表為 false,一個非空列表為 true;這不會改變,而且也不打算改變。 唯一改變的是在返回或賦值時,用於表示真假值的首選值。以前,這些首選的真假值是 1 和 0;本 PEP 將首選值更改為 True 和 False,並修改內建操作以返回這些首選值。 ## 相容性 因為要向後相容,所以布林型別擁有一些不嚴格的屬性。例如,允許使用布林引數進行算術運算,即將 False 視為 0,將 True 視為 1。而且,可以將 bool 用作序列物件的索引。 我不認為這是一個問題,也不希望朝這個方向發展語言。我認為,對“布林性(Booleanness)”的更嚴格的解釋不會使語言更清晰。 相容性要求的另一個結果是表示式“True and 6”的值為 6,類似地,表示式“ False or None”的值為 None。 “and”和“or”運算子被設計來返回第一個決定了結果的引數,這點不會改變;特別地,它們不強制要求結果為布林型別。當然,如果兩個引數都是布林值,那麼結果肯定是一個布林值。通過寫“bool(x and y)”,也可以很容易地將其強制轉成布林型別。 ## 解決了的問題 (另請參見上面的“評審”部分。) - 由於 bool 值的 repr() 或 str() 與 int 值不同,因此某些程式碼(例如,基於doctest 的單元測試,以及可能依賴於 “%s”%truth 的資料庫程式碼)可能會出錯。解決這個問題很容易(無需顯式引用 bool 型別),並且預計這隻會影響非常少量的可以輕鬆修復的程式碼。 - 其它語言(C99、C ++、Java)均以小寫形式命名常量“false”和“true”。對於Python,我更喜歡遵照現有內建常量的慣例,這些內建常量全部使用駝峰式命名:None 、Ellipsis、NotImplemented (以及所有的內建異常)。Python 內建的名稱空間全部用小寫字母表示函式和型別。 - 前面提到過,為了滿足使用者的期望,對於在布林上下文中被認為是真的每個 x,`x == True` 表示式都應該為真,同樣,如果 x 被認為是假,則`x == False` 也應該為真。那些剛瞭解布林變數的新手可能會寫: ```python if x == True: ... ``` 而不是正確的形式: ```python if x: ... ``` 許多人乍一看會對後一種形式感到不舒服,這在心理和語言上似乎有很強的理由,但是我認為解決辦法應該是教育而不是削弱語言。 畢竟,== 通常被視為傳遞符號,這意味著根據 a == b 和 b == c,可以推論出 a == c。但是,如果在一個數是真值的情況下,它與 True 進行比較的結果是相等的,則像 6 == True == 7 這樣的暴行將成立,從而可以推斷出錯誤的 6 == 7。那是不可接受的。(此外,它會破壞向後相容性。但是,即使它不破壞,出於前面的原因,我仍然反對。) 還應該提醒新手,沒有理由寫: ```python if bool(x): ... ``` 因為布林值隱含在“if”中。在這裡,顯式並**不** 比隱式好,因為新增的詞法會損害可重用性,並且限制瞭解釋器的解釋行為。*(譯註:”The Zen of Python“中認為”顯式比隱式好“,但在這裡,Guido 認為隱式更好,所以他在原文件中加粗了”not“)* 但是,有時候有理由寫成: ```python b = bool(x) ``` 當不需要保留對任意 x 物件的引用時,或者由於某些其它原因需要規範化時,這很有用。有時候這樣寫也很合適: ```python i = int(bool(x)) ``` 它將布林值轉換為整數的 0 或 1。傳達了將該值用作 int 的意圖。 ## 實現 完整的 C 實現程式碼已上傳到 SourceForge 補丁管理器:https://bugs.python.org/issue528022 它將很快被合入到 python 2.3a0 的 CVS 中。 ## 版權 本文件已進入公共領域。 源文件:https://github.com/python/peps/blob/master/pep-0285.txt 更多的 PEP 中文翻譯內容,可在 Github 查閱:https://github.com/chinesehuazhou