本文出自「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
緣由以下:
除了這 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 的真值判斷過程並不簡單,能夠提煉出如下的幾個要點:
False
或者有__len__() 方法返回0
,不然布爾操做的結果都是 True。兩個魔術方法老是會先計算__bool__()若是你以爲本文分析得不錯,那你應該會喜歡這些文章:
四、Python 爲何沒有 main 函數?爲何我不推薦寫 main 函數?
六、Python 爲何不支持 i++ 自增語法,不提供 ++ 操做符?
七、Python 爲何只需一條語句「a,b=b,a」,就能直接交換兩個變量?
本文屬於「Python爲何」系列(Python貓出品),該系列主要關注 Python 的語法、設計和發展等話題,以一個個「爲何」式的問題爲切入點,試着展示 Python 的迷人魅力。全部文章將會歸檔在 Github 上,項目地址:https://github.com/chinesehuazhou/python-whydo