空虛的真
來一個我自己覺得滿有趣的 Python 程式問答題。
在 Python 裡,all()
這個函數可以接一個串列,串列裡的每一顆元素都成立的時候就會回傳 True
,反之只要有一顆不成立,就會是 False
,例如:
>>> all([True, True, True])
True
>>> all([True, False, True])
False
題目來了:
>>> all([]) # A
>>> all([[]]) # B
>>> all([[[]]]) # C
一堆中括號、小括號,眼睛都快看花了,在正式的專案千萬不要這樣整自己(或是你的同事)!大家猜猜看 A、B、C 的結果分別是什麼?
答案是 True
/ False
/ True
首先,我們得先看看 all()
這個函數的設計,當如果裡面所有的元素都是 True
的時候會得到 True
,但文件上跟原始碼裡都有一行但書:
If the iterable is empty, return True.
也就是說如果是 all([])
的話,會得到 True
,至於為什麼是這樣的結果我們待會再看。
第二題,因為在 Python 裡空的串列 []
會被當做 False
看待,所以 all([[]])
相當於 all([False])
,所以答案是 False
。
最後一題,[[]]
是一個串列,它是一個包含空串列的串列,但它本身不是一個空的串列。有點像是箱子裡面裝了一個箱子,就算內層的箱子是空的,對外層的箱子來說它也是有裝東西的,就是裝了你這個空箱子!
對於非空串列,在 Python 會被當做 True
,所以 all([[[]]])
等於是 all([True])
的效果,答案會得到 True
。繼續延伸下去,all([[[[[[[[]]]]]]]])
就算裡面包再多層次,答案也是 True
。
真空?空虛?
這裡比較難理解的可能是為什麼 all([])
是 True
。這不是腦筋急轉彎,也不是設計者故意惡搞,其實這是刻意的設計。在數學或邏輯上稱為「Vacuous Truth」。我覺得中文翻譯成「空虛的真」滿酷的,聽起來有點像領域展開之類的中二招式。它的意思是想要表達在這個空間裡沒有任何一個條件是不成立的,用一般路人也聽的懂的話來舉個例子:
如果房間裡一隻手機都沒有,那麼「房間裡所有的手機都已經關機」這句話就是成立的。
事實上,你要說「房間裡所有的手機都已經開機」或是「房間裡每一隻手機都是 iPhone」也都是對的。也就是說,對於一個空的陣列或集合來說,透過 all()
函數問它是否每個元素都成立(或都不成立),基本上沒什麼太大的意義,因為都會成立。
如果用幾個希臘字母來寫的話:
∀𝑥(𝑥∈𝐴 → 𝑃(𝑥))
這幾個字母組成一起的意思是「對所有的 𝑥,如果 𝑥 屬於集合 𝐴,那麼 𝑥 具有性質 𝑃」。在邏輯中,當一個條件語句(若 p 則 q)的前提(p)永遠不成立時,無論結論(q)是真是假,整個條件語句都被認為是成立的。因為「 𝑥 屬於集合 𝐴」這個前提永遠不會成立(因為集合 𝐴 是空集合),所以無論 𝑃(𝑥) 是什麼,整個陳述都成立。
如果有興趣翻 CPython 的原始碼,就會發現它的實作滿簡單的,為了簡化,我把一些判斷錯誤、處理記憶體跟相對比較不重要的語句拿掉,看起來像這樣:
// 檔案:Python/bltinmodule.c
static PyObject *
builtin_all(PyObject *module, PyObject *iterable)
{
PyObject *it, *item;
int cmp;
for (;;) {
item = iternext();
if (item == NULL)
break;
cmp = PyObject_IsTrue(item);
if (cmp == 0) {
Py_RETURN_FALSE;
}
}
Py_RETURN_TRUE;
}
簡單的說,就是用 for
跑個無窮迴圈,把元素一個一個拿出來,如果有任何一個值是 False
就結束迴圈,不然最後就回傳 True
。簡化成 Python 的寫法,大概就是:
def all(iterable):
for element in iterable:
if not element:
return False
return True
在 Python 跟 all()
類似邏輯的還有 any()
,大家可想想看,如果把原本題目裡的 all()
換成 any()
,答案會變成什麼。
不是只有 Python,Ruby 的.all?
方法也是這樣的設計。如果你是前端工程師,你去看看 JavaScript 陣列的 .every()
方法也一樣:
// 每個元素都大於 1000
> [1450, 9527, 1314].every((x) => x > 1000)
true
// 有一個沒有滿足條件
> [1450, 9527, 520].every((x) => x > 1000)
false
// 但是沒有人不滿足條件!
> [].every((x) => x > 1000)
true
對於空的集合體來說,基努李維也說過(並沒有),就算你說 1 加 1 等於 5,也是對的:
> [].every(() => 1 + 1 == 5)
true
是說,當你跟朋友經過一家餐廳門口,餐廳裡一個客人都沒有,但你朋友突然說「喂,這家餐廳裡的所有客人都在低頭安靜的吃飯耶!」,這句話雖然是符合邏輯,但放在七月份就會覺得有點嚇人了。