# 空虛的真

> 你知道在 Python 裡，all([]) 為什麼會回傳 True 嗎？這不是腦筋急轉彎，而是有趣的「空虛的真（Vacuous Truth）」邏輯！

Published: 2024-10-09
URL: http://cdn.kaochenlong.com/vacuous-truth

---

來一個我自己覺得滿有趣的 Python 程式問答題。

在 Python 裡，`all()` 這個函數可以接一個串列，串列裡的每一顆元素都成立的時候就會回傳 `True`，反之只要有一顆不成立，就會是 `False`，例如：

```python
&gt;&gt;&gt; all([True, True, True])
True
&gt;&gt;&gt; all([True, False, True])
False
```

題目來了：

```python
&gt;&gt;&gt; all([])     # A
&gt;&gt;&gt; all([[]])   # B
&gt;&gt;&gt; all([[[]]]) # C
```

一堆中括號、小括號，眼睛都快看花了，在正式的專案千萬不要這樣整自己（或是你的同事）！大家猜猜看 A、B、C 的結果分別是什麼？

&lt;!-- more --&gt;

答案是 `True` / `False` / `True`

首先，我們得先看看 `all()` 這個函數的設計，當如果裡面所有的元素都是 `True` 的時候會得到 `True`，但文件上跟原始碼裡都有一行但書：

&gt; 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`。這不是腦筋急轉彎，也不是設計者故意惡搞，其實這是刻意的設計。在數學或邏輯上稱為「&lt;a href=&quot;https://en.wikipedia.org/wiki/Vacuous_truth&quot; target=&quot;_blank&quot;&gt;Vacuous Truth&lt;/a&gt;」。我覺得中文翻譯成「空虛的真」滿酷的，聽起來有點像領域展開之類的中二招式。它的意思是想要表達在這個空間裡沒有任何一個條件是不成立的，用一般路人也聽的懂的話來舉個例子：

&gt; 如果房間裡一隻手機都沒有，那麼「房間裡所有的手機都已經關機」這句話就是成立的。

事實上，你要說「房間裡所有的手機都已經開機」或是「房間裡每一隻手機都是 iPhone」也都是對的。也就是說，對於一個空的陣列或集合來說，透過 `all()` 函數問它是否每個元素都成立（或都不成立），基本上沒什麼太大的意義，因為都會成立。

如果用幾個希臘字母來寫的話：

∀𝑥(𝑥∈𝐴 → 𝑃(𝑥))

這幾個字母組成一起的意思是「對所有的 𝑥，如果 𝑥 屬於集合 𝐴，那麼 𝑥 具有性質 𝑃」。在邏輯中，當一個條件語句（若 p 則 q）的前提（p）永遠不成立時，無論結論（q）是真是假，整個條件語句都被認為是成立的。因為「 𝑥 屬於集合 𝐴」這個前提永遠不會成立（因為集合 𝐴 是空集合），所以無論 𝑃(𝑥) 是什麼，整個陳述都成立。

如果有興趣翻 CPython 的原始碼，就會發現它的實作滿簡單的，為了簡化，我把一些判斷錯誤、處理記憶體跟相對比較不重要的語句拿掉，看起來像這樣：

```c
// 檔案：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 的寫法，大概就是：

```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()` 方法也一樣：

```javascript
// 每個元素都大於 1000
&gt; [1450, 9527, 1314].every((x) =&gt; x &gt; 1000)
true

// 有一個沒有滿足條件
&gt; [1450, 9527, 520].every((x) =&gt; x &gt; 1000)
false

// 但是沒有人不滿足條件！
&gt; [].every((x) =&gt; x &gt; 1000)
true
```

對於空的集合體來說，基努李維也說過（並沒有），就算你說 1 加 1 等於 5，也是對的：

```javascript
&gt; [].every(() =&gt; 1 + 1 == 5)
true
```

是說，當你跟朋友經過一家餐廳門口，餐廳裡一個客人都沒有，但你朋友突然說「喂，這家餐廳裡的所有客人都在低頭安靜的吃飯耶！」，這句話雖然是符合邏輯，但放在七月份就會覺得有點嚇人了。


