Python進階技巧 (1) — Private member on Python

Jack Cheng
整個程式都是我的咖啡館
11 min readOct 7, 2018

「如何在 Python 上實現類似 Access Modifiers 的機制?!」

難易度:★★★☆☆(有Java、C++等學習經驗者較易上手)

實用度:★★★☆☆(依照開發團隊、專案規模而有不同)

【導言】

隨機詢問一位具有開發經驗的工程師Python的缺點,多半不外乎是「運算速度慢」和「共同開發困難」。前者「運算速度慢」,是因Python屬於Interpreted Language(直譯語言)所導致的(不知道什麼是直譯語言?沒關係,請滑到文章最後【飯後餐點】參考資料!),幾乎難以扭轉;而後者則是因為Python在設計上不須assign type to variable、沒有interface、沒有標準access modifier 等特質,使得較為具有共同開發經驗的developers非常不舒服。但別忘記,「共同開發困難」這些開發缺點其實也是Python的學習優點,它們讓初學者不需具備非常扎實的計算機程式概念就可以輕易上手。但在正式開發上,共同開發是必要的,小則數位developers,大則上百位developers,甚至是open source projects,原先設計好的簡單架構反而成為重大缺點。

因此在這裡會慢慢介紹如何使用不同的技巧,優化程式的擴充性、可靠性和穩定性。今天先介紹如何在Python上實作「類Private」的class member。

【開發環境與建議先備知識】

OS: Ubuntu 16.04

Python: 3.6

Required Knowledge: Familiar with Python Class

【壹、什麼時候需要Private class member?】

熟悉Object-Oriented Language任意「前輩」語言(C++、Java…)的人都一定知道access modifiers。它是用來限制class member 被access的範圍。Java有public、protected、default(none)和private四種,而C++有public、protected和private三種。以Java之modifier為例:

  • Public: 任意處accessible
  • Protected: Package and subclasses內accessible
  • Default(None): Package內accessible
  • Private: Class內accessible

從以上可發現access由上而下增難,「隱蔽性」增強。

說到這裡大致讓沒有接觸過access modifier的人簡單體會到他們的功能了。但何時、又為什麼需要這樣的access modifier呢?在此提供兩個主要原因:「增加source developer’s code stability(程式穩定性)」和「共同開發code readability(程式可讀性)」,以下說明。

  • Code Stability : 一份source通常非常龐大,動輒數千到數十萬行,因此很難避免packages之間或是classes之間的dependency,倘若現在A部份有些member和B部份部份member共用或互相牽連,這時候developers會很想「隱藏、封裝」這些敏感的members,以避免其他使用者在import或是更改該source code時因為一個不小心更動到member而導致其他部份也牽動,甚至噴error噴到你不要不要的。在這樣的情況下,developer會需要access modifier去「隱藏、封裝」這些敏感的members,而不同的access modifiers對應了不同的「隱藏、封裝」需求。
  • Code Readability : 一份code通常非常龐大,在共同開發時其他developers要研究或是更動你的code時,假設你一個package中共含了10000個functions,我想對方大概直接辭職不幹了!在這樣10000個functions中若只有10個是抽出來的接口(不是街口支付喔!),外部developers只需要接觸這樣的接口,其他幾乎是不用更動的底層運算等等,那麼透過access modifiers去區分functions,通常在正常情況下其他developers都可以很輕易的辨識出public是最重要的接口,也就是那10個抽出來的接口,如此優化readability減少非常多的review code時間。不過這部份需要team間先達成協議,「比較」容易能完整發揮到這樣的優點。當然,也是有那種需要把所有source code看完的特殊情況,不過那又是另一個structure design的問題了!

【貳、Python能實現哪些access modifiers?】

首先解釋一下為何不斷說是「類」private member呢?原因是Python並不支援「真正」的private member這個機制,我們來看看official document怎麼說:

“Private” instance variables that cannot be accessed except from inside an object don’t exist in Python. However, there is a convention that is followed by most Python code: a name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API (whether it is a function, a method or a data member).

等等我們會說明哪些地方並不是和真正的private member一樣!

那麼為何這麼多access modifiers,除了public本身是大家原本在用的概念外,我們卻只介紹了private這個access modifier呢?原因是Python幾乎不支援其他的access modifiers,只有private有對應較為明確的語法,剩下Java中的default(None)和protected雖然沒有辦法直接在Python裡找到對應的語法,但仍然是可以「類」實作出來的,但本質上都是基於private的語法去建立的。此外,一般Project開發public/private就非常足夠了,所以本篇文章只介紹類private語法囉!(未來有需求或是心情好也可能會出default(None)和protected如何實作啦xD不過我想聰明的你們一定能夠自己實現出來的!)

