Python之父:為什么要使用操作符?
△點(diǎn)擊上方“Python貓”關(guān)注 ,回復(fù)“1”領(lǐng)取電子書(shū)

這是我在 python-ideas 上發(fā)布的一些東西,但我認(rèn)為這些很有趣,應(yīng)該分享給更多的人。
最近有很多關(guān)于合并兩個(gè) dict 的運(yùn)算符的討論。(Python貓注:Guido 指的是 PEP-584 的字典合并操作符,文章寫(xiě)于 2019 年 3月,當(dāng)時(shí)這個(gè) PEP 剛剛誕生,后來(lái)已合入 Python 3.9。)
這促使我思考為什么有些人喜歡運(yùn)算符,我想起了 30 多年前與導(dǎo)師 Lambert Meertens 的一次討論。
對(duì)于數(shù)學(xué)家來(lái)說(shuō),運(yùn)算符對(duì)于他們的思考方式至關(guān)重要。我們來(lái)選取一個(gè)簡(jiǎn)單的操作,比如將兩個(gè)數(shù)相加,并嘗試研究它的一些行為。
add(x, y) == add(y, x) (1)
式(1)表示了加法的交換律。它通常用運(yùn)算符來(lái)書(shū)寫(xiě),這使得它更簡(jiǎn)潔:
x + y == y + x (1a)
這似乎是一個(gè)小小的收獲。
現(xiàn)在我們來(lái)考慮一下結(jié)合律:
add(x, add(y, z)) == add(add(x, y), z) (2)
式(2)可以用運(yùn)算符重寫(xiě):
x + (y + z) == (x + y) + z (2a)
這比式(2)容易理解得多,并且我們發(fā)現(xiàn)括號(hào)是多余的,所以現(xiàn)在我們可以這樣寫(xiě):
x + y + z (3)
沒(méi)有歧義(+ 運(yùn)算符綁定到左邊還是右邊并不重要)。
許多其他定律也可以很容易的使用運(yùn)算符來(lái)寫(xiě)。這里還有一個(gè)關(guān)于加法恒等元素的例子:
add(x, 0) == add(0, x) == x (4)
相比于
x + 0 == 0 + x == x (4a)
這里的總的思想就是一旦你學(xué)會(huì)了這個(gè)簡(jiǎn)單的表示法,用它們寫(xiě)的方程就比用函數(shù)表示法寫(xiě)的方程更容易“操作”——就好像我們的大腦用不同的大腦機(jī)制來(lái)掌握運(yùn)算符,這是更有效率的方法。
我認(rèn)為,使用運(yùn)算符編寫(xiě)的公式更容易被“視覺(jué)化”處理就與此有關(guān): 它們利用了大腦的視覺(jué)處理機(jī)制,而這一機(jī)制在很大程度上是在潛意識(shí)中運(yùn)作的,并且它會(huì)告訴大腦的意識(shí)部分它看到了什么(比如,“椅子”而不是“幾塊木頭連在一起”)。函數(shù)符號(hào)在我們的大腦中則必須走一條不同的路徑,這是無(wú)意識(shí)的(它與內(nèi)容的閱讀和理解有關(guān),這是在比視覺(jué)處理更晚的年齡段才學(xué)會(huì)/訓(xùn)練的)。
當(dāng)你將多個(gè)運(yùn)算符結(jié)合在一起時(shí),視覺(jué)處理的功能就會(huì)變得非常明顯。例如,考慮一下分配律:
mul (n,add(x, y)) == add(mul (n, x) mul (n, y)) (5)
這寫(xiě)起來(lái)很惱火,我相信一開(kāi)始你是不會(huì)看到這個(gè)規(guī)律的(或者至少你不會(huì)立刻看到它,如果我沒(méi)有提到這是分配律的話(huà))。
與下式比較:
n * (x + y) == n * x + n * y (5a)
注意,這里也使用了相對(duì)的運(yùn)算符優(yōu)先級(jí)。通常數(shù)學(xué)家們會(huì)把它寫(xiě)得更緊湊:
n(x+y) == nx+ny (5b)
但是,遺憾的是,目前這超出了 Python 解析器的能力。
運(yùn)算符表示法的另一個(gè)非常強(qiáng)大的方面是,可以方便地將它們應(yīng)用于不同類(lèi)型的對(duì)象。例如,定律(1)到(5)在x、y和z是相同大小的向量,而n是標(biāo)量(用0向量代替字面量“0”)時(shí)也適用,如果它們是矩陣(同樣,n必須是一個(gè)標(biāo)量)也適用。
你可以這樣處理不同域中的對(duì)象。例如,上面的定律(1)到(5)也適用于函數(shù)(n也是一個(gè)標(biāo)量)。
通過(guò)明智地選擇運(yùn)算符,數(shù)學(xué)家們可以運(yùn)用他們的視覺(jué)大腦來(lái)幫助他們更好地進(jìn)行數(shù)學(xué)研究: 他們會(huì)更快地發(fā)現(xiàn)新的有趣的定律,因?yàn)橛袝r(shí)黑板上的符號(hào)就會(huì)跳到你面前,給你提供一條通往難以捉摸的數(shù)學(xué)證明的道路。
現(xiàn)在,編程并不完全等同于數(shù)學(xué),但我們都知道可讀性很重要,這就是 Python 中運(yùn)算符重載的作用。一旦你內(nèi)化了運(yùn)算符具有的簡(jiǎn)單屬性,使用+號(hào)進(jìn)行字符串或列表連接將比純 OO 表示法更具可讀性,上面(2)和(3)解釋了(部分程度上)為什么是這樣。
當(dāng)然,這樣做絕對(duì)有可能做過(guò)火——然后你就會(huì)使用 Perl。但我認(rèn)為,那些指出“已經(jīng)有辦法做到這一點(diǎn)”的人忽略了一點(diǎn),即真正理解這一點(diǎn)是比較容易的:
d = d1 + d2
和下面相比:
d = d1.copy ()
d.update(d2)
這不僅僅是少了幾行代碼的事: 第一種形式允許我們使用我們的視覺(jué)處理,以幫助我們更快的看到它的意思,并且不會(huì)影響我們大腦的其他部分(例如,這些部分可能已經(jīng)被跟蹤 d1 和 d2 的意思占據(jù))。
當(dāng)然,任何事情都是有代價(jià)的。你必須學(xué)習(xí)運(yùn)算符,并且在應(yīng)用于不同對(duì)象類(lèi)型時(shí)必須學(xué)習(xí)它們的屬性。(數(shù)學(xué)中也是如此——對(duì)于數(shù)字,xy == yx,但是這個(gè)屬性不適用于函數(shù)或矩陣; 另一方面,正如結(jié)合律一樣, x+y == y+x 適用于所有情況。)
“但是性能呢?”我聽(tīng)見(jiàn)你這樣問(wèn)。好問(wèn)題。在我看來(lái),可讀性第一,性能第二。 在基本的例子(d = d1 + d2)中,與使用 update 的兩行代碼版本相比,沒(méi)有性能損失,而且可讀性明顯提高。
我能想到很多情況,性能差異無(wú)關(guān)緊要,但可讀性是最重要的,對(duì)我來(lái)說(shuō),這是默認(rèn)的假設(shè)(即使在 Dropbox——我們最重要的性能代碼已經(jīng)用丑陋的 Python 或 Go 重寫(xiě)過(guò)了)。對(duì)于少數(shù)性能至關(guān)重要的情況,很容易將運(yùn)算符版本轉(zhuǎn)換為其他版本——一旦你認(rèn)為這很有必要 (可能是通過(guò)分析得出的)。

還不過(guò)癮?試試它們
