文章目錄
- ◇Python的對象
- ◇對象的屬性
- ◇對象的類型
- ◇對象的標示
- ◇“一切皆對象”的好處?
- ◇廣義封裝
- ◇狹義封裝
- ◇對存取控制的偏見
- ◇Python的繼承
- ◇繼承的文法
- ◇繼承的動態性
本系列已經中斷了很長時間 直到最近一個讀者來信問俺,為啥不繼續寫,俺才突然想起這個被遺忘的系列,實在是抱歉!前一個文章介紹了作為動態語言的Python,今天來聊一聊Python在物件導向編程(OOP)方面的特色。
本文主要針對那些熟悉OOP,但還不熟悉Python的同學。為了讓大伙兒有一個直觀的認識,俺會拿C++/Java來進行文法上的對比。(這倆語言的名氣夠大,且號稱支援OO,也算有些可比性)
強調一下:本文雖然拿了某些語言來作對比,但絲毫沒有貶低這些語言的意思,請這些語言的粉絲們,不要對號入座
★抽象(Abstraction)
但凡介紹OOP,自然會提到抽象。因為抽象,是OO的第一要素,也是其它要素的基礎。而提到抽象,又不免提到對象(Object)。所以,俺首先來聊一下,Python語言是如何體現對象的。
◇Python的對象
如果要問俺,什麼是Python中的對象,還真不好下一個嚴密又通俗易懂的定義。為了敷衍大伙兒,俺只好用一句話來概括,那就是Python語言中,一切皆對象。這句話該如何理解捏?簡單來說,就是你在Python語言中涉及到的各種東東,都是“對象”。比如,函數是對象、各種數值(比如整數值、浮點數值、布爾值)是對象、模組(類似於Java的package)是對象、None(類似於Java的Null 參考null、C++的null 指標NULL)也是對象、......
對比一下C++和Java的文法:只有類的執行個體才能算得上是對象。連基本類型(比如int、char、float、等)都算不上對象,至於函數,就更算不上了。
既然是一切皆對象,俺有必要稍微總結一下,Python對象的共性,否則初學Python的同學還是會一頭霧水。
◇對象的屬性
首先,所有的Python的對象,都具有若干個屬性。你可以通過內建的dir()函數進行反射,從而瞭解到某個對象分別都包含哪些屬性。熟悉Java的同學,應該明白啥是"反射"。光懂C/C++的同學,如果理解上有困難,可以參見“這裡”。
另外,Python還提供了若干內建的函數,用於在運行時操作指定對象的屬性。具體如下:
hasattr(obj, name) #判斷obj對象是否具有名為name的屬性setattr(obj, name, value) #設定obj對象的名為name的屬性值為valuegetattr(obj, name) #擷取obj對象的名為name的屬性值delattr(obj, name) #刪除obj對象的名為name的屬性
◇對象的類型
所有的Python對象,都可以通過內建的type()函數擷取該對象的類型。這實際上就是Python的RTTI機制的體現。懂C++的同學,可以回顧一下C++的typeid關鍵字;懂Java的同學,可以想一想instanceof關鍵字。
◇對象的標示
所有的Python對象,都可以通過內建的id()函數擷取該對象的唯一標示。而且當一個對象建立之後,這個唯一標示就會始終保持不變。對於學過C/C++的同學,不妨把這個唯一標示想象成該對象在記憶體的地址。這或許有助於你的理解
Python對象還有其它一些共性,考慮到本文的掃盲性質,就不再費口水了。有興趣的同學,可以自己找些入門書研讀一番。
◇“一切皆對象”的好處?
可能有同學會問,“一切皆對象”有啥好處捏?俺竊以為:當一切皆為對象,就可以把很多概念、操作、慣用手法統一起來,在文法層面體現出美感。
下面俺舉幾個例子,並拿Java來對比一下。
在Java裡面,由於基本類型不是繼承自Object類,引出不少麻煩。當初Java它爹剛開始設計容器類(比如Vector、ArrayList、...)的時候,頗費了一番功夫。因為容器裡面放置的東東必須是Object,為了讓容器能適應基本類型,只好給每一種基本類型分別對應一個派生自Object的封裝類(Integer類對應int、Float類對應float、...);後來又平添了自動裝箱/拆箱的概念。而Python就沒有這方面的困擾。
再拿剛才提及的“反射”來說事兒。雖然Java語言支援對象的反射,但是Java的package不是Object,所以也就無法對package進行反射。反觀Python,任何一個module(相當於Java的package)import之後,都可以直接通過前面提到的dir()函數進行反射,得知該module包含了哪些東東。僅僅需要2行代碼:
import xxxdir(xxx)
★封裝(Encapsulation)
為了避免歧義,首先要明確一下:什麼是“封裝”?為了敘述方便,俺把OOP的封裝,分為狹義和廣義兩種。(關於封裝的深入討論,可以參見“這裡”)
◇廣義封裝
OOP很強調以資料為中心。所以OOP的廣義封裝,就是把資料和操作資料的行為,打包到一起。比如C++/Java裡的class,可以同時包含資料成員和函數成員,就算是滿足廣義的封裝了。對於Python而言,其class關鍵字類似於C++和Java,也已經具有廣義的封裝性了。
◇狹義封裝
而OOP的狹義封裝,則更進一步,增加了資訊隱藏(Information Hiding)。比如C++和Java的public、protected、private關鍵字,就是通過存取控制來達到資訊隱藏的效果。Python雖然沒有針對存取控制的關鍵字來修飾類成員,但是Python採用了另外一套機制——根據命名來約定。在Python的對象中,如果某個屬性以雙底線開頭來命名(比如 __name),就能起到類似於private的效果。
◇對存取控制的偏見
俺曾經在某技術論壇看到有人質疑Python的存取控制機制,說Python的私人屬性,可以通過反射機制繞過,因此形同虛設。在此,俺想舉C++和Java來進行反駁。
在Java中,同樣可以通過反射機制,來訪問類的私人成員。至於C++,得益於指標的強大,只要能訪問某個對象(的this指標),通過計算該對象成員變數在記憶體中的位移,即可輕易對其進行讀寫。雖然這麼幹挺變態滴,但理論上是可行滴。
★繼承(Inheritance)
緊接著,咱再來說一下繼承的話題。
◇Python的繼承
Python沒有像Java那樣,區分出類繼承(OO的術語中也叫“實現繼承”)、介面繼承;也沒有像C++那樣,區分出公有繼承、私人繼承、保護繼承這麼花哨的玩意兒。Python就只有一種繼承方式。
◇繼承的文法
Python的繼承文法,相比C++/Java而言,更加簡潔。比如子類Child需要繼承父類Parent,代碼只需如下:
class Child(Parent) :
如果是多繼承,代碼大同小異:
class Child(Parent1, Parent2, Parent3) :
如果你想知道某個類有哪些父類(基類),只需要通過 Child.__bases__ 便可知曉。
◇繼承的動態性
其實上一個文章已經介紹了動態改變繼承關係的例子。然而上一個文章年代久遠(距今快1年),想必很多同學沒看過或者看過又忘了。俺不妨再囉嗦一下。作為一種動態語言,Python可以在運行時修改類的繼承關係。這個特性比較酷,是C++/Java所望塵莫及滴。請看下面的例子:
class Parent1 : def dump(self) : print("parent1")class Parent2 : def dump(self) : print("parent2")class Child : def dump(self) : print("child")print(Child.__bases__)Child.__bases__ += (Parent1, Parent2) # 動態追加了2個父類print(Child.__bases__) # 列印出的父類資訊中,已經包含Parent1、Parent2
★多態(Polymorphism)
至於Python的多態,和傳統的OO語言差不多,似乎沒有太多值得說道的地方。俺簡單舉個代碼作例子。為了省打字,直接複用上述的3個類,然後再另外增加一個test()函數如下:
def test(obj) : obj.dump()
然後對test()函數分別傳入不同的類型的對象,後面俺就無需多說了吧?
c = Child()test(c) # 列印出 childp1 = Parent1()test(p1) # 列印出 parent1
★結尾
今天的話題,主要是讓不熟悉Python的網友,對Python在物件導向方面的特性,有一個粗淺、感性的認識。聊完了OOP,下一個文章會聊一下關於關於FP(函數式編程)的話題。
回到本系列的目錄
著作權聲明
本部落格所有的原創文章,作者皆保留著作權。轉載必須包含本聲明,保持本文完整,並以超連結形式註明作者編程隨想和本文原始地址:
http://program-think.blogspot.com/2010/08/why-choose-python-3-oop.html