來自Kenneth Reitz大神的建議:避免不必要的物件導向程式設計

Python貓發表於2018-10-22

你也許見過很多人對於Python的評價,他們說Python是“指令碼語言”和“膠水語言”,在某種程度上,他們說的是對的。但是,如果你學習過Python,你會知道Python也支援物件導向的程式設計,更有甚者,在Python中所有東西都是物件。

事實上,Python有著很強大的支援物件導向程式設計的能力,比如我們剛介紹過的pathlib模組,它就是一個用物件導向思想來處理檔案系統的模組。

可以說,Python是“能屈能伸”吧,小打小鬧的時候開箱即用輕鬆上手,認真嚴肅起來耍大刀也是虎虎生風不遑多讓。

只不過,物件導向就一定是好事麼?支援物件導向程式設計,就一定要時刻這樣用麼?

下面段落出自《Python最佳實踐指南》,這是由圈內大神Kenneth Reitz發起和維護的開源專案(文末附了相關連結),讓我們一起來看看K神提出的建議。

Kenneth Reitz大神的建議

Python 有時被描述為一種物件導向的程式語言。這可能對大家有些誤導,需要加以澄清。

在 Python 中,所有東西都視為一個物件,並且可以按物件處理。當我們說,函式是“一級”物件,就是將函式視為物件的意思。函式、類、字串,甚至型別都是 Python 中的物件:像任何物件一樣,它們有一個型別,可以作為函式引數傳遞,並且它們可能有方法和屬性。按這種理解, Python 是一種物件導向的語言。

但是,與 Java 不同, Python 並沒有將物件導向的程式設計作為主要的程式設計範例來實施。Python 專案不採用物件導向的方式是完全可行的,即不使用或很少使用類定義、類繼承或特定於物件導向程式設計的任何其他機制。

此外,從 模組 部分可以看出, Python 處理模組和名稱空間的方式為開發人員提供了一種自然的方法來確保抽象層的封裝和分離,這兩者都是使用物件導向的最常見原因。因此,當業務模型不需要物件導向時, Python 程式設計師有更大的自由來不使用物件導向程式設計。

基於一些因素的考慮,我們應避免不必要的物件導向程式設計。 當我們想將一些狀態和功能粘合在一起時,定義自定義類是很有用的。在函式程式設計的討論中,我們指出,“不必要的物件導向程式設計”這個問題出自方程的“狀態”部分。

在某些體系結構中,例如典型的 web 應用程式,會生成多個 Python 程式例項,以響應可能同時發生的外部請求。在這種情況下,將一些狀態儲存到例項物件中,意味著保留一些關於世界的靜態資訊,這很容易出現併發或競爭問題。有時,在物件的初始化(通常用 __init__() 方法來完成)狀態和實際使用物件方法的狀態之間,世界資訊可能已經改變,保持的狀態可能已經過時。例如,一個請求載入了記憶體中的某一項,並將其標記為由使用者讀取。而另一個請求同時要求刪除該項,這可能發生在第一個程式載入該項之後,然後我們必須將其標記為已刪除物件。

上述以及其他問題引出了這樣的想法:使用無狀態函式是一種更好的程式設計範例。

另一種說法是建議儘可能少的使用具有隱式上下文和副作用的函式和程式。函式的隱式上下文由全域性變數和持久層中的資料項(使用方法訪問)組成。副作用是指函式對其隱式上下文所做的更改。如果函式會儲存或刪除全域性變數或持久層中的資料,則稱它有副作用。

將有上下文和副作用的函式與邏輯函式(稱為純函式)隔離開來,可以獲得以下好處:

  • 純函式是確定性的:給定一個固定的輸入,輸出始終是相同的。

  • 純函式需要重構或最佳化時,更容易更改或替換。

  • 純函式更易於使用單元測試進行測試:對於複雜的上下文設定和事後的資料清理的需求更少。

  • 純函式更容易操作、修飾和傳遞。

總之,針對某些體系結構,由於沒有上下文或副作用,純函式是比類和物件更有效的構建塊

