Python 爲何能支持任意的真值判斷?

本文出自「Python爲何」系列,請查看所有文章html

Python 在涉及真值判斷Truth Value Testing)時,語法很簡便。python

好比,在判斷某個對象是否不爲 None 時,或者判斷容器對象是否不爲空時,並不須要顯示地寫出判斷條件,只須要在 if 或 while 關鍵字後面直接寫上該對象便可。android

下圖以列表爲例,if my_list 這個簡短的寫法能夠表達出兩層意思:git

若是須要做出相反的判斷,即「若是爲 None 或爲空」,只須要寫成if not my_list 便可。github

不同凡響的真值判斷方式

一般而言,當一個值自己是布爾類型時,寫成"if xxx"(若是真),在語義上就很好理解。若是 xxx 自己不是布爾類型時,寫成「if xxx」(若是某東西),則在語義上並很差理解。微信

在 C/C++/Java 之類的靜態語言中,一般要先基於 xxx 做一個比較操做,好比「if (xxx == null)」,以此獲得一個布爾類型的值的結果,而後再進行真值判斷。不然的話,若「if xxx」中有非布爾類型的值,則會報類型錯誤。cookie

Python 這門動態語言在這種場景中表現出了一種靈活性,那麼,咱們的問題來了:爲何 Python 不須要先作一次比較操做,直接就能對任意對象做真值判斷呢?session

先來看看文檔 中對真值判斷的描述:app

簡單而言,Python 的任何對象均可以用在 if 或 while 或布爾操做(and、or、not)中,默認狀況下認爲它是 true,除非它有__bool__() 方法返回False 或者有__len__() 方法返回0函數

對於前面的例子,my_list 沒有__bool__() 方法,可是它有__len__() 方法,因此它是否爲 true,取決於這個方法的返回值。

真值判斷的字節碼

接着,咱們繼續刨根問底:Python 爲何能夠支持如此寬泛的真值判斷呢?在執行if xxx 這樣的語句時,它到底在作些什麼?

對於第一個問題,Python 有個內置的 bool() 類型,能夠將任意對象轉化成布爾值。那麼,這是否意味着 Python 在進行真值判斷時,會隱式地 調用 bool() 呢(即轉化成if bool(xxx))?(答案爲否,下文有分析)

對於第二個問題,能夠先用dis 模塊來查看下:

POP_JUMP_IF_FALSE指令對應的是 if 語句那行,它的含義是:

If TOS is false, sets the bytecode counter to target. TOS is popped.

若是棧頂元素爲 false,則跳轉到目標位置。

這裏只有跳轉動做的描述,仍看不到一個普通對象是如何變成布爾對象的。

Python 在解釋器中究竟是如何實現真值判斷的呢?

真值判斷的源碼實現

在微信羣友 Jo 的幫助下,我找到了 CPython 的源碼(文件:ceval.c、object.c):

能夠看出,對於布爾類型的對象(即 Py_True 和 Py_False),代碼會進入到快速處理的分支;而對於其它對象,則會用 PyObject_IsTrue() 計算出一個 int 類型的值。

PyObject_IsTrue() 函數在計算過程當中,依次會獲取 nb_bool、mp_length 和 sq_length 的值,對應的應該就是 __bool__() 和 __len__() 這兩個魔術方法的返回值。

這個過程就是前文中所引用的官方文檔的描述,正是咱們想要找的答案!

另外,對於內置的 bool(),它的核心實現邏輯正是上面的 PyObject_IsTrue() 函數,源碼以下(boolobject.c):

因此,Python 在對普通對象做真值判斷時,並無隱式地調用 bool(),相反它調用了一個獨立的函數(PyObject_IsTrue()),而這個函數又被 bool() 所使用。

也就是說,bool() 與 if/while 語句對普通對象的真值判斷,事實上是基本相同的處理邏輯。 知道了原理,就會明白if bool(xxx) 這種寫法是畫蛇添足的了(我曾見到過)。

