我雖然在 Java 有十多年經驗,但 Python 的物件導向我還尚未深入研究,希望這樣的開頭不會撲滅你的興致,我這篇來分享我在 Python 中看見的物件導向觀點。
Object, 物件
通常在 scripting language 裡頭,物件導向的思維、物件的概念,都跟在一些具有嚴謹類別階層的語言不太一樣。例如在 PyLR 裡頭,第三章談到 object 時,它說:
Objects are Python’s abstraction for data. All data in a Python program is represented by objects or by relations between objects.
有人說 Python 裡什麼都是物件,這句話對一半。在 Ruby 剛出來時,它也是宣稱什麼都是物件。而 JavaScript 也有人如此宣稱。然而,我認為一個語言要稱自己的物件導向徹底到一個程度,它必須滿足三個條件:
- 具備根類別,所有物件都是從根類別衍生出來的,這在 Python 裡,是一個叫
object
的類別 - 具備封裝能力,至少能有 public、private、protected 這三種存取控制機制 (其實大多數 scripting language 很難做到這層面,不該苛求啦...)
- 透過類別定義而來的多型機制,我可以使用這物件的某個行為 (method),是因為它的類別或介面有定義,而非剛好它有這個函式 (function) (碰過 Ruby 的人,知道我說的正是 duck typing...)
精確地來說,Python 應該說是一個以物件的形式來表現資料的語言,而非一個 everything is object 的語言。
self, 自參考
在物件導向的實作中,大概有這兩種區分屬於類別、屬於物件不同之處。一種是把所有的成員都預設當作物件的,除非特別標示,它才是類別的,像 Java、C# 都是標為 static;另一種是把所有成員都預設成類別的,除非特別傳入自參考 (如:self),或特別指定它與 self 之間的關係,它才會是屬於物件的。
例如這樣:
class Sample:
static_attr = 1
def __init__(self):
self._attr = 0
def get_attr(self):
return self._attr
@staticmethod
def get_attr_of_class():
return static_attr
Python 的成員查找邏輯其實可以更複雜,進一步可以參考 淺談 Python 的屬性
既然談到物件與類別成員,我想從另一個角度來分享。
動態定義成員
Python 既然不同於傳統的靜態型別語言,那麼我們對它的期待,一定會是它能動態建立成員內容。這應該怎麼做呢?
例如我們要為剛剛那個 Sample
的物件增加某個屬性,這時可以透過 __setattr__
來達成:
class Sample:
def __init__(self):
pass
def add_attr(self, name, value):
self.__attr__(name, value)
sample = Sample()
sample.add_attr('foo', 100)
sample.foo # 100
如果,從另一個方面來講,有人呼叫了 Sample 未定義的物件,也忘了呼叫 add_attr
怎麼辦?這時候可以搭配 __getattr__
來使用:
class Sample:
def __init__(self):
pass
def __getattr__(self, name):
return self.__setattr__(name, 'EMPTY')
sample = Sample()
sample.foo # EMPTY
當 Python 發現 sample
這個物件沒有 foo
這個屬性時,會自動呼叫 __getattr__
,而 __getattr__
便會透過內定的 __setattr__
自動增添一個屬性,並賦予預設值 EMPTY
。
結語
原本在打算著用以往在其他語言開發物件導向的經驗,來看待 Python 物件應當如何,但因為自認為對 Python 的物件導向設計哲學理解甚少而語塞。我希望特別地討論 Python 物件導向特有的面向,避免從其他語言的範式來寫,只是把 sample code 改成 Python。
今天已是第四天,或許我們明天就能開始來思考,在 Python 這個多範式語言裡,能否調和這些範式的特性,發揮各自的作用。不是為了 FP 而 FP,或為了 OOP 而 OOP。