為了溝通方便,底下的內文都以private取代「類」private的正確稱呼喔,請 自行翻譯哈哈!

【參、Name mangling實現private member】

1. Name mangling (雙底線)

Python本身沒有嚴謹定義private,因此採用運用上最相似的詞name mangling,也就是雙底線命名規則。

在class內利用 __member 雙底線 name mangling,就可以達到類private的效果囉。

直接給code :

在class Animal直接在想要宣告成private的member前加上 __ 雙底線就可以囉!現在在class外部已經無法呼叫到被命名為private的member喔!

以下再示範帶有initializer且稍微複雜的例子 :

例子當中 dog instance一樣是無法拿到所有帶有 __ 雙底線命名的members,包含variable、function和subclass(inner class)。

2. Getter / Setter

延續上個例子,要介紹一個特別的技巧就是GetterSetter。這在傳統的Object-Oriented 是很常見的用法。(在此先澄清,Python本身也沒有定義嚴謹的setter/getter概念,以下解說中提到的getter/setter是參考其他Object-Oriented Language而來的,不過基本上解說過程清楚,不會造成混淆~)

由於像是 __food 這種private member是無法透過外部去access,更別說是變更了,但是在開發上想要做出一個「接口」去操作變更和獲取這些members的值要怎麼辦呢?

在Python上必須透過「自己寫public getter/setter function」去達到!

如同上面例子所示,我在 Animal class內寫了兩個 public functions為 get_food()set_food() 。不難理解,因為這兩個functions都是屬於class內部的members,它們自然可以拿到裡面的所有members(不論是不是private)。也就是說我透過寫出這兩個public functions就可以操作原本無法拿到的class private members了!

3. 為什麼要 Getter / Setter ?!

此時你或許會想說,「不對啊,阿我都要隱藏封裝起來要什麼還要這樣getter和setter去操作private members阿 = =」。

這也是剛學習這種架構的人常常提出的問題,以下是幾個常見的解釋 :

  • 只加getter : 這個不難想像吧!如今天我的某個member只想讓外部讀取(getter)而不想要更改(setter)那麼我們還是需要仰賴private的寫法才能達到!
  • setter限定方法 : 有時候developer想要保護特定的member而想要使用private同時又想限定其他developers「以特定方式變更(set)該member的值」,這時候仍然需要寫成private的形式,並加入相對應的setter function。以下給code :

如上範例所示,我想保護age這個variable,同時想要「限定developers操作age只能一次加一」,這時候可以利用這種setter寫法來達到限定的目的!當然,function命名是可以依照你的「限定行為」去命名的,只要合乎邏輯且命名易懂就可以!

  • 其他複雜原因 : 開發流程、多變數更動、複雜架構變動等等,實際開發上才會遇到的各種問題,這就是每位developers心中不同的痛了,我也還在拓展不同的痛點……

4.如何破解Python private members?

一開始就說過,Python沒有嚴謹定義private的機制,所以還是有以下這個方法可以拿到private members喔!

直接給範例code :

是不是很簡單呢?用這個寫法能夠access到所有private members,當然也就能夠做任何操作囉~

好,現在問題來了,阿我介紹這個功能要做什麼,當初就是為了要access不到特定members才設置private的,現在又有這個方法可以完全access,介紹這個到底要做什麼……

還記得我們提到Java access modifiers有四種,沒錯,介紹這個方法就是讓你實作出其他兩種default(none)和protected的可用工具啦!!!至於怎麼做,請給你們自己動動大腦囉~

【結語】

以上的內容如果有其他Object Oriented Language學習經驗或是Project開發經驗或許會比較能夠感受到其重要性。整體過程比較像是去模擬效仿傳統Object Oriented Language的架構,有好有壞。

不過還是要強調一點,介紹這樣的方法並不是說非用不可或是一定實用等等,一定還是要考慮開發時程、team合作情況與project scale等因素去判斷code要如何開發才是最有效率與相對優化的~

未來還會繼續介紹自己覺得可以運用的方法或技巧給大家,大家可以根據自己的project與開發習慣隨意撿拾,也歡迎給我們內容上的指正與交流~~

網柴要去睡覺了。晚安。

【飯後餐點】

特別感謝阿玉(Jade Chen)校稿斧正本文章。

最後附上一些延伸相關資料。

如果你也喜歡我們的文章,幫我們動動手部肌肉,按下掌聲Clap,讓我們有動力繼續煮下一頓料理!

--

--

Jack Cheng
整個程式都是我的咖啡館

Interested in ML, algorithms, and back-end. Studied M.S. at NTU GICE.