至此,咱們已經回答了前文中提出的問題。

驗證真值判斷的過程

接下來,有 3 個測試例子,能夠做進一步的驗證:

你能夠暫停而思考下:bool(Test1)bool(Test1()) 各是什麼結果?而後依次判斷剩下的兩個類,結果又會是什麼?

揭曉答案:

bool(Test1)    # True
bool(Test2)    # True
bool(Test3)    # True

bool(Test1())  # True
bool(Test2())  # False
bool(Test3())  # True

緣由以下:

  • 類對象沒被實例化時,bool() 不會調用它的 __bool__() 或 __len__() 這兩個魔術方法
  • 類對象被實例化後,若同時存在 __bool__() 或 __len__() 魔術方法,則 bool() 會先調用 __bool__() 方法(PS:這個方法要求返回值必須爲 bool 類型,所以只要有它,就必然不須要再用__len__() 方法來判斷真假)

數字類型如何做真值判斷?

除了這 3 個例子,還有一種狀況值得驗證,那就是對於數字類型,它們是怎麼作真值判斷的呢?

咱們能夠驗證一下數字類型是否擁有那兩個魔術方法:

hasattr(2020, "__bool__")
hasattr(2020, "__len__")

不難驗證出,數字擁有的是 __bool__() 魔術方法,並無__len__() 魔術方法,並且全部類型的數字其實被分紅了兩類:

  • __bool__() 返回 False:全部表示 0 的數字,例如0, 0.0, 0j, Decimal(0), Fraction(0, 1)
  • __bool__() 返回 True:全部其它非 0 的數字

文章小結

Python 中if xxx 這種簡便的寫法,雖然是正規的真值判斷語法,並它但並不符合常規的語義。在 C/C++/Java 之類的語言中,要麼 xxx 自己是布爾類型的值,要麼是一種可返回布爾類型值的操做,可是在 Python 中,這個「xxx」居然還能夠是任意的 Python 對象!

本文經過對文檔、字節碼和 CPython 解釋器的源碼逐步分析,發現了 Python 的真值判斷過程並不簡單,能夠提煉出如下的幾個要點:

  • if/while 是隱性的布爾操做符: 它們除了有「判斷」真假的做用,還具備隱式地將普通對象計算出布爾結果的功能。實際的操做是解釋器根據「POP_JUMP_IF_FALSE」指令來完成的,其核心邏輯跟內置的 bool() 是共用了一個底層方法
  • 真值判斷過程依賴兩個魔術方法: 除非被判斷對象有__bool__() 方法返回False 或者有__len__() 方法返回0 ,不然布爾操做的結果都是 True。兩個魔術方法老是會先計算__bool__()
  • 數字類型也可作真值判斷: 數字有__bool__() 魔術方法,但沒有__len__() 魔術方法,除了表示 0 的數字爲 False,其它數字都爲 True

若是你以爲本文分析得不錯,那你應該會喜歡這些文章:

一、Python爲何使用縮進來劃分代碼塊?

二、Python 的縮進是否是反人類的設計?

三、Python 爲何不用分號做語句終止符?

四、Python 爲何沒有 main 函數?爲何我不推薦寫 main 函數?

五、Python 爲何推薦蛇形命名法?

六、Python 爲何不支持 i++ 自增語法,不提供 ++ 操做符?

七、Python 爲何只需一條語句「a,b=b,a」,就能直接交換兩個變量?

八、Python 爲何用 # 號做註釋符?

九、Python 爲何要有 pass 語句?

十、Python 爲何會有個奇怪的「...」對象?

本文屬於「Python爲何」系列(Python貓出品),該系列主要關注 Python 的語法、設計和發展等話題,以一個個「爲何」式的問題爲切入點,試着展示 Python 的迷人魅力。全部文章將會歸檔在 Github 上,項目地址:https://github.com/chinesehuazhou/python-whydo

相關文章
相關標籤/搜索