在這一節(jié)怀薛,結(jié)合之前學(xué)過的內(nèi)容,我們來看如何在Python中表示類之間的繼承關(guān)系豌鸡。
首先疹蛉,基于上一節(jié)的例子活箕,我們創(chuàng)建了三個文件:
- Person.py:
- Employee.py:
- Main.py:
現(xiàn)在,我們打開Employee.py可款,在其中給Person
添加一個派生類Empolyee
:
from Person import Person
class Employee(Person):
在這里例子里育韩,有三點(diǎn)值得我們注意:
第一克蚂、為了在Employee.py中使用Person
,我們在文件開始使用了from Person import Person
筋讨。由于Person
定義在Person.py中埃叭,因此from
后面的模塊名是Person
,從中悉罕,我們引入了class Person
赤屋。雖然它們同名,大家要把它們分清楚壁袄。
第二类早、我們的類定義,要和import
語句保持兩個空行嗜逻,這是Python中推薦的編碼習(xí)慣莺奔。
第三、Python中变泄,我們用(Person)
這樣的形式表示基類。在這個括號里恼琼,我們可以用逗號分隔多個基類妨蛹。但經(jīng)驗(yàn)告訴我們,多重繼承在更多時候并不會像我們想象一樣正常工作晴竞。因此蛙卤,我們還是暫時只討論單一繼承的情況。
派生類的init方法
定義了派生類之后噩死,第一件事颤难,就是定義它的__init__
方法,就像你已有的OO經(jīng)驗(yàn)一樣已维,在派生類的__init__
方法里行嗤,我們要初始化派生類和基類兩部分內(nèi)容:
class Employee(Person):
def __init__(self, work_id, name, age):
Person.__init__(self, name, age)
self.work_id = work_id
這里唯一要注意的是派生類調(diào)用基類__init__
的方法,是通過類名調(diào)用的垛耳。然后栅屏,我們就可以這樣來創(chuàng)建派生類對象了。在Main.py中堂鲜,添加下面的代碼:
from Employee import Employee
mars = Employee(11, 'mars', 30)
既然有了派生類栈雳,繼續(xù)之前,我們介紹幾個判斷類關(guān)系的函數(shù)缔莲。要判斷某個類是否是另外一個類的派生類可以使用issubclass
:
print(issubclass(Employee, Person)) # True
它的第一個參數(shù)是派生類類型哥纫,第二個參數(shù)是基類類型,最后痴奏,返回一個boolean值蛀骇。要判斷一個對象是否是某個類的對象厌秒,可以使用isinstance
:
print(isinstance(mars, Person)) # True
print(isinstance(mars, Employee)) # True
它的第一個參數(shù)是類對象,第二個參數(shù)是要判斷所屬的類型松靡,同樣返回一個boolean值简僧。要注意的是,這兩個函數(shù)都直接傳給它們類型就好了雕欺,并不用給它們傳遞類型的字符串名稱岛马。
重寫類方法
在Python里,重寫基類方法和在派生類中定義方法是沒有任何區(qū)別的屠列,我們不用像Swift一樣使用override
關(guān)鍵字啦逆,只要重定義了,就算是重寫了笛洛。
我們通過重寫幾個Python class
中內(nèi)置的方法夏志,來理解這個過程。實(shí)際上苛让,之前我們已經(jīng)干過這個事了沟蔑,就是在派生類中重寫__init__
。除此之外狱杰,class
中還有以下可以重寫的方法瘦材。
理解repr和str
每個Python class
都包含了兩個和類型的字符串表達(dá)方式有關(guān)的方法,叫做__repr__
和__str__
仿畸,默認(rèn)情況下食棕,它們返回的結(jié)果是相同的。在Main.py中错沽,添加下面的代碼:
print(mars.__repr__())
print(mars.__str__())
# <Employee.Employee object at 0x109563550>
執(zhí)行一下就會看到簿晓,它們返回的都是類似上面注釋中的結(jié)果。那么千埃,為什么要有兩個方法呢憔儿?也許你可以找到很多關(guān)于這個問題的討論。但在我看來镰禾,理解下面這兩點(diǎn)就足以讓你用對它們了:
- 首先皿曲,
__str__
是通過調(diào)用__repr__
實(shí)現(xiàn)的,因此吴侦,它的默認(rèn)實(shí)現(xiàn)確沒什么特別的用途屋休; - 其次,
__repr__
是給一個類型的開發(fā)者使用的备韧,它應(yīng)該包含關(guān)于類型的更多信息劫樟;而__str__
的描述則是針對類型的使用者使用的,它應(yīng)該盡可能易讀、友好叠艳;
有了上面這兩個原則之后奶陈,我們就可以試著改寫__str__
的實(shí)現(xiàn),讓它只返回類型名稱本身附较,例如這樣:
class Employee(Person):
# ...
def __str__(self):
return "Employee"
稍后吃粒,我們還會看到更好的獲取類型名稱的方法,這里我們只是直接返回了Employee
拒课。重新執(zhí)行下徐勃,就會發(fā)現(xiàn),此時__repr__
和__str__
的結(jié)果不同了早像。
'''
<Employee.Employee object at 0x10e388518>
Employee
'''
重定義對象的比較行為
除了重定義類型的表達(dá)方式之外僻肖,我們還可以明確指定兩個類對象的比較方法。在Python 2的時候卢鹦,這是通過重寫__cmp__
方法實(shí)現(xiàn)的臀脏。但在Python 3中,已經(jīng)不提倡這么做了冀自。Python 3中提供了一組表意更明確的函數(shù):
-
__eq__
: equal -
__ne__
: not equal -
__lt__
: less than -
__le__
: less equal -
__gt__
: greater than -
__ge__
: greater equal
首先來看揉稚,當(dāng)我們不定義這些方法的時候,比較兩個Employee
對象熬粗,執(zhí)行的是比較對象引用的操作:
mars = Employee(11, 'mars', 30)
eleven = Employee(11, 'mars', 30)
print(mars == eleven) # false
顯然窃植,mars
和eleven
引用的是不同的對象,因此這個比較結(jié)果肯定是False
荐糜。這時,如果我們希望按照對象的值比較葛超,認(rèn)為只要姓名暴氏、年齡和工號相等,那么兩個Employee
對象就相等绣张,就可以重寫__eq__
方法:
class Employee(Person):
# ...
def __eq__(self, other):
return self.name == other.name and \
self.age == other.age and \
self.work_id == other.work_id
可以看到答渔,__eq__
有兩個參數(shù),self
可以理解為==
的左操作數(shù)侥涵,other
是==
的右操作數(shù)沼撕。而它的比較邏輯,就是逐個比較Employee
的每個屬性芜飘,很簡單务豺。
定義好__eq__
之后,之前的比較結(jié)果嗦明,就變成True
了笼沥。
定義Class attributes
在這一節(jié)最后,我們來看如何給class
添加靜態(tài)屬性,在Python里奔浅,這叫做class attribute馆纳。
之前我們提到過,所有直接定義在__init__
方法里的屬性汹桦,都是類對象屬性鲁驶,它們都是綁定在某個對象上的。如果我們把屬性定義在__init__
外面舞骆,這個屬性就被所有的類對象共享了钥弯。例如,我們添加一個統(tǒng)計所有員工對象數(shù)量的counter
:
class Employee(Person):
counter = 0
可以看到葛作,counter
的初始化是在定義的時候完成的寿羞。這樣,counter
就會被所有的類對象共享了赂蠢,我們可以在每次__init__
方法被調(diào)用的時候绪穆,把它加1:
def __init__(self, work_id, name, age):
Person.__init__(self, name, age)
self.work_id = work_id
Employee.counter += 1
此時,由于我們有mars
和eleven
兩個Employee
對象虱岂,訪問counter
的時候玖院,得到的值,就是2了:
mars = Employee(11, 'mars', 30)
eleven = Employee(11, 'mars', 30)
print(Employee.counter) # 2
在上面的例子里可以看到看到第岖,訪問class attribute的時候难菌,我們要使用ClassName.Attribute
這樣的形式。
看到這蔑滓,你可能會想郊酒,這個counter
可以隨便被人修改啊,用它來計數(shù)并不靠譜键袱。沒錯燎窘,默認(rèn)情況下,class
的所有的屬性和方法都是可以被外部訪問的蹄咖。如果我們要隱藏這個屬性褐健,可以在它前面使用兩個下劃線:
class Employee(Person):
__counter = 0
def __init__(self, work_id, name, age):
Person.__init__(self, name, age)
self.work_id = work_id
Employee.__counter += 1
然后,在Main.py里澜汤,無論你用哪種方式訪問counter
:
print(Employee.counter)
print(Employee.__counter)
都會觸發(fā)AttributeError
異常蚜迅,并提示我們對應(yīng)的counter
屬性不存在。
不過俊抵,你也別太當(dāng)真谁不,因?yàn)檫@至多也就是一層設(shè)計意圖上的保障罷了,它只能提醒你自己徽诲,__counter
只能用在Employee
內(nèi)部拍谐。
因?yàn)橹虻蓿琍ython中有一個魔術(shù)前綴,就是一個下劃線加上類名轩拨,對于我們的例子來說践瓷,就是_Employee,通過它亡蓉,你就可以訪問到私有成員了晕翠。于是,在Main.py里砍濒,我們把代碼改成這樣:
print(Employee._Employee__counter) # 2
就可以恢復(fù)執(zhí)行了淋肾,而且,讀寫均可 :-)