1. 程式人生 > >聊聊我對python的感受

聊聊我對python的感受

工作中主要是在寫python2,把python作為使用者邏輯的載體的,一般是C++寫好底層,匯出成python模組使用,或者是可執行程式帶起來一個python虛擬機器,把函式注入進去,然後載入python程式碼執行。我接觸的這部分python,沒有太多的框架,幾乎不使用基本資料結構之外的標準庫,因為很多東西都是C++封裝好給python的,效能比純python好很多。因此下面的文字也主要圍繞著我使用的這個python的子集展開,如果有所偏差,那也是無法避免的。

python的一些優點

hook一切的能力

python的裝飾器提供了一種在定義處直接修改被定義的物件的能力,有點類似清潔巨集。巨集這種東西,目的就是對語言的語法樹做變換,生成被調整過的語法樹結構,來達到抽象某些東西的意義。然而大部分加巨集,尤其是清潔巨集的語言,都不可避免的要考慮一個問題,就是如何表示語法樹和語法樹的變換。Lisp系因為本身程式碼就是資料,所以語法樹,程式碼,變換都是S-expr;rust則是發明了另一套語法來做這一件事情;還在proposal階段的C++ reflection,不僅要發明語法,還要發明對應的庫和namespace,甚至還要和constexpr之類的東西耦合,這就很醜,而且又很大地增加了語言的複雜度。不噴C++了,python的裝飾器很巧妙的複用了python語法,通過放棄一些變換的自由度的方式(裝飾器只能以函式/類為單位進行變換)實現了較為舒服的巨集。

另一個要說的就是metaclass,metaclass用來hook型別的建立,可以用來做一些在型別建立時對型別進行調整的工作,比如增減類變數,修改基類,註冊一些東西,變換一下函式等等。

最後就是屬性索引器__get__和__set__,我曾經在看到他們之後突然意識到,這就是C#的DependencyProperty需要的東西,不通過使用者編寫程式碼,來達到hook一個屬性的讀取和寫入的能力。寫過WPF的同學都知道,WPF裡面有一套很複雜的DependencyProperty/Object系統,其實說白了就是需要hook屬性更改,用來做資料繫結之類的東西,寫起來很麻煩,VS也專門提供了插入dependency property定義的boilerplate的功能。python這個屬性索引器設計就很好的解決了這個問題,至少讓程式碼寫起來沒有那麼多廢話了。

有人可能覺得我用C++和C#跟python對比不合適,的確,這些語言的主戰場是大型系統,高效能,硬體抽象,的確不適合用來實現過於靈活的東西。

不吝於用開洞的方法來解決問題

python有很多魔術方法,而且隨著語言的進化,還在加入更多的魔術方法。物件構造走魔術方法,運算子過載走魔術方法,上面說的hook屬性索引器也是魔術方法,甚至檢查一個類是不是另一個的基類,也可以走魔術方法。當語言層面原生為你提供很多hook的時候,尤其是需要靈活性的動態語言,他的靈活性就真的有了。反觀C++一直在吹噓我們可以用抽象來實現一切東西,然後寫出來的東西都是一坨一坨的模板,然後initializer_list,structured binding,attribute還是忍不住開了洞。C#這樣語法還算很優雅的語言,也需要一堆boilerplate來做dependency property。當然他們都是靜態語言,主戰場是效能,本來就不應該和動態語言相比較。

另一個要說的就是__getitem__和__setitem__,python的obj[key]和obj[key]=value走了兩個不同的方法,這樣就很棒地繞開了key不存在時到底是要新增,還是丟異常的問題。因為分開了讀取和寫入,obj[key],不存在丟異常,obj[key]=value,不存在就新增,和C#/C++的Dictionary/map設計相比好了不少,這倆一個不存在都丟異常,一個不存在都新增。

與C++雜亂的開洞相比,python的洞基本都開在魔術方法上,位置相對比較統一。

python的垃圾之處

智障縮排/換行語法

強制縮排,換行,python是唯一一個lambda會降低程式碼可讀性的語言。就沒聽說過其他哪個語言的lambda會讓程式碼可讀性變差的

強行換行用\,尤其是當if條件稍微長一點的時候,你不想用\,把if拆開,就得縮排好幾個tab,你用\換行,看著就跟C++寫了一坨巨集似的

基本控制流太少

雖然傳統的for、do-while都可以變換成while來做,但是while True: if xxx break很醜哎,加個do-while又不會懷孕

雖然switch可以用dict+lambda做,但lambda都那麼醜了,我又不想白白產生一個構造dict的overhead,只能寫一坨if else

真的慢,沒有jit,沒有內聯優化,沒有逃逸分析,物件全都開在堆上,引用計數+mark sweep也慢,處理個陣列能比C++慢兩個數量級,小函式呼叫特別傷。