多态:意味着可以對不同類的對象使用同樣的操作
封裝:對外部世界隐藏對象的工作細節
繼承:以普通的類為基礎建立專門的類對象
def getPrice(object):
if isinstance(object,tuple):
return object[1]
else:
return magic_network_method(object)
這裡用isinstance進行類型/類檢查是為了說明一點-----類型檢查一般說來并不是什麼好方法,能不用則不用。
前面的代碼中使用isinstance函數檢視對象是否為元組。如果是的話,就傳回它的第二個元素,否則會調用一些“有魔力的”網絡方法。
假設網絡功能部分已經存在,那麼問題解決了------目前為止是這樣。但程式還不是很靈活。如果某些聰明的程式員決定用十六進制數的字元串來表示價格,然後存儲在字典中德鍵“price”下面呢? 沒問題,隻要更新函數:
def getPrice(object)
elif isinstance(object,dict)
return int(object['price'])
1.多态和方法
程式得到了一個對象,不知道它是怎麼實作的-----它可能有多種“形狀”。你要做的就是詢問價格,這就夠了,實作方法是我們熟悉的:
>>>object.getPrice()
2.5
綁定到對象特性上面的函數稱為方法。我們已經見過字元串、清單和字典方法。
>>>'abc'.count('a')
1
>>>[1,2,'a'].count('a')
對于變量x來說,不需要知道它是字元串還是清單,就可以調用它的count方法----不用管它是什麼類型。
标準庫random中包含choice函數,可以從序列中随機選出來元素。給變量指派:
>>>from random import choice
>>>x = choice(['hello,world!',[1,2,'e','e',4]])
運作後,變量x可能會包含字元串'hello,world!',也有可能包含清單[1,2,'e','e',4]----不用關心到底是哪個類型。要關心的就是在變量x中字元e出現多少次,而不管x是字元串還是清單。可以使用剛才的count函數:
>>>x.count('e')
2
x有個叫做count的方法,帶有一個字元作為參數,并且傳回整數值就夠了。如果其他人建立了對象類也有count方法,那也無所謂-----你隻需要像用字元串和清單一樣使用該對象就行了。
2.多态的多種形式
任何不知道對象到底是什麼類型,但是又想對對象做點什麼的時候,都會用到多态,這不僅限于方法----很多内建運算符和函數都有多态的性質:
>>>1+2
3
>>>'Fish'+'license'
'Fishlicense'
這裡的加運算符對于數字和字元串都能起作用。為說明這一點,假設有個叫做add的函數,它可以将兩個對象相加。那麼可以直接将其定義成上面的形式。
def add(x,y):
return x+y
對于很多類型的參數都可以用:
>>>add(1,2) 隻支援同類相加
>>>add('Fish','license')
列印對象長度
def length_message(x):
print "The length of",repr(x),"is",len(x)
repr函數是多态特性的代表之一-------可以對任何東西使用。
>>>length_message('Fnord')
The length of 'Fnord' is 5
>>>length_message([1,2,3])
The length of [1,2,3] is 3
7.1.2 封裝
封裝是對外部隐藏不必要細節的原則。
多态可以讓使用者對于不知道是什麼類(或對象類型)的對象進行方法調用,而封裝是可以不用關心對象是如何建構的而直接進行使用。
>>>o = OpenObject()
>>>o.setName('Sir Lancelot')
>>>o.getName()
'Sir Lancelot'
建立了一個對象(通過像調用函數一樣調用類)後,将變量o綁定到該對象上。可以使用setName和getName方法(假設已經由OpenObject類提供)。假設變量o将它的名字存儲在全局變量globalName中:
>>>globalName = 'Sir Gumby'
'Sir Gumby'
如果建立了多個OpenObject執行個體的話就會出現問題,因為變量相同,是以可能會混了:
>>>o1 = OpenObject()
>>>o2 = OpenObject()
>>>o1.setName('Robin Hood')
>>>o2.getName()
'Robin Hood'
可以看到,設定一個名字後,其他的名字也就自動設定了。
基本上,需要将對象看作抽象,調用方法的時候不用關心其他的東西,比如它是否幹擾了全局變量。是以能将名字“封裝”在對象内嗎?。可以将其作為特性存儲。
正如方法一樣,特性是對象内部的變量;事實上方法更像是綁定到函數的特性。
如果不用全局變量而用特性重寫類,并且重命名為ClosedObject,它會像下面這樣工作:
>>>c = ClosedObject()
>>>c.setName('Sir Lancelot')
>>>c.getName()
名字還在!這是因為對象有它自己的狀态。對象的狀态由它的特性來描述。對象的方法可以改變它的特性。是以就像是将一大堆函數捆在一起,并且給予它們通路變量的權利,它們可以在函數調用之間保持儲存的值。
7.1.3 繼承
所有的對象都屬于某一個類,稱為類的執行個體。 例如 鳥 就是鳥類
當一個對象所屬的類是另外一個對象所屬類的子集時,前者就被稱為後者的子類,是以百靈鳥類是鳥類的子集,鳥類是百靈鳥類的超類。
7.2.2 建立自己的類
__metaclass_ = type #确定使用新式類
class Person:
def setName(self,name):
self.name = name
def getName(self):
return self.name
def greeting(self):
print "Hello,world! I'm %s." % self.name
所謂的舊式類和新式類之間是有差別的。除非是Python3.0之前版本中預設附帶的代碼,否則在繼續使用舊式類已無必要。新式類的文法中,需要在子產品或者腳本開始的地方放置指派語句_metaclass_ = type。除此之外也有其他的方法,例如繼承新式類。後面馬上就會介紹繼承的知識。
這個例子包含3個方法定義,除了它們是寫在class語句裡面外,一切都像是函數定義。
>>>foo = Person()
>>>bar = Person()
>>foo.setName('Luke Skywalker')
>>>bar.setName('Anakin Skywalker')
>>>foo.greet()
hello,world! I'm Luke Skywalker
>>>bar.greet()
hello,world! I'm Anakin Skywalker.
在調用foo的setName和greet函數時,foo自動将自己作為第一個參數傳入函數中----是以形象地命名為self。對于這個變量,每個人可能都會有自己的叫法,但是因為它總是對象自身,是以習慣上總是叫做self。顯然這就是self的用處和存在的必要性。沒有它的話,成員方法就沒法通路她們要對其特性進行操作的對象本身了。
和之前一樣,特性是可以在外部通路的:
>>>foo.name
'Luke Skywalker'
>>>bar.name = 'Yoda'
Hello,world! I'm Yoda
7.2.3 特性、函數和方法
self參數事實上正是方法和函數的差別。方法(更專業一點可以稱為綁定方法)将它們的第一個參數綁定到所屬的執行個體上,是以這個參數可以不必提供。是以可以将特性綁定到一個普通函數上,這樣就不會有特殊的self參數了:
>>>class Class:
def method(self):
print 'I have a self!'
>>>def function():
print "I don't..."
>>>instance = Class()
>>>instance.method()
I have a self!
>>>instance.method = function
I don't...
注意,self參數并不取決于調用方法的方式,目前使用的是執行個體調用方法,可以随意使用引用同一個方法的其他變量:
>>>class Bird:
song = 'Squaawk!'
def sing(self):
print self.song
>>>bird = Bird()
>>>bird.sing()
Squaawk!
>>>birdsong = bird.sing
>>>birdsong()
Squaakw!
盡管最後一個方法調用看起來與函數調用十分相似,但是變量birdsong引用綁定方法bird.sing上,也就意味着這還是對self參數的通路(也就是說,它仍綁定到類的相同執行個體上)
再論私有化
預設情況下,程式可以從外部通路一個對象的特性。
>>>c.name
>>>c.name = 'Sir Gumby'
Python并不直接支援私有方式,而要靠程式員自己把握在外部進行特性修改的時機。畢竟在使用對象前應該知道如何使用。但是,可以用一些小技巧達到私有特性的效果。
為了讓方法或者特性變為私有,隻要在它的名字前面加上雙下劃線即可;
class Secretive:
def __inaccessible(self):
print "Bet you can't see me ..."
def accessible(self):
print "The secret message is:"
self.__inaccessible()
現在__inaccessible從外界是無法通路的/ ,而在類内部還能使用(比如從accessible)通路:
>>>s = Secretive()
>>>s.__inaccessible()
報錯
>>>s.accessible()
The secret message is:
Bet you can't see me...
類的内部定義中,所有以雙下劃線開始的名字都被“翻譯”成前面加上單下劃線和類名的形式。
>>>Secretive._Secretive__inaccessible
其實還是能在類外通路這些私有方法,盡管不應該這麼做:
>>>s._Secretive__inaccessible()
簡而言之,確定其他人不會通路對象的方法和特性是不可能的,但是這類“名稱變化術”就是他們不應該通路這些函數或者特性的強有力信号。
如果不需要使用這個種方法但是又想讓其他對象不要通路内部資料,那麼可以使用單下劃線。這不過是個人習慣,例如,前面有下劃線的名字都不會被帶星号的imports語句(from module import *)導入。
7.2.4 類的命名空間
下面兩個語句幾乎等價:
def foo(x):return x*x
foo = lamba x:x*x
在類的定義區并不隻限使用def語句:
>>>class C:
print 'class C being defined...'
Class C being defined...
>>>
看起來有點傻,但是看看下面的:
class MemberCounter:
members = 0
def init(self):
MemberCounter.members +=1
>>>m1 = MemberCounter()
>>>m1.init()
>>>MemberCounter.members
>>>m2 = MemberCounter()
>>>m2.init()
上面的代碼中,在類作用域内定義了一個可供所有成員通路的變量,用來計算類的成員數量。注意init用來初始化所有執行個體。
就像方法一樣,類作用域内的變量也可以被所有執行個體通路:
>>>m1.members
>>>m2.members
那麼在執行個體中重綁定members特性呢?
>>>m1.members = 'Two'
'Two'
新numbers值被寫到了m1的特性中,屏蔽了類範圍内的變量。這跟函數内的局部和全部變量的行為十分類似。
7.2.5 指定超類
子類可以擴充超類的定義,将其他類名寫在class語句後的圓括号内可以指定超類:
class Filter:
self.blocked = []
def filter(self,sequence):
return [ x for x in sequence if x not in self.blocked]
class SPAMFilter(Filter):
self.blocked = ['SPAM']
Filter是個過濾序列的通用類,事實上它不能過濾任何東西:
>>>f = Filter()
>>>f.init()
>>>f.filter([1,2,3])
[1,2,3]
Filter類的用處在于它可以用作其他類的基類,比如SPAMFilter類,可以将序列中的‘SPAM’過濾出去。
>>>s = SPAMFilter()
>>>s.init()
>>>s.filter(['SPAM','SPAM','eggs','b'])
['eggs','b']
注意SPAMFilter定義的兩個要點:
這裡用提供新定義的方式重寫了Filter的init定義。
filter方法的定義是從Filter類中拿過來的,是以不用重寫它的定義。
第二要點揭示了繼承的用處:我可以寫一大堆不同的過濾類,全都從Filter繼承,每一個我都可以使用已經實作的filter方法。
7.2.6 調查繼承
如果想要檢視一個類是否是另一個的子類:可以使用内建的issubclass函數:
>>>issubclass(SPAMFilter,Filter)
True
>>>issubclass(Filter,SPAMFilter)
False
如果想要知道已知類的基類,可以直接使用它的特殊特性__bases__:
>>>SPAMFilter.__bases__
(<class __main__.Filter at 0x177le40>.)
>>>Filter.__bases__
()
同樣,還能用使用isinstance方法檢查一個對象是否是一個類的執行個體:
>>>s = SPAMFilter.__bases__
>>>isinstance(s,SPAMFilter)
>>>isinstance(s,Filter)
>>>isinstance(s,str)
使用isinstance并不是好習慣,使用多态會更好一些。
可以看到,s是SPAMFilter類的成員,但是它也是Filter類的間接成員,因為SPAMFilter是Filter的子類。另外一種說法就是SPAMFilters類就是Filters類。可以從前一個例子中看到,isinstance對于類型也起作用,比如字元串類型(str)。
如果隻想知道一個對象屬于哪個類,可以使用__class__特性:
>>>s.__class__
<class __main__.SPAMFilter at 0x17070>
如果使用__metaclass__=type或從object繼承的方式來定義新式類,那麼可以使用type(s)檢視執行個體的類。
7.2.7 多個超類
找到一個類的基類,也就是暗示基類可能會多于1個:
class Calculator:
def calculate(self,expression):
self.value = eval(expression)
class Talker:
def talk(self):
print 'Hi,my value is',self.value
class TalkingCalculator(Calculator,Talker):
pass
子類自己不做任何事,它從自己的超類繼承所有的行為。它從Calculator類那裡繼承calculate方法,從Talker類那裡繼承talk方法,這樣它就成了會說話的電腦。
>>>tc = TalkingCalculator()
>>>tc.calculate('1+2*3')
>>>tc.talk()
Hi,my value is 7
這種行為稱為多重繼承,是個非常有用的工具。但除非讀者特别熟悉多重繼承,否則應該盡量避免使用,因為有些時候會出現不可預見的麻煩。
當使用多重繼承時,有個需要注意的地方,如果一個方法從多個超類繼承(也就是你有兩個具有相同名字的不同方法),那麼必須要注意一下超類的順序(在class語句中):先繼承的類中方法會重寫後繼承的類中的方法。是以如果前例中Calculator類也有個叫做talk的方法,那麼它就會重寫talker的talk方法(使其不可通路)。如果把它們的順序掉過來,像下面這樣:
class TalkingCalculator(Talker,Calculator): pass
就會讓Talter的talk方法可用了。如果超類們共享一個超類,那麼在查找給定方法或者特性時通路超類的順序稱為MRO(方法判定順序),使用的算法相當複雜。
7.2.8 接口和内省
檢查所需方法是否存在:
>>>hasattr(tc,'talk')
檢查特性是否可調用:
>>>callable(getattr(tc,'talk',None))
callable函數在python3.0已不再可用,可以使用hasattr(x,'__call__')來代替callable(x)。
設定對象的特性
>>>setattr(tc,'name','Mr. Gumby')
>>>tc.name
'Mr. Gumby'
如果要檢視對象内所有存儲的值,那麼可以使用__dict__特性。如果真的想要找到對象是由什麼組成的,可以看看inspect子產品。這是為那些想要編寫對象浏覽器以及其他需要類似功能的程式的進階使用者準備的。
7.3 一些關于面向對象設計的思考
1.将屬于一類的對象放在一起。如果一個函數操縱一個全局變量,那麼兩者最好都在類内作為特性和方法出現。
2.不要讓對象過于親密。方法應該隻關心自己執行個體的特性。讓其他執行個體管理自己的狀态。
3.要小心繼承,尤其是多重繼承。繼承機制有時很有用,但也會在某些情況下讓事情變得過于複雜。多繼承難以正确使用,更難以調試。
4.簡單就好,讓你的方法小巧。盡量将代碼控制在一頁或者一屏之内。
1.寫下問題的描述(程式要做什麼),把所有名詞,動詞和形容詞加下劃線
2.對于所有名詞,用作可能的類
3.對于所有動詞,用作可能的方法
4.對于所有的形容詞,用作可能的特性
5.把所有的方法和特性配置設定到類
本文轉自潘闊 51CTO部落格,原文連結:http://blog.51cto.com/pankuo/1661441,如需轉載請自行聯系原作者