Python 是一門面向對象的高級語言,正因為如此,在 Python 中創(chuàng)建一個類和對象是很容易的。類的繼承機制允許多個基類,派生類可以覆蓋基類中的任何方法,方法中可以調用基類中的同名方法。對象可以包含任意數(shù)量和類型的數(shù)據(jù)。
一、類定義
語法格式如下:
class ClassName: <statement-1> . . . <statement-N>
與函數(shù)定義 (def 語句) 一樣,類定義必須先執(zhí)行才能生效。把類定義放在 if 語句的分支里或函數(shù)內部試試。
二、類對象
類對象支持兩種操作:屬性引用和實例化。
屬性引用 使用 Python 中所有屬性引用所使用的標準語法: obj.name。 有效的屬性名稱是類對象被創(chuàng)建時存在于類命名空間中的所有名稱。 因此,如果類定義是這樣的:
class MyClass: """A simple example class""" i = 12345 def f(self): return 'hello world'
那么 MyClass.i 和 MyClass.f 就是有效的屬性引用,將分別返回一個整數(shù)和一個函數(shù)對象。 類屬性也可以被賦值,因此可以通過賦值來更改 MyClass.i 的值。 __doc__ 也是一個有效的屬性,將返回所屬類的文檔字符串: “A simple example class”。
類的實例化使用函數(shù)表示法。 可以把類對象視為是返回該類的一個新實例的不帶參數(shù)的函數(shù)。 舉例來說(假設使用上述的類):
x = MyClass()
創(chuàng)建類的新 實例 并將此對象分配給局部變量 x。
實例化操作 (“調用”類對象) 會創(chuàng)建一個空對象。 許多類都希望創(chuàng)建的對象實例是根據(jù)特定初始狀態(tài)定制的。 因此一個類可能會定義名為 __init__() 的特殊方法,就像這樣:
def __init__(self): self.data = []
當一個類定義了 __init__() 方法時,類的實例化會自動為新創(chuàng)建的類實例發(fā)起調用 __init__()。 因此在這個例子中,可以通過以下語句獲得一個已初始化的新實例:
x = MyClass()
當然,__init__() 方法還有一些參數(shù)用于實現(xiàn)更高的靈活性。 在這種情況下,提供給類實例化運算符的參數(shù)將被傳遞給 __init__()。 例如,
>>>class Complex: ... def __init__(self, realpart, imagpart): ... self.r = realpart ... self.i = imagpart ... >>>x = Complex(3.0, -4.5) >>>x.r, x.i (3.0, -4.5)
三、類的方法
在類的內部,使用 def 關鍵字可以為類定義一個方法,與一般函數(shù)定義不同,類方法必須包含參數(shù) self,且為第一個參數(shù):
#!/usr/bin/python3 #類定義 class people: #定義基本屬性 name = '' age = 0 #定義私有屬性,私有屬性在類外部無法直接進行訪問 __weight = 0 #定義構造方法 def __init__(self,n,a,w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 說: 我 %d 歲。" %(self.name,self.age)) # 實例化類 p = people('ABC',10,30) p.speak()
執(zhí)行以上程序輸出結果為:
ABC 說: 我 10 歲。
四、繼承
Python 同樣支持類的繼承,如果一種語言不支持繼承,類就沒有什么意義。派生類的定義如下所示:
class DerivedClassName(BaseClassName1): <statement-1> . . . <statement-N>
名稱 BaseClassName 必須定義于可從包含所派生的類的定義的作用域訪問的命名空間中。 作為基類名稱的替代,也允許使用其他任意表達式。 例如,當基類定義在另一個模塊中時,這就會很有用處:
class DerivedClassName(modname.BaseClassName):
派生類定義的執(zhí)行過程與基類相同。派生類的實例化沒有任何特殊之處: DerivedClassName() 會創(chuàng)建該類的一個新實例。 方法引用將按以下方式解析:搜索相應的類屬性,如有必要將按基類繼承鏈逐步向下查找,如果產生了一個函數(shù)對象則方法引用就生效。
Python有兩個內置函數(shù)可被用于繼承機制:
- 使用 isinstance() 來檢查一個實例的類型: isinstance(obj, int) 僅會在 obj.__class__ 為 int 或某個派生自 int 的類時為 True;
- 使用 issubclass() 來檢查類的繼承關系: issubclass(bool, int) 為 True,因為 bool 是 int 的子類。 但是,issubclass(float, int) 為 False,因為 float 不是 int 的子類。
五、多重繼承
Python 也支持一種多重繼承。 帶有多個基類的類定義語句如下所示:
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>
需要注意圓括號中父類的順序,若是父類中有相同的方法名,而在子類使用時未指定,Python 從左至右搜索 即方法在子類中未找到時,從左到右查找父類中是否包含方法。
對于多數(shù)目的來說,在最簡單的情況下,你可以認為搜索從父類所繼承屬性的操作是深度優(yōu)先、從左到右的,當層次結構存在重疊時不會在同一個類中搜索兩次。 因此,如果某個屬性在DerivedClassName 中找不到,就會在 Base1 中搜索它,然后(遞歸地)在 Base1 的基類中搜索,如果在那里也找不到,就將在 Base2 中搜索,依此類推。
六、私有變量
那種僅限從一個對象內部訪問的“私有”實例變量在 Python 中并不存在。 但是,大多數(shù) Python 代碼都遵循這樣一個約定:帶有一個下劃線的名稱 (例如 _spam) 應該被當作是 API 的非公有部分 (無論它是函數(shù)、方法或是數(shù)據(jù)成員)。 這應當被視為一個實現(xiàn)細節(jié),可能不經通知即加以改變。
由于存在對于類私有成員的有效使用場景(例如避免名稱與子類所定義的名稱相沖突),因此存在對此種機制的有限支持,稱為 名稱改寫。 任何形式為 __spam 的標識符(至少帶有兩個前綴下劃線,至多一個后綴下劃線)的文本將被替換為 _classname__spam,其中 classname 為去除了前綴下劃線的當前類名稱。 這種改寫不考慮標識符的句法位置,只要它出現(xiàn)在類定義內部就會進行。
名稱改寫有助于讓子類重載方法而不破壞類內方法調用。例如:
class Mapping: def __init__(self, iterable): self.items_list = [] self.__update(iterable) def update(self, iterable): for item in iterable: self.items_list.append(item) __update = update # private copy of original update() method class MappingSubclass(Mapping): def update(self, keys, values): # provides new signature for update() # but does not break __init__() for item in zip(keys, values): self.items_list.append(item)
上面的示例即使在 MappingSubclass 引入了一個 __update 標識符的情況下也不會出錯,因為它會在 Mapping 類中被替換為 _Mapping__update 而在 MappingSubclass 類中被替換為 _MappingSubclass__update。
請注意,改寫規(guī)則的設計主要是為了避免意外沖突;訪問或修改被視為私有的變量仍然是可能的。這在特殊情況下甚至會很有用,例如在調試器中。
請注意傳遞給 exec() 或 eval() 的代碼不會將發(fā)起調用類的類名視作當前類;這類似于 global 語句的效果,因此這種效果僅限于同時經過字節(jié)碼編譯的代碼。 同樣的限制也適用于 getattr(), setattr() 和 delattr(),以及對于 __dict__ 的直接引用。