顯然,物件導向程式設計在許多情況下是有用的,甚至是必要的,例如在開發圖形化桌面應用程式或遊戲時,被操作的東西(視窗、按鈕、化身、車輛)在計算機記憶體中具有相對較長的壽命。

貓貓的思考

以上就是K神的建議。他在後半段提到了純函式(pure functions),這讓貓貓聯想到了函數語言程式設計(Functional Programming),但純函式似乎是一種更具普遍性的東西,它就像是一種數學上的定義。純函式真的有那麼神麼?

於是,貓貓去google了“純函式”。沒想到,排在前面的結果竟然全跟Javascript相關。

來自Kenneth Reitz大神的建議:避免不必要的物件導向程式設計

除去維基百科的條目,第一個答案指向了一本GitBook《JS函數語言程式設計指南》,好奇的貓貓點進去看了,結果大為歎服!建議大家有條件的話都去讀一下(連結見文末,不懂js也不影響理解)。

非常巧合的是,這本書的作者也發表了他對於物件導向程式設計的看法:

我最喜歡的名言之一是 Erlang 語言的作者 Joe Armstrong 說的這句話:“面嚮物件語言的問題是,它們永遠都要隨身攜帶那些隱式的環境。你只需要一個香蕉,但卻得到一個拿著香蕉的大猩猩…以及整個叢林”。

讀完《純函式的好處》章節,貓貓提煉了幾條筆記。一方面是為了加強對純函式的理解,在實戰中規避一些“不純”的用法,另一方面,也提出了幾個思考和疑問,今後在學習Python的過程中,留神找到答案:

1、避免使用不純的函式。JS中的splice是個不純的函式,那Python中是否也有這樣的函式呢?

2、下例中第一個是不純的,因為函式的結果取決於minimum這個可變變數,換句話說,它取決於系統狀態(system state);這一點令人沮喪,因為它引入了外部的環境,從而增加了認知負荷(cognitive load)。(題外話:這個例子,貓貓大有感觸。公司有個專案的老版本程式碼中,充斥了各種全域性變數,小夥伴們在維護時吃了好多苦頭!)JS中可以用Object.freeze 方法令minimum成為不可變物件,Python中有類似的實現麼?

// 不純的
var minimum = 21;
var checkAge = function(age) {
  return age >= minimum;
};

// 純的
var checkAge = function(age) {
  var minimum = 21;
  return age >= minimum;
};

3、不純函式會帶來“副作用”,其“作用”本身沒有壞處,但其“副”是滋生bug的溫床。並不是說,要禁止使用一切副作用,而是說,要讓它們在可控的範圍內發生。堅持這種「相同輸入得到相同輸出」的原則。

4、純函式實際上就是數學定義中的函式。“函式是不同數值之間的特殊關係:每一個輸入值返回且只返回一個輸出值。”

5、追求“純”的理由:可快取性(有點像生成器,延遲執行)、可移植性/自文件化(因其完全自給自足,依賴關係明確)、可測試性(為函式式環境定製的測試工具,JS中有Quickcheck,Python中有麼?)、合理性(引用透明性:一段程式碼可以替換成它執行所得的結果,而且是在不改變整個程式行為的前提下替換)、並行程式碼(純函式根本不需要訪問共享的記憶體,而且純函式也不會因副作用而進入競爭態(race condition))。

今天的分享就到這了,最後再邏輯幾句。本篇文章裡,一句Python程式碼都沒有,真的是夠“幹”的了。貓貓有時候挺喜歡看這樣的文章,因為它會帶給你思想上的啟迪,就像是絕世高人在傳授秘籍心法一樣。所以,貓貓也喜歡轉述和思考這類問題,比如之前發過的一篇《超強彙總:學習Python列表,只需這篇文章就夠了》,就不僅僅有程式碼層面的內容,還特意加入了Guido老爹關於Python列表索引為何從0開始的解釋,以及其它程式語言對索引值的考慮。

不記得在哪裡曾看到過一句話,送予大家共勉:

如果一個人眼裡只看得見程式碼,那他跟鹹(ma)魚(nong)有啥區別?

相關文章