天天看點

python内置函數len_len(x) 擊敗 x.len(),從内置函數看 Python 的設計思想

内置函數是 Python 的一大特色,用極簡的文法實作很多常用的操作。

它們預先定義在内置命名空間中,開箱即用,所見即所得。Python 被公認是一種新手友好型的語言,這種說法能夠成立,内置函數在其中起到了極關鍵的作用。

舉個例子,求字元串 x 的長度,Python 的寫法是 len(x) ,而且這種寫法對清單、元組和字典等對象也同樣适用,隻需要傳入對應的參數即可。len() 函數是共用的。

這是一種極簡哲學的展現:Simple is better than complex。

但是,有些語言并不是這樣,例如在 Java 中,字元串類有一個求長度的方法,其它類也有自己的求長度的方法,它們無法共用。每次使用時,通過類或執行個體來調用。

同樣是求字元串長度,Python 的寫法:

saying = "Hello world!"

print(len(saying))

# 結果:12

而在 Java 中,寫法可能如下(簡化起見):

String saying = "Hello world!";

System.out.println(saying.length());

// 結果:12

Python 采用的是一種字首表達式 ,而 Java 采用的則是字尾表達式 。

除了求長度,Python 的某些内置函數也能在 Java 中找到對應的表達。例如,數值型字元串 s 轉化為整型數字,Python 可以用 int(s) 函數,而 Java 可以用 Integer.parseInt(s) ;整型數字轉化為字元串,Python 可以用 str(i) ,而 Java 也有 String.valueOf(i) 。

Python 的内置函數不與特定的類綁定,它們是一級對象。而 Java 的“函數”則無法脫離類而存在,它們隻是附屬品。

從直覺角度來看,Python 的表達似乎是更優的。但是,它們并不具有可比性 ,因為這是兩套語言系統,各有獨特的範疇背景,并不能輕易地化約。

就好比是,不能因為拉丁字母筆畫簡單,就說它優于漢字,因為在表意時,字母(表音文字)是遠遜于漢字(表意文字)的。同樣的,日本借用了漢字的偏旁部首而造出來的文字,雖然更省筆墨,但是也完全喪失了意蘊。

以此類比,Python 的内置函數雖有簡便之美,但卻丢失了某些表意功能。有些人在質疑/抨擊 Python 的時候,也喜歡拿這點說事,認為這是 Python 的設計缺陷。

這就引出本文最想讨論的一個問題來:為什麼 Python 要設計成 len(x) 這種字首表達,而不是 x.len() 這樣的字尾表達呢?

事實上,字尾設計也是可行的,以 Python 中清單的兩個方法為例:

mylist = [2, 1, 3, 5, 4]

mylist.sort()

print(mylist) # [1, 2, 3, 4, 5]

mylist.reverse()

print(mylist) # [5, 4, 3, 2, 1]

它們都是通過清單對象來調用,并不是憑空從内置命名空間中拿來的。語義表達得也很清楚,就是對 mylist 做排序和逆轉。

恰恰那麼巧,它們還有兩個同父異母的兄弟 sorted() 與 reversed(),這倆是字首表達型。

mylist = [2, 1, 3, 5, 4]

sort_list = sorted(mylist)

print(sort_list) # [1, 2, 3, 4, 5]

reverse_list = reversed(mylist)

print(list(reverse_list)) # [4, 5, 3, 1, 2]

不同的寫法,都在做同一件事(不考慮它們的副作用)。是以,字尾文法并非不可行,之是以不用,那肯定是刻意的設計。

回到前面的問題:為什麼是 len(x) ,而不是 x.len(x),這根源于 Python 的什麼設計思想呢?

Python 之父 Guido van Rossum 曾經解釋過這個問題(連結見文末),有兩個原因:

對于某些操作,字首符比字尾更好讀——字首(和中綴)表示法在數學中有着悠久的曆史,其視覺效果有助于數學家思考問題。我們可以簡單地把公式 x(a + b)` 重寫成 `xa + x*b ,但同樣的事,以原生的面向對象的方式實作,就比較笨拙。

當讀到 len(x) 時,我就 知道 這是在求某對象的長度。它告訴我了兩點:傳回值是一個整數,參數是某種容器。但當讀到 x.len() 時,我必須事先知道某種容器 x,它實作了一個接口,或者繼承了一個擁有标準 len() 方法的類。我們經常會目睹到這種混亂:一個類并沒有實作映射(mapping)接口,卻擁有 get() 或 keys() 方法,或者某些非檔案對象,卻擁有一個 write() 方法。

解釋完這兩個原因之後,Guido 還總結成一句話說:“I see 'len' as a built-in operation ”。這已經不僅是在說 len() 更可讀易懂了,而完全是在拔高 len() 的地位。

這就好比說,分數 ½ 中的橫線是數學中的一個“内置”表達式,并不需要再實作什麼接口之類的,它自身已經表明了“某數除以某數 ”的意思。不同類型的數(整數、浮點數、有理數、無理數…)共用同一個操作符,不必為每類資料實作一種求分數的操作。

優雅易懂是 Python 奉行的設計哲學 ,len() 函數的字首表達方式是最好的展現。我想起在《超強彙總:學習Python清單,隻需這篇文章就夠了》這篇文章中,曾引述過 Guido 對“為什麼索引從 0 開始 ”的解釋。其最重要的原因,也正是 0-based 索引最優雅易懂。

讓我們來先看看切片的用法。可能最常見的用法,就是“取前 n 位元素”或“從第i 位索引起,取後 n 位元素”(前一種用法,實際上是 i == 起始位的特殊用法)。如果這兩種用法實作時可以不在表達式中出現難看的 +1 或 -1,那将會非常的優雅。

使用 0-based 的索引方式、半開區間切片和預設比對區間的話(Python最終采用這樣的方式),上面兩種情形的切片文法就變得非常漂亮:a[:n] 和 a[i:i+n],前者是 a[0:n] 的縮略寫法。

是以,我們能說 len(x) 擊敗 x.len() ,支撐它的是一種化繁為簡、純粹卻深邃的設計思想。

面向對象的程式設計語言自發明時起,就想模拟我們生活于其中的現實世界。可是什麼類啊、接口啊、對象啊、以及它們的方法啊,這些玩意的毒,有時候蒙蔽了我們去看見世界本質的眼睛。

桌子類有桌子類的求長度方法,椅子類有椅子類的求長度方法,無窮無盡,可現實真是如此麼?求長度的方法就不能是一種獨立存在的對象麼?它之是以存在,是因為有“對象”存在,而不是因為有某個類才存在啊。

是以,我想說,len(x) 擊敗 x.len(),這還展現了 Python 對世界本質的洞察 。

求某個對象的長度,這種操作獨立于對象之外而存在,并不是該對象内部所有的一種屬性或功能。從這個角度了解,我們能夠明白,為什麼 Python 要設計出内置函數? 内置函數其實是對世界本質的一種捕捉。

這些見微知著的發現,足夠使我們愛上這門語言了。人生苦短,我用 Python。

關聯閱讀:

Guido 解釋 len 的由來:http://suo.im/4ImAEo

Guido 解釋 0 索引的由來:http://suo.im/5cr12S

本文原創并首發于公衆号【Python貓】,未經授權,請勿轉載。

原文位址:https://mp.weixin.qq.com/s/pKQT5wvyaSNFvnJexiCC8w

python内置函數len_len(x) 擊敗 x.len(),從内置函數看 Python 的設計思想

公衆号【Python貓】, 本号連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫作、優質英文推薦與翻譯等等,歡迎關注哦。背景回複“愛學習”,免費獲得一份學習大禮包。

繼續閱讀