創建自定義類
終于要創建自定義類了!下面是一個簡單的示例:
class Person:def set_name(self, name):self.name = namedef get_name(self):return self.namedef greet(self):print("Hello, world! I'm {}.".format(self.name))
這個示例包含三個方法定義,它們類似于函數定義,但位于class語句內。Person當然是類的名稱。class語句創建獨立的命名空間,用于在其中定義函數。一切看起來都挺好,但你可能想知道參數self是什么。它指向對象本身。那么是哪個對象呢?下面通過創建兩個實例來說明這一點。
foo = Person()bar = Person()foo.set_name('Luke Skywalker’)bar.set_name("Anakin Skywalker’)foo.greet()Hello, world! I'm Luke Skywalker.bar. greet()Hello, world! I'm Anakin Skywalker.
這個示例可能有點簡單,但澄清了self是什么。對foo調用set name和greet時,foo都會作為第一個參數自動傳遞給它們。我將這個參數命名為self,這非常貼切。實際上,可以隨便給這個參數命名,但鑒于它總是指向對象本身,因此習慣上將其命名為self.顯然,self很有用,甚至必不可少。如果沒有它,所有的方法都無法訪問對象本身–要操作的屬性所屬的對象。與以前一樣,也可以從外部訪問這些屬性。
foo. nameLuke Skywalkerbar.name ='Yodabar.greet()Hello, world! I'm Yoda.
提示 如果foo是一個Person實例,可將foo.greet0)視為Person.greet(foo)的簡寫,但后者的多態性更低。
屬性、函數和方法
實際上,方法和函數的區別表現在前面提到的參數self上。方法(更準確地說是關聯的方法)將其第-個參數關聯到它所屬的實例,因此無需提供這個參數。無疑可以將對象的屬性關聯到一個普通函數,但這樣就沒有特殊的self參數了。
class Class:def method(self):...print('I have a self!’)def function():print("I don't...")instance = Class()instance.method() instance.method = functioninstance.method() I don't...
請注意,有沒有參數self并不取決于是否以剛才使用的方式(如instance.method)調用方法。實際上完全可以讓另一個變量指向同一個方法。
class Bird:song =’Squaawk!’def sing(self):print(self. song)bird = Bird()bird. sing()Squaawk!birdsong = bird.singbirdsong()Squaawk!
雖然最后一個方法調用看起來很像函數調用,但變量birdsong指向的是關聯的方法bird.sing,這意味著它也能夠訪問參數self(即它也被關聯到類的實例)。
再談隱藏
默認情況下,可從外部訪問對象的屬性。再來看一下前面討論封裝時使用的示例。
c.nameSir Lancelotc.name ='Sir Gumby’c.get name()Sir Gumby
有些程序員認為這沒問題,但有些程序員(如Smalltalk雅之父)認為這違反了封裝原則。他們認為應該對外部完全隱藏對象的狀態(即不能從外部訪問它們)。你可能會問,為何他們的立場如此極端?由每個對象管理自己的屬性還不夠嗎?為何要向外部隱藏屬性?畢竟,如果能直接訪問ClosedObject(對象c所屬的類)的屬性name,就不需要創建方法setName和getName了,關鍵是其他程序員可能不知道(也不應知道)對象內部發生的情況。例如,ClosedObject可能在對象修改其名稱時向管理員發送電子郵件。這種功能可能包含在方法set_name中。但如果直接設置c.name,結果將如何呢?什么都不會發生–根本不會發送電子郵件。為避免這類問題,可將屬性定義為私有。私有屬性不能從對象外部訪問,而只能通過存取器方法(如get name和set name)來訪問。
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()Bet you can't see me .
總之,你無法禁止別人訪問對象的私有方法和屬性,但這種名稱修改方式發出了強烈的信號,讓他們不要這樣做。
類的命名空間
下面兩條語句大致等價:
def foo(x): return x*xfoo=lambda x:x*x
它們都創建一個返回參數平方的函數,并將這個函數關聯到變量foo。可以在全局(模塊)作用域內定義名稱foo,也可以在函數或方法內定義。定義類時情況亦如此:在class語句中定義的代碼都是在一個特殊的命名空間(類的命名空間)內執行的,而類的所有成員都可訪問這個命名空間。類定義其實就是要執行的代碼段,并非所有的Python程序員都知道這一點,但知道這一點很有幫助。例如,在類定義中,并非只能包含def語句。
class C:print('Class C being defined...’)Class C being defined.
這有點傻,但請看下面的代碼:
class MemberCounter:members=0def init(self):MemberCounter.members += 1m1 = MemberCounter()ml.init()MemberCounter.members1m2 = MemberCounter()m2.init()2MemberCounter.members
上述代碼在類作用域內定義了一個變量,所有的成員(實例)都可訪問它,這里使用它來計算類實例的數量。注意到這里使用了init來初始化所有實例,每個實例都可訪問這個類作用域內的變量,就像方法一樣。
m1.membersm2.members
如果你在一個實例中給屬性members賦值,結果將如何呢?
m1.members ='Two'm1.membersTwom2.members2
新值被寫入m1的一個屬性中,這個屬性遮住了類級變量。
指定超類
本章前面討論過,子類擴展了超類的定義。要指定超類,可在class語句中的類名后加上超類名,并將其用圓括號括起。
class Filter:def init(self):self.blocked=[]def filter(self,sequence):return [x for x in sequence if x not in self.blocked# SPAMFilter是Filter的子類class SPAMFilter(Filter):def init(self):#重寫超類Filter的方法initself.blocked = ['SPAM’]
Filter是一個過濾序列的通用類。實際上,它不會過濾掉任何東西。
f = Filter()f.init ()f.filter([1, 2,3])[1,2,3]
Filter類的用途在于可用作其他類(如將’SPAM’從序列中過濾掉的SPAMFilter類)的基類(超類)
s=SPAMFilter()s.init()s.filter(['SPAM'.'SPAM','SPAM','SPAM','eggs’,’SPAM,’bacon '])bacon’"eggs
請注意SPAMFilter類的定義中有兩個要點。
口 以提供新定義的方式重寫了Filter類中方法init的定義
口 直接從Filter類繼承了方法filter的定義,因此無需重新編寫其定義。
第二點說明了繼承很有用的原因:可以創建大量不同的過濾器類,它們都從Filter類派生而來,并且都使用已編寫好的方法filter。這就是懶惰的好處。
面向對象設計總結:
口將相關的東西放在一起。如果一個函數操作一個全局變量,最好將它們作為一個類的屬性和方法,。
口 不要讓對象之間過于親密。方法應只關心其所屬實例的屬性,對于其他實例的狀態,讓它們自己去管
理就好了。
口慎用繼承,尤其是多重繼承。繼承有時很有用,但在有些情況下可能帶來不必要的復雜性。要正確地
使用多重繼承很難,要排除其中的bug更難。
口 保持簡單。讓方法短小緊湊。一般而言,應確保大多數方法都能在30秒內讀完并理解。對于其余的方法,盡可能將其篇幅控制在一頁或一屏內。
確定需要哪些類以及這些類應包含哪些方法時,嘗試像下面這樣做,
(1)將有關問題的描述(程序需要做什么)記錄下來,并給所有的名詞、動詞和形容詞加上標記
(2)在名詞中找出可能的類。
(3)在動詞中找出可能的方法。
(4)在形容詞中找出可能的屬性
(5)將找出的方法和屬性分配給各個類.
有了面向對象模型的草圖后,還需考慮類和對象之間的關系(如繼承或協作)以及它們的職責。為進-步改進模型,可像下面這樣做。
(1)記錄(或設想)一系列用例,即使用程序的場景,并盡力確保這些用例涵蓋了所有的功能,
(2)透徹而仔細地考慮每個場景,確保模型包含了所需的一切。如果有遺漏,就加上;如果有不太對的地方,就修改。不斷地重復這個過程,直到對模型滿意為止。
有了你認為行之有效的模型后,就可以著手編寫程序了。你很可能需要修改模型或程序的某些部分,所幸這在Python中很容易,請不用擔心。只管按這里說的去做就好。