Python基础(六)--类与对象
目錄
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? Python基礎(chǔ)(六)--類與對象
1 類與對象的基本概念
1.1 什么是對象
1.2 什么是類
1.3 類與對象的關(guān)系
2 定義與初始化
2.1 類的定義
2.2 對象的初始化
2.3 動態(tài)增加屬性方法
3 類成員
3.1 類屬性與實例屬性
3.2 類方法與實例方法
3.3 靜態(tài)方法
3.4 類與實例
4 魔法方法
5 動態(tài)屬性
6 面向?qū)ο笈c面向過程
7 面向?qū)ο蟮娜筇卣?/p>
7.1 封裝
7.2 繼承
7.3 多態(tài)
? ? ? ? ? ? ? ? ? ? ? ? ? ? Python基礎(chǔ)(六)--類與對象
1 類與對象的基本概念
1.1 什么是對象
對象具有屬性和行為,屬性多體現(xiàn)為名詞,而行為多體現(xiàn)為動詞。
1.2 什么是類
類,其實就是指一個類別,具有相同屬性與行為的所有對象構(gòu)成的一個整體。
1.3 類與對象的關(guān)系
(1)類是對象的抽象,而對象是類的具體表現(xiàn)形式
(2)類是設(shè)計的模板,而對象是該模板設(shè)計出的具體產(chǎn)物
2 定義與初始化
2.1 類的定義
使用class關(guān)鍵字定義類,如下:
class 類名:
? ? ? 類體
想讓對象具備那些功能(屬性和行為),就需要在類中指出。就需要在現(xiàn)實與Python程序中進行一種映射,對象的屬性,通過令對象綁定一個變量來實現(xiàn),而對象的行為,通過在類內(nèi)定義方法來實現(xiàn)。所謂方法,其形式與函數(shù)非常相似,只不過是定義類的內(nèi)部,關(guān)聯(lián)了某個對象而已。
self:在兩個方法中,都具有一個參數(shù):self。該參數(shù)用來表示當前的對象(調(diào)用該方法的時候,所使用的對象。簡單說,就是誰調(diào)用了這個方法,當前對象就是誰)。
如果僅僅定義了類,而沒有為類具體化(創(chuàng)建對象),是不能夠使用類中定義的功能的,通過對象調(diào)用方法時,該對象就會隱式的傳遞給方法的第一個參數(shù)(無需我們顯式傳遞)。
# 定義一個學生類 class Student:def study(self):print("學習") # 通過類創(chuàng)建對象 s = Student() # 通過對象訪問類中的成員 s.study() # 為對象增加屬性,有則修改,無則添加 s.name = "refuel" s.age = 18 print(s.name,s.age)方法與函數(shù)的區(qū)別:①方法就是函數(shù),只不過函數(shù)是面向過程的稱呼,而方法是面向?qū)ο蟮姆Q呼;②函數(shù)不依賴類對象,而方法依賴于一個類的對象
2.2 對象的初始化
在類中定義了方法,來表示對象的行為,對象也是可以具有屬性的。例如,人可以具有姓名,年齡等屬性。定義屬性,我們可以在__init__中進行定義
(1)__init__方法
__init__方法會在創(chuàng)建對象時自動得到執(zhí)行。并且,每創(chuàng)建一個對象,該方法都會執(zhí)行一次??梢栽谠摲椒ㄖ袌?zhí)行對象的初始化,定義屬性就是一件最常用的操作。而__init__方法具有初始化的能力,我們也習慣將其稱為構(gòu)造器或構(gòu)造方法。
(2)含有參數(shù)的__init__方法
不含參的__init__方法的一個不足,就是無論我們創(chuàng)建多少個對象,對象的屬性都是完全一致的。為了能夠更加靈活的創(chuàng)建對象,讓對象具有不同的屬性值,我們可以在__init__方法中增加參數(shù),然后在創(chuàng)建對象時,動態(tài)傳遞實際參數(shù)來初始化對象的屬性(不再使用固定值初始化),這樣就能夠避免創(chuàng)建屬性值完全相同的對象了。
# 對象的初始化 # self表示當前的對象,__init__(self)中的self指我們創(chuàng)建的對象,對于其他方法,當前對象只調(diào)用該方法的那個對象 class Student:# __init__方法在創(chuàng)建對象的時候會自動執(zhí)行,可以在該方法中為當前創(chuàng)建的對象增加屬性def __init__(self):self.name = "refuel"self.age = 18def study(self):print("學習") s = Student()class Student2:# 在定義__init__方法的時候,可以為該方法提供一些參數(shù),來更加靈活的進行初始化,就可以避免千篇一律的創(chuàng)建對象def __init__(self,name,age):self.name = nameself.age = agedef study(self):print("學習") s2 = Student2("refuel",18)?
2.3 動態(tài)增加屬性方法
除了在類中預(yù)先定義屬性與方法外,我們也可以動態(tài)的為對象增加屬性與方法?!?/p>
(1)動態(tài)增加的方法在調(diào)用時,需要顯式的傳遞當前對象。
(2)動態(tài)增加的屬性與方法僅對當前對象有效,對于其他對象是無效的。
# 動態(tài)增加屬性與方法,僅對當前對象有效,對其他對象無效 class Student:pass def study(self):print("動態(tài)增加的方法") s = Student s.name = "動態(tài)增加的屬性" s.study = study print(s.name) # 動態(tài)增加的方法,需要顯示的傳遞self對象 s.study(s)3 類成員
3.1 類屬性與實例屬性
(1)實例屬性:定義的屬性都會與類的對象相關(guān)聯(lián)的。對象也就是實例,創(chuàng)建類的對象,也可以稱為創(chuàng)建類的實例。
(2)類屬性:定義的屬性與當前類進行關(guān)聯(lián),不屬于對象,直接定義在類體中。
(3)實例屬性與類屬性的區(qū)別:
①屬性的綁定不同:類屬性與類進行綁定,與對象無關(guān),可以共享給所有對象訪問。實例屬性與對象綁定,每個對象有自己的實例屬性,不影響其他對象
②訪問方式不同:類屬性可以通過類訪問,也可以通過對象訪問。實例屬性只能通過對象訪問,而不能通過類訪問。通過對象訪問類屬性,僅限于訪問,無法修改類屬性的值,嘗試修改的話只不過是動態(tài)的為當前對象新增一個實例屬性,這個實例屬性與類屬性同名。
類屬性與實例屬性名稱相同時:不會產(chǎn)生錯誤。通過類訪問的,是類屬性,通過對象訪問的,可能是類屬性,也可能是實例屬性。此時要分為讀取屬性的值,還是修改屬性的值。當讀取屬性值時,首先嘗試訪問同名的實例屬性,如果實例屬性不存在,則會訪問類屬性(如果類屬性也不存在則會產(chǎn)生錯誤)。當修改屬性值時,如果實例屬性存在,則修改,如果實例屬性不存在,則新增該實例屬性,即通過對象修改屬性時,操作的屬性一定是實例屬性。
注意:最好使用類名訪問類屬性,不要通過對象訪問類屬性,避免造成不必要的混淆
# 實例屬性:每個對象獨享,彼此不受干擾;類屬性:對象共享 # 類屬性與實例屬性的區(qū)別:①屬性的綁定不同:類屬性綁定的是當前類,與當前類的任何對象都無關(guān),實例屬性綁定的是當前對象 # ②訪問方式不同:類屬性可以通過類名訪問,也可以通過對象進行訪問(只讀),因為類屬性共享給所有對象。實例屬性只能通過對象訪問,不能通過類名訪問 class Student:# 類屬性stu = "學生"def __init__(self,name):# 實例屬性self.name = name # 類名訪問類屬性 print(Student.stu) # 對象訪問類屬性 s = Student("refuel") print(s.stu) # 注意:通過類名是不能訪問實例屬性的,也就是不能Student.name # 對象無法修改類屬性,只能訪問,所以當名稱和類屬性一樣時,僅僅是為當前對象增加一個實例屬性。修改類屬性可以通過類名修改 s.stu = "學生-修改" print(s.stu) print(Student.stu) # 實例屬相是對象獨享的,彼此不受干擾 s2 = Student("張三") s3 = Student("李四") print(s2.name,s3.name)3.2 類方法與實例方法
(1)類方法:
使用@classmethod修飾的方法,這樣的方法稱為類方法。
按照慣例(并非語法要求),將類方法的第一個參數(shù)命名為cls(這類似于實例方法的第一個參數(shù)self),該參數(shù)是有特殊意義的,用來表示當前類,即調(diào)用該類方法的那個類。在調(diào)用類方法時,cls無需我們顯式傳遞(就像實例方法的self無需我們顯式傳遞一樣)。
類方法的作用:類方法與當前類綁定,與類的對象無關(guān),因此,我們通常使用類方法來作為工廠方法,用來創(chuàng)建并返回類的對象,這就相當于提供了另外一種構(gòu)造器。
(2)實例方法:
實例方法與具體的類的某個對象想關(guān)聯(lián)
類方法與實例方法,二者既可以通過類名調(diào)用,也可以通過對象來調(diào)用。不過,就像通過對象訪問類屬性一樣,通過對象來調(diào)用類方法會造成不必要的混淆(類方法與類的對象無關(guān),是綁定當前類的),因此,我們應(yīng)該總是通過類名來調(diào)用類方法。
(3)類方法與實例煩啊對屬性的訪問
在類方法中,需要通過參數(shù)(cls)訪問類屬性,而不能直接訪問類屬性。實例方法沒有cls參數(shù),可以通過類名來訪問類屬性,但是,這是一種硬編碼的方式,如果以后類名發(fā)生了改變,則我們通過類名訪問類屬性的語句都需要進行修改。這不利于程序的維護。為了降低耦合性,我們在實例方法中,可以通過__class__來動態(tài)獲取當前對象所屬的類:
self.__class__? 相當于獲取到了cls,也就是self對象所屬的類(動態(tài)獲取),如果是在類的內(nèi)部,我們還可以省略對象的限定,直接使用:__class__? ? 那么訪問方式就可以改為:? 類的對象.__class__.類屬性(__class__.類屬性? ?必須在類的內(nèi)部)。這種訪問的優(yōu)勢在于,以后就算類名發(fā)生了改變,訪問類屬性的語句也無需進行任何修改,利于程序的維護。
class Student:# 實例方法def study(self):print("實例方法")# 類方法,使用@classmethod,類方法的第一個參數(shù)固定的,按照慣例命名為cls@classmethoddef clsmethod(cls):print("類方法") # 類方法的第一個參數(shù)會隱式傳遞 Student.clsmethod() # 通過對象可以調(diào)用實例方法,也可以調(diào)用類方法。雖然對象可以訪問類方法,但是不建議這么做 s = Student() s.clsmethod() s.study() # 通過類名可以訪問類方法,也可以訪問實例方法(需要顯示傳遞對象) Student.study(s) print("--------------------------------") # 類方法與實例方法訪問屬性 class Person:# 類屬性intro = "類屬性"def __init__(self,name,age):# 實例屬性self.name = nameself.age = age# 類方法@classmethoddef eat(cls):# 類方法訪問類屬性cls.intro = "類方法修改"# 類方法不能訪問實例屬性,如果一定要訪問那就要傳入一個對象進行訪問,但是如果這樣,那就應(yīng)該定義成實例方法# 實例方法def jump(self):# 實例方法訪問實例屬性print(self.name)# 實例方法訪問類屬性print(self.intro)# 獲取對象所屬的類 self.__class__print(self.__class__.intro)# Python不支持方法的重載,可以用類方法來返回一個對象,相當于提供了一種不同的構(gòu)造器來創(chuàng)建對象@classmethoddef copy(cls,p):return cls(p.name,p.age) Person.eat() print(Person.intro) p = Person("refuel",18) p.jump() p2 = Person.copy(p) print(p2.name,p2.age)3.3 靜態(tài)方法
靜態(tài)方法使用@staticmethod修飾
靜態(tài)方法沒有cls參數(shù)。對于靜態(tài)方法而言,其既與當前類的對象無關(guān),也與當前類無關(guān),可以說,靜態(tài)方法只是定義在類的內(nèi)部,從邏輯結(jié)構(gòu)上,與當前類劃分到了一起,從功能的角度講,我們完全可以將靜態(tài)方法遷移到類的外部,作為函數(shù)來實現(xiàn)同樣的功能。
如果某個函數(shù),其實現(xiàn)的功能與某個類關(guān)系緊密,但是又不依賴于當前類(cls)與當前對象(self)時,我們就可以將該函數(shù)聲明在類的內(nèi)部,作為靜態(tài)方法。靜態(tài)方法既可以通過類名調(diào)用,也可以通過對象調(diào)用,建議通過類名調(diào)用,因為靜態(tài)方法與對象無關(guān)。
class Student:def __init__(self,name):self.name = name# 靜態(tài)方法使用@staticmethod修飾,既沒有self,也沒有cls,所以# 靜態(tài)方法的設(shè)計原則為:既不依賴當前的類屬性,也不訪問當前對象的實例屬性# 從結(jié)構(gòu)上看,可以將靜態(tài)方法移除到類外,就像函數(shù)一樣,但是# 靜態(tài)方法定義在類的內(nèi)部的意義為:有些方法完全為一個類服務(wù),具有邏輯上的關(guān)聯(lián)性@staticmethoddef statmethod():print("靜態(tài)方法")@staticmethoddef hand_in_homework(s):print(f"{s.name}交作業(yè)") Student.statmethod() s = Student("refuel") # 雖然對象可以訪問靜態(tài)方法,但是靜態(tài)方法與當前對象沒有關(guān)系,我們應(yīng)該總是通過類名來訪問靜態(tài)方法 s.statmethod() # 上交作業(yè)的函數(shù),既沒有用到類屬性,也沒有使用實例屬性(雖然用到name,是對象傳過來的,不是用self訪問的), # 但是該函數(shù)的功能是完全為Student類服務(wù)的,因此可以定義在Student方法中,成為靜態(tài)方法 def hand_in_homework(s):print(f"{s.name}交作業(yè)")Student.hand_in_homework(s)3.4 類與實例
我們在類中可以定義類屬性,類方法,靜態(tài)方法,也可以定義實例屬性與實例方法。在設(shè)計一個類的時候,該如何進行選擇呢?
(1)對于類屬性和實例屬性
看屬性是所有對象共享的(類屬性,如人有幾根手指),還是單獨一個對象獨享的(實例屬性,如人的名字年齡)
(2)實例方法,類方法和靜態(tài)方法
定義方法的目的往往就是操作類中的屬性(因為如果不操作類屬性就和類沒關(guān)系就可以定義成函數(shù)就可以了)。方法里面對實例屬相進行操作定義成實例方法,因為有self,self才能訪問到實例屬性。當與當前對象沒有關(guān)系,不需要訪問實例屬性,但是需要訪問到類中定義的類屬性,定義成類方法。靜態(tài)方法既沒有self,也沒有cls,為了提供邏輯上的緊密關(guān)聯(lián),完全是為了類服務(wù)的,既沒有用到self,也沒有用到cls,就用靜態(tài)方法
4 魔法方法
魔法方法:定義了一些特殊的方法,這些方法通常不會顯式去調(diào)用,而是在特定的場合下隱式調(diào)用執(zhí)行的。這些特殊的方法名稱都是以雙下劃線開頭,并且以雙下劃線結(jié)尾。
(1)__new__(cls, ……)
創(chuàng)建cls類的對象。通過參數(shù)的cls可知,該方法是一個類方法,但是不需要使用裝飾器來修飾(@classmethod),該方法的其他參數(shù)由構(gòu)造器傳入。該方法中,通常會調(diào)用父類的__new__方法來創(chuàng)建對象,并返回。如果該方法返回值為當前類的對象,則會繼續(xù)調(diào)用初始化方法(__init__),傳入當前創(chuàng)建的對象,否則,初始化方法不會執(zhí)行。
該方法通常不需要自行實現(xiàn),當我們實現(xiàn)該方法時,主要用來完成自定義的對象創(chuàng)建。
(2)__init__(self)
初始化方法,在創(chuàng)建對象時,如果__new__方法創(chuàng)建并返回了當前類型的對象,則會調(diào)用該方法,對new創(chuàng)建的對象執(zhí)行初始化。
(3)__del__(self)
當銷毀對象時,調(diào)用該方法。
(4)__str__(self)
當調(diào)用內(nèi)建函數(shù)str,format或print時,就會調(diào)用對象的該方法。該方法必須返回字符串(str)類型,用來描述對象的字符串表示。
(5)__repr__(self)
當調(diào)用內(nèi)建函數(shù)repr時,就會調(diào)用對象的該方法。該方法必須返回字符串(str)類型,用來描述對象的字符串表示。如果類中定義了該方法,但是沒有定義__str__方法,則當需要調(diào)用__str__方法時,也會調(diào)用該方法代替。
(6)__bytes__(self)
當調(diào)用內(nèi)建函數(shù)bytes時,會調(diào)用該方法,該方法必須返回字節(jié)(bytes)類型,用來以字節(jié)的形式描述對象。
(7)__call__(self)
當把類的對象作為方法調(diào)用時,就會調(diào)用該方法。
# 魔法方法就是使用__方法名__的命名規(guī)則,通常情況下,不會主動調(diào)用,在滿足一定條件下自動調(diào)用 class Student:# __new__是一個不使用@classmethod修飾的類方法,在創(chuàng)建對象時,首先會調(diào)用這個方法,返回當前類的對象# 如果該方法返回當前類的對象,那么接下來會調(diào)用__init__方法,對對象進行初始化,否則__init__方法不會得到執(zhí)行def __new__(cls):print("new執(zhí)行")# return cls()不能這么做會無限遞歸# 通過super調(diào)用__new__就不會出現(xiàn)無限遞歸return super().__new__(cls)# 初始化方法,在創(chuàng)建對象時,如果__new__方法創(chuàng)建并返回了當前類型的對象,則會調(diào)用該方法,對new創(chuàng)建的對象執(zhí)行初始化。def __init__(self):print("init方法")# 當前對象銷毀時調(diào)用這個方法,可以執(zhí)行一些清理的工作def __del__(self):print("對象銷毀")# 使用內(nèi)建函數(shù)str,format,print時調(diào)用,需要返回一個str類型的對象def __str__(self):return "Student類型對象"# 在使用內(nèi)建函數(shù)repr時調(diào)用,需要返回一個str類型的對象# __str__與__repr__的區(qū)別:都返回字符串,__str__返回的是讓人容易閱讀的格式,__repr__通常返回# 的是面向Python解釋器的。當需要調(diào)用__str__的場合,沒有定義__str__,就是要__repr__替代def __repr__(self):return "<Student class>"# 當使用內(nèi)建函數(shù)bytes時調(diào)用,返回字節(jié)類型def __bytes__(self):return b"byte student class"# 當將對象當成函數(shù)調(diào)用會執(zhí)行該方法def __call__(self):print("對象當函數(shù)調(diào)用") s = Student() print(s) print(str(s)) print(repr(s)) print(bytes(s)) s() del s5 動態(tài)屬性
Python提供了如下的內(nèi)建函數(shù),用來操作對象的屬性。
(1)hasattr(obj,name):判斷obj對象中是否存在name指定的屬性名,存在返回True,否則返回False。
(2)setattr(obj,name,value):將obj對象的name屬性設(shè)置為value值。相當于執(zhí)行如下的操作:obj.name = value。如果name屬性存在,則覆蓋,如果不存在,則為對象obj新增name屬性。
(3)getattr(obj,name):返回obj對象的name屬性值,相當于執(zhí)行如下的操作:obj.name。如果name屬性值存在,則返回屬性值,如果屬性值不存在,返回default參數(shù)值,如果屬性值不存在,default參數(shù)也不存在,則產(chǎn)生AttributeError。
(4)delattr(obj,name,value):刪除obj對象的name屬性。相當于執(zhí)行如下的操作:del obj.name.如果指定的屬性不存在,則會產(chǎn)生AttributeError。
如果使用obj.name的方式訪問屬性的話,name必須是編寫代碼時已知的內(nèi)容,而不能通過字符串來指定。以上函數(shù)的優(yōu)勢在于,我們可以通過字符串的形式來操作屬性,而無需在編寫代碼的時候就知道具體的屬性名稱。因此,這就給我們操作屬性帶來了一定的靈活性。我們完全可以在運行時以字符串的形式傳遞屬性名,進行操作。
# 魔法方法就是使用__方法名__的命名規(guī)則,通常情況下,不會主動調(diào)用,在滿足一定條件下自動調(diào)用 class Student:pass s = Student() # 參數(shù)1:對象,參數(shù)2:屬性名 判斷對象中是否存在第二個參數(shù)指定的屬性名,存在返回True,否則False print(hasattr(s,"name")) # 參數(shù)1:對象,參數(shù)2:屬性名 返回對象對應(yīng)的第二個參數(shù)指定的屬性,不存在產(chǎn)生異常,可以指定第三參數(shù),不存在時返回的默認值 print(getattr(s,"name","沒屬性的默認值")) # 參數(shù)1:對象,參數(shù)2:屬性名 參數(shù)三:屬性值 print(setattr(s,"age",18)) delattr(s,"age")6 面向?qū)ο笈c面向過程
編程方式可分為面向?qū)ο蠛兔嫦蜻^程。
面向過程:體現(xiàn)的是一種流程設(shè)計,即按部就班的完成每一個環(huán)節(jié)。為了實現(xiàn)每個環(huán)節(jié)的可復(fù)用性,環(huán)節(jié)都是通過調(diào)用一個個函數(shù)來實現(xiàn)的。因此,在面向過程的編程中,通常都是定義大量的函數(shù),按照需求順序依次進行調(diào)用。
面向?qū)ο?#xff1a;程序不再是以函數(shù)為單位,而是以對象為單位,通過調(diào)用對象提供的方法處理。
7 面向?qū)ο蟮娜筇卣?/h2>
7.1 封裝
(1)什么是封裝?
封裝:隱藏具體的實現(xiàn)細節(jié),只提供給外界調(diào)用的接口。只要提供給外界的接口不變即可,底層細節(jié)改變的時候,不會對外界造成影響。
(2)私有成員
在程序中可以通過變量私有化做封裝,使得在類中定義的變量,僅能在當前類(定義變量的類)中訪問,而不能在類的外部訪問。如果一個屬性名(或方法名)使用兩個下劃線(__)開頭,并且少于兩個下劃線結(jié)尾,則這樣的屬性(方法)就稱為私有屬性(方法)。私有屬性(方法)只能在類的內(nèi)部訪問。
注意:其他語言定義成私有了是真正訪問不到,但是python的私有只不過是進行了偽裝而已。當在類中定義私有成員時,在程序內(nèi)部會將其處理成_類名 + 原有成員名稱的形式。也就是會將私有成員的名字進行一下偽裝而已,如果使用處理之后的名字,還是能夠進行訪問的。
# 私有變量以__開頭,但是不能以兩個或更多的_進行結(jié)尾 class Student:def __init__(self):self.desc = "學生"# 私有成員(私有實例屬性)self.__books = 5# 在定義成員類的內(nèi)部可以訪問私有成員print(self.__books)# 提供公有方法訪問私有的屬性def set_books(self,books):self.__books = books# 提供公有反復(fù)噶獲取私有的屬性def get_books(self):return self.__books s = Student() print(s.desc) # print(s.__books) AttributeError: 'Student' object has no attribute '__books' 私有成員不能在類外進行訪問 # 通過類提供的公有方法訪問私有成員 s.set_books(10) print(s.get_books())(3)property
在客戶端訪問時,公有的方法總不如變量訪問那樣簡便,為了既可以直接訪問變量,又能夠?qū)崿F(xiàn)很好的封裝,做到信息隱藏,可以使用property的兩種方式來實現(xiàn)封裝。
property有兩種方式來實現(xiàn)封裝:①使用property函數(shù);②使用@property裝飾器
# 使用property定義一個屬性(偽裝成一個屬性) # 使用property既可以提供訪問的便利性,也可以提供很好的封裝性 # 1使用property函數(shù) class Student:def __init__(self):self.__books = 2# property的4個參數(shù)# get方法:在讀取property值的時候調(diào)用# set方法:在修改property值的時候調(diào)用# del方法:在刪除property的時候調(diào)用# doc:給出該property的說明文檔def getBooks(self):print("調(diào)用get方法")return self.__booksdef setBooks(self,books):print("調(diào)用set方法")self.__books = booksdef delBooks(self):print("調(diào)用del方法")del self.__booksbooks = property(getBooks,setBooks,delBooks,"書本的數(shù)量") s = Student() print(s.books) s.books = 10 print(s.books) del s.books print("----------------------") # 2 使用property裝飾器 class Student2:def __init__(self):self.__books = 2# 將方法名偽裝成一個屬性,獲取屬性會調(diào)用,說明文檔也寫在該方法中@propertydef books(self):"""books的說明文檔"""return self.__books# 設(shè)置屬性會調(diào)用該方法@books.setterdef books(self,books):self.__books = books# 刪除屬性調(diào)用該方法@books.deleterdef books(self):del self.__books s2 = Student() print(s2.books) s2.books = 10 print(s2.books) del s2.books?
7.2 繼承
(1)什么是繼承
繼承體現(xiàn)的是一種一般與特殊的關(guān)系。如果兩個類A與B,A(蘋果)是一種特殊的B(水果),我們就稱,特殊的類A繼承了一般的類B(蘋果繼承了水果)。對于一般的類型(水果),我們稱為父類,而對于特殊的類型(蘋果),我們稱為子類。當子類繼承了父類,子類就可以繼承父類中定義的成員(變量,方法等),就好像在子類中自己定義的一樣。繼承的語法為:
class B(A):
? ? ? 類體
這樣,B類就繼承了A類,B就成為一種特殊的A,B類就會繼承A類的成員。
如果沒有顯式指定繼承的類型,則類隱式繼承object類,object是Python中最根層次的類,所有類都是object的直接或間接子類。
# 通過繼承可以將公共的功能提取出來,放入父類中,然后每一個子類去繼承父類 # 那就無需將公共的功能在子類中分別實現(xiàn),從而實現(xiàn)代碼的重用 class Animal:def desc(self):print("動物")def eat(self):print("動物需要進食") # 繼承:定義類是指定繼承的父類 class cat(Animal):# 子類重寫父類的功能def desc(self):print("我是一只貓")# 子類增加自己特有的方法def catchMouse(self):print("抓老鼠") a = Animal() a.desc() c = cat() c.desc() c.catchMouse()(2)為什么要使用繼承
我們有時候可能還需要對現(xiàn)有類進行調(diào)整,這體現(xiàn)在:①現(xiàn)有類的提供的功能不充分,我們需要增加新的功能。②現(xiàn)有類的提供的功能不完善(或?qū)ξ覀儊碚f不適合),我們需要對現(xiàn)有類的功能進行改造(就是重寫)。
(3)成員的繼承
子類可以繼承父類的成員,父類中聲明的類屬性、實例屬性、類方法、實例方法與靜態(tài)方法,子類都是可以繼承的。但是,對于實例屬性有些特別。因為實例屬性是定義__init__方法中,實現(xiàn)與對象(self)的綁定。如果子類沒有定義init方法,就會繼承父類的init方法,從而在創(chuàng)建對象時,調(diào)用父類的init方法,會將子類對象傳遞到父類的init方法中,從而實現(xiàn)子類對象與父類init方法中實例屬性的綁定。但是,如果子類也定義了自己的init方法(重寫),則父類init方法就不會得到調(diào)用,這樣,父類init方法中定義的實例屬性就不會綁定到子類對象中。
如果子類與父類的初始化方式完全相同,子類只要繼承父類__inir__方法就可以的。但有的時候,子類可能會增加自己的屬性,此時,就不能完全套用父類的初始化方式。
雖然父類的__init__不完全適合子類,但是也并非完全不適合子類。因為兩個類還是存在相同的屬性的,因此,我們應(yīng)該充分利用現(xiàn)有的功能,不要重復(fù)的實現(xiàn)。實現(xiàn)方式就是,我們在子類的構(gòu)造器中,去調(diào)用父類的構(gòu)造器,完成公共屬性的初始化,然后在子類構(gòu)造器中,再對子類新增屬性進行初始化。我們可以這樣來調(diào)用父類的構(gòu)造器:
? ? ? ? ? super().__init__(父類構(gòu)造器參數(shù))
# 成員的繼承 class Animal:classAttr = 6def __init__(self):self.instanceAttr = 2def instanceMethod(self):print("實例方法")@classmethoddef classMethod(cls):print("類方法")@staticmethoddef staticMethod():print("靜態(tài)方法")# 私有成員是可以被子類繼承的,不過繼承的不是原來的名字,而是偽裝之后的名字def __pri(self):print("私有方法") class cat(Animal):# 父類的初始化不完全適用于子類,子類定義自己的初始化方法def __init__(self,name,age):#調(diào)用父類的__init__方法super().__init__()self.name = nameself.age = agedef private(self):# 繼承私有成員self._Animal__pri() c = cat("aa",18) print(cat.classAttr) print(c.instanceAttr,c.name,c.age) cat.classMethod() c.instanceMethod() cat.staticMethod() c.private()(4)繼承的兩個內(nèi)建函數(shù)isinstance與issubclass
class Animal:pass class Cat(Animal):pass a = Animal() c = Cat() # instance(o,t)參數(shù)一:對象,參數(shù)二:類型。判斷第一個指定的參數(shù)是否是第二個參數(shù)類型(包括父類型) print(isinstance(a,Animal)) print(isinstance(c,Cat)) print(isinstance(c,Animal)) print(isinstance(c,object)) print(isinstance(a,Cat)) print("------------") # issubclass()判斷第一個參數(shù)(類型)是否是第二個參數(shù)的類型(或者是其子類型) print(issubclass(Cat,Cat)) print(issubclass(Cat,Animal)) print(issubclass(Animal,Cat)) print(issubclass(Animal,Animal)) print(issubclass(Cat,object))(5)重寫
當子類繼承了父類,子類就可以繼承父類的成員。然而,父類的成員未必完全適合于子類(例如鳥會飛,但是鴕鳥不會飛),此時,子類就將父類中的成員進行調(diào)整,以實現(xiàn)適合子類的特征與功能。我們將父類中的成員在子類中重新定義的現(xiàn)象,稱為重寫。當通過子類對象訪問成員時,如果子類重寫了父類的成員,將會訪問子類自己的成員。否則(沒有重寫)訪問父類的成員。
如果子類需要訪問父類的成員,可以通過:super().父類成員? ? ?進行訪問。
class Animal:def eat(self):print("進食") class Cat(Animal):def eat(self):super().eat()print("吃魚") c = Cat() c.eat()(6)多重繼承
在Python中,類是支持多重繼承的,即一個子類可以繼承多個父類。java只能單繼承。
當子類繼承多個父類時,子類會繼承所有父類的成員。當多個父類含有相同名稱的成員時,我們可以通過具體的父類名,來指定要調(diào)用哪一個父類的成員(如果是實例方法,需要顯式傳遞一個類對象),這樣就能夠避免混淆。
同名的訪問:由Python中的方法解析順序(MRO)來決定,訪問某個類的成員時,成員的搜索順序。該順序大致如下:①如果是單繼承(繼承一個父類),則比較簡單,搜索順序從子類到父類,一直到object為止。②如果是多繼承(繼承多個父類),則子類到每個父類為一條分支,按照繼承的順序,沿著每一條分支,從子類到父類進行搜索,一直到object類為止(深度優(yōu)先)。③在搜索的過程中,子類一定會在父類之前進行搜索。
成員的搜索順序為:F -> D -> B -> E -> C -> A -> object
# 在python中一個類可以繼承多個父類 class Animal:def desc(self):print("動物")class Felid:def desc(self):print("貓科動物") # 貓同時繼承兩個類型 class Cat(Animal,Felid):def meth(self):self.desc() c = Cat() c.meth()成員搜索順序:之前使用的super類,就是根據(jù)方法解析順序來查找指定類中成員,但是有一點例外,就是super對象不會在當前類中搜索,即從方法解析順序的第二個類開始。super的構(gòu)造器會返回一個代理對象,該代理對象會將成員訪問委派給相關(guān)的類型(父類或兄弟類)。
(7)方法解析順序MRO
每個類都提供了mro方法與__mro__屬性,可以獲得該類的方法解析順序。mro方法與__mro__屬性獲取的元素內(nèi)容是相同的,只不過前者返回的是列表(list)類型,后者返回的是元組(tuple)類型。
class A:x = "A" class B(A):x = "B" class C(A):x = "C" class D(B):x = "D" class E(C):x = "E" class F(D,E):x = "F" # 獲取方法的解析順序 print(F.mro()) print(F.__mro__) # super()返回一個委派對象,通過委派對象訪問類中的成員,委派的順序 # 與mro的順序是一致的,除了排除自身7.3 多態(tài)
多態(tài):指運行時表現(xiàn)的多種形態(tài),運行時會根據(jù)對象的真正類型來決定調(diào)動哪一個類型的成員。
因為Python是鴨子類型,因此多態(tài)在Python中體現(xiàn)的不明顯,不像一些定義變量時需要指定明確類型的語言(例如Java,C++等)中那么明顯。
class Father:def talk(self):print("father") class Son:def talk(self):print("son") def talk(m):m.talk() f = Father() s = Son() talk(f) talk(s) """ 其他指定類型的語言 Father f = new Father() Son s = new Son() talk(f) talk(s) def talk(Father f)f.talk """?
總結(jié)
以上是生活随笔為你收集整理的Python基础(六)--类与对象的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android p dp5,谷歌释出An
- 下一篇: php mqtt qos,Mqtt Qo