類(Class): 用來(lái)描述具有相同的屬性和方法的對(duì)象的集合。
10余年專注成都網(wǎng)站制作,企業(yè)網(wǎng)站制作,個(gè)人網(wǎng)站制作服務(wù),為大家分享網(wǎng)站制作知識(shí)、方案,網(wǎng)站設(shè)計(jì)流程、步驟,成功服務(wù)上千家企業(yè)。為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁(yè)設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù),專注于企業(yè)網(wǎng)站制作,高端網(wǎng)頁(yè)制作,對(duì)成都生料攪拌車等多個(gè)方面,擁有多年的網(wǎng)站維護(hù)經(jīng)驗(yàn)。
類變量:類變量在整個(gè)實(shí)例化的對(duì)象中是公用的。類變量定義在類中且在函數(shù)體之外。類變量通常不作為實(shí)例變量使用。
類有一個(gè)名為 __init__() 的特殊方法(構(gòu)造方法),該方法在類實(shí)例化時(shí)會(huì)自動(dòng)調(diào)用
self:self 代表的是類的實(shí)例,代表當(dāng)前對(duì)象的地址,而 self.class 則指向類。
類調(diào)用 Car.weight
實(shí)例化 car01=Car(5)
實(shí)例對(duì)象調(diào)用 car01.weght
我們?cè)跇?gòu)造類時(shí),Python3默認(rèn)我們繼承了object這個(gè)基類,我個(gè)人理解object就是個(gè)空的類,可以不用管為何要在括號(hào)中寫上object,這是Python3的特性,在python2中如果你沒有寫object的話不會(huì)默認(rèn)繼承了object這個(gè)基類。
同樣的我們自己希望繼承的父類只需要把objetc改為我們自己定義的類名即可。子類中可以擁有父類中所有的公有屬性和方法,但是可以通過在變量名前加下劃線使其變?yōu)樗接?,這樣子類就不可以訪問父類中的成員了。
以下三個(gè)公交車類的父類均為客車類,我們可以寫一個(gè)funcs方法使得每次調(diào)用funcs方法時(shí),傳入不同的對(duì)象以執(zhí)行不同的func方法,具體實(shí)現(xiàn)如下:
主函數(shù) :
可以看到,我將小 汽車 實(shí)例化為帶有重量為5t的一個(gè)具體對(duì)象,將客車實(shí)例化為帶有重量為20t的一個(gè)具體對(duì)象,將三個(gè)公交車實(shí)例化為帶有重量為15t的一個(gè)具體對(duì)象.
如上圖所示,我每次在調(diào)用funcs方法時(shí)都傳入了一個(gè)實(shí)例化對(duì)象,funcs根據(jù)不同的對(duì)象執(zhí)行相應(yīng)的內(nèi)部方法。
Python類的多重繼承問題深入分析
首先得說明的是,Python的類分為經(jīng)典類 和 新式類
經(jīng)典類是python2.2之前的東西,但是在2.7還在兼容,但是在3之后的版本就只承認(rèn)新式類了
新式類在python2.2之后的版本中都可以使用
經(jīng)典類和新式類的區(qū)別在于:
經(jīng)典類是默認(rèn)沒有派生自某個(gè)基類的,而新式類是默認(rèn)派生自object這個(gè)基類的:
代碼如下:
# old style
class A():pass
# new style
class A(obejct):pass
2.經(jīng)典類在類多重繼承的時(shí)候是采用從左到右深度優(yōu)先原則匹配方法的..而新式類是采用C3算法(不同于廣度優(yōu)先)進(jìn)行匹配的
3.經(jīng)典類是沒有__MRO__和instance.mro()調(diào)用的,而新式類是有的.
為什么不用經(jīng)典類,要更換到新式類
因?yàn)樵诮?jīng)典類中的多重繼承會(huì)有些問題...可能導(dǎo)致在繼承樹中的方法查詢繞過后面的父類:
代碼如下:
class A():
def foo1(self):
print "A"
class B(A):
def foo2(self):
pass
class C(A):
def foo1(self):
print "C"
class D(B, C):
pass
d = D()
d.foo1()
按照經(jīng)典類的查找順序從左到右深度優(yōu)先的規(guī)則,在訪問d.foo1()的時(shí)候,D這個(gè)類是沒有的..那么往上查找,先找到B,里面沒有,深度優(yōu)先,訪問A,找到了foo1(),所以這時(shí)候調(diào)用的是A的foo1(),從而導(dǎo)致C重寫的foo1()被繞過.
所以python引入了新式類的概念,每個(gè)基類都繼承自object并且,他的匹配規(guī)則也從深度優(yōu)先換到了C3
C3算法
C3算法是怎么做匹配的呢..在問答版塊上面討論之后,歸結(jié)如下:
C3算法的一個(gè)核心是merge.
在merge列表中,如果第一個(gè)序列mro的第一個(gè)類是出現(xiàn)在其它序列,并且也是第一個(gè),或者不出現(xiàn)其它序列,那么這個(gè)類就會(huì)從這些序列中刪除,并合到訪問順序列表中
比如:(引用問題中zhuangzebo的回答@zhuangzebo)
代碼如下:
class A(O):pass
class B(O):pass
class C(O):pass
class D(A,B):pass
class E(C,D):pass
首先需要知道 O(object)的mro(method resolution order)列表是[O,]
那么接下來(lái)是:
代碼如下:
mro(A) = [A, O]
mro(B) = [B, O]
mro(C) = [C, O]
mro(D) = [D] + merge(mro(A), mro(B), [A, B])
= [D] + merge([A, O], [B, O], [A, B])
= [D, A] + merge([O], [B, O], [B])
= [D, A, B] + merge([O], [O])
= [D, A, B, O]
mro(E) = [E] + merge(mro(C), mro(D), [C, D])
= [E] + merge([C, O], [D, A, B, O], [C, D])
= [E, C] + merge([O], [D, A, B, O], [D])
= [E, C, D] + merge([O], [A, B, O])
= [E, C, D, A, B] + merge([O], [O])
= [E, C, D, A, B, O]
然后還有一種特殊情況:
比如:
merge(DO,CO,C) 先merge的是D
merge(DO,CO,C) 先merge的是C
意思就是.當(dāng)出現(xiàn)有 一個(gè)類出現(xiàn)在兩個(gè)序列的頭(比如C) 這種情況和 這個(gè)類只有在一個(gè)序列的頭(比如D) 這種情況同時(shí)出現(xiàn)的時(shí)候,按照順序方式匹配。
新式類生成的訪問序列被存儲(chǔ)在一個(gè)叫MRO的只讀列表中..
你可以使用instance.__MRO__或者instance.mro()來(lái)訪問
最后匹配的時(shí)候就按照MRO序列的順序去匹配了
C3和廣度優(yōu)先的區(qū)別:
舉個(gè)例子就完全明白了:
代碼如下:
class A(object):pass
class B(A):pass
class C(B):pass
class D(A):pass
class E(D):pass
class F(C, E):pass
按照廣度優(yōu)先遍歷,F的MRO序列應(yīng)該是[F,C,E,B,D,A]
但是C3是[F,E,D,C,B,A]
意思是你可以當(dāng)做C3是在一條鏈路上深度遍歷到和另外一條鏈路的交叉點(diǎn),然后去深度遍歷另外一條鏈路,最后遍歷交叉點(diǎn)
新式類和經(jīng)典類的super和按類名訪問問題
在經(jīng)典類中,你如果要訪問父類的話,是用類名來(lái)訪問的..
代碼如下:
class A():
def __init__(self):
print "A"
class B(A):
def __init__(self):
print "B"
A.__init__(self) #python不會(huì)默認(rèn)調(diào)用父類的初始化函數(shù)的
這樣子看起來(lái)沒三問題,但是如果類的繼承結(jié)構(gòu)比較復(fù)雜,會(huì)導(dǎo)致代碼的可維護(hù)性很差..
所以新式類推出了super這個(gè)東西...
代碼如下:
class A():
def __init__(self):
print "A"
class B(A):
def __init__(self):
print "B"
super(B,self).__init__()
這時(shí)候,又有一個(gè)問題:當(dāng)類是多重繼承的時(shí)候,super訪問的是哪一個(gè)類呢?
super實(shí)際上是通過__MRO__序列來(lái)確定訪問哪一個(gè)類的...實(shí)際上就是調(diào)用__MRO__中此類后面的一個(gè)類的方法.
比如序列為[F,E,D,C,B,A]那么F中的super就是E,E的就是D
super和按照類名訪問 混合使用帶來(lái)的坑
代碼如下:
class A(object):
def __init__(self):
print "enter A"
print "leave A"
class B(object):
def __init__(self):
print "enter B"
print "leave B"
class C(A):
def __init__(self):
print "enter C"
super(C, self).__init__()
print "leave C"
class D(A):
def __init__(self):
print "enter D"
super(D, self).__init__()
print "leave D"
class E(B, C):
def __init__(self):
print "enter E"
B.__init__(self)
C.__init__(self)
print "leave E"
class F(E, D):
def __init__(self):
print "enter F"
E.__init__(self)
D.__init__(self)
print "leave F"
這時(shí)候打印出來(lái)是:
代碼如下:
enter F
enter E
enter B
leave B
enter C
enter D
enter A
leave A
leave D
leave C
leave E
enter D
enter A
leave A
leave D
leave F
可以看出來(lái)D和A的初始化函數(shù)被亂入了兩次!
按類名訪問就相當(dāng)于C語(yǔ)言之前的GOTO語(yǔ)句...亂跳,然后再用super按順序訪問..就有問題了
所以建議就是要么一直用super,要么一直用按照類名訪問
最佳實(shí)現(xiàn):
避免多重繼承
super使用一致
不要混用經(jīng)典類和新式類
調(diào)用父類的時(shí)候注意檢查類層次
以上便是本人對(duì)于python類的繼承的認(rèn)識(shí)了,希望對(duì)大家能有所幫助
9.5. 繼承
當(dāng)然,如果一種語(yǔ)言不支持繼承就,“類”就沒有什么意義。派生類的定義如下所示:
class DerivedClassName(BaseClassName):
命名 BaseClassName (示例中的基類名)必須與派生類定義在一個(gè)作用域內(nèi)。除了類,還可以用表達(dá)式,基類定義在另一個(gè)模塊中時(shí)這一點(diǎn)非常有用:
class DerivedClassName(modname.BaseClassName):
派生類定義的執(zhí)行過程和基類是一樣的。構(gòu)造派生類對(duì)象時(shí),就記住了基類。這在解析屬性引用的時(shí)候尤其有用:如果在類中找不到請(qǐng)求調(diào)用的屬性,就搜索基類。如果基類是由別的類派生而來(lái),這個(gè)規(guī)則會(huì)遞歸的應(yīng)用上去。
派生類的實(shí)例化沒有什么特殊之處: DerivedClassName() (示列中的派生類)創(chuàng)建一個(gè)新的類實(shí)例。方法引用按如下規(guī)則解析:搜索對(duì)應(yīng)的類屬性,必要時(shí)沿基類鏈逐級(jí)搜索,如果找到了函數(shù)對(duì)象這個(gè)方法引用就是合法的。
派生類可能會(huì)覆蓋其基類的方法。因?yàn)榉椒ㄕ{(diào)用同一個(gè)對(duì)象中的其它方法時(shí)沒有特權(quán),基類的方法調(diào)用同一個(gè)基類的方法時(shí),可能實(shí)際上最終調(diào)用了派生類中的覆蓋方法。(對(duì)于 C++ 程序員來(lái)說,Python 中的所有方法本質(zhì)上都是 虛 方法。)
派生類中的覆蓋方法可能是想要擴(kuò)充而不是簡(jiǎn)單的替代基類中的重名方法。有一個(gè)簡(jiǎn)單的方法可以直接調(diào)用基類方法,只要調(diào)用: BaseClassName.methodname(self, arguments)。有時(shí)這對(duì)于客戶也很有用。(要注意只有 BaseClassName 在同一全局作用域定義或?qū)霑r(shí)才能這樣用。)
Python 有兩個(gè)用于繼承的函數(shù):
函數(shù) isinstance() 用于檢查實(shí)例類型: isinstance(obj, int) 只有在 obj.__class__ 是 int 或其它從 int 繼承的類型
函數(shù) issubclass() 用于檢查類繼承: issubclass(bool, int) 為 True,因?yàn)?bool 是 int 的子類。
然而, issubclass(float, int) 為 False,因?yàn)?float 不是 int 的子類。
Python面向?qū)ο缶幊讨^承與多態(tài)詳解
本文實(shí)例講述了Python面向?qū)ο缶幊讨^承與多態(tài)。分享給大家供大家參考,具體如下:
Python 類的繼承
在OOP(Object Oriented Programming)程序設(shè)計(jì)中,當(dāng)我們定義一個(gè)class的時(shí)候,可以從某個(gè)現(xiàn)有的class 繼承,新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Base class、Super class)。
我們先來(lái)定義一個(gè)class Person,表示人,定義屬性變量 name 及 sex (姓名和性別);
定義一個(gè)方法print_title():當(dāng)sex是male時(shí),print man;當(dāng)sex 是female時(shí),print woman。參考如下代碼:
class Person(object):
def __init__(self,name,sex):
self.name = name
self.sex = sex
def print_title(self):
if self.sex == "male":
print("man")
elif self.sex == "female":
print("woman")
class Child(Person): # Child 繼承 Person
pass
May = Child("May","female")
Peter = Person("Peter","male")
print(May.name,May.sex,Peter.name,Peter.sex) # 子類繼承父類方法及屬性
May.print_title()
Peter.print_title()
而我們編寫 Child 類,完全可以繼承 Person 類(Child 就是 Person);使用 class subclass_name(baseclass_name) 來(lái)表示繼承;
繼承有什么好處?最大的好處是子類獲得了父類的全部屬性及功能。如下 Child 類就可以直接使用父類的 print_title() 方法
實(shí)例化Child的時(shí)候,子類繼承了父類的構(gòu)造函數(shù),就需要提供父類Person要求的兩個(gè)屬性變量 name 及 sex:
在繼承關(guān)系中,如果一個(gè)實(shí)例的數(shù)據(jù)類型是某個(gè)子類,那它也可以被看做是父類(May 既是 Child 又是 Person)。但是,反過來(lái)就不行(Peter 僅是 Person,而不是Child)。
繼承還可以一級(jí)一級(jí)地繼承下來(lái),就好比從爺爺?shù)桨职?、再到兒子這樣的關(guān)系。而任何類,最終都可以追溯到根類object,這些繼承關(guān)系看上去就像一顆倒著的樹。比如如下的繼承樹:
isinstance() 及 issubclass()
Python 與其他語(yǔ)言不同點(diǎn)在于,當(dāng)我們定義一個(gè) class 的時(shí)候,我們實(shí)際上就定義了一種數(shù)據(jù)類型。我們定義的數(shù)據(jù)類型和Python自帶的數(shù)據(jù)類型,比如str、list、dict沒什么兩樣。
Python 有兩個(gè)判斷繼承的函數(shù):isinstance() 用于檢查實(shí)例類型;issubclass() 用于檢查類繼承。參見下方示例:
class Person(object):
pass
class Child(Person): # Child 繼承 Person
pass
May = Child()
Peter = Person()
print(isinstance(May,Child)) # True
print(isinstance(May,Person)) # True
print(isinstance(Peter,Child)) # False
print(isinstance(Peter,Person)) # True
print(issubclass(Child,Person)) # True
Python 類的多態(tài)
在說明多態(tài)是什么之前,我們?cè)?Child 類中重寫 print_title() 方法:若為male,print boy;若為female,print girl
class Person(object):
def __init__(self,name,sex):
self.name = name
self.sex = sex
def print_title(self):
if self.sex == "male":
print("man")
elif self.sex == "female":
print("woman")
class Child(Person): # Child 繼承 Person
def print_title(self):
if self.sex == "male":
print("boy")
elif self.sex == "female":
print("girl")
May = Child("May","female")
Peter = Person("Peter","male")
print(May.name,May.sex,Peter.name,Peter.sex)
May.print_title()
Peter.print_title()
當(dāng)子類和父類都存在相同的 print_title()方法時(shí),子類的 print_title() 覆蓋了父類的 print_title(),在代碼運(yùn)行時(shí),會(huì)調(diào)用子類的 print_title()
這樣,我們就獲得了繼承的另一個(gè)好處:多態(tài)。
多態(tài)的好處就是,當(dāng)我們需要傳入更多的子類,例如新增 Teenagers、Grownups 等時(shí),我們只需要繼承 Person 類型就可以了,而print_title()方法既可以直不重寫(即使用Person的),也可以重寫一個(gè)特有的。這就是多態(tài)的意思。調(diào)用方只管調(diào)用,不管細(xì)節(jié),而當(dāng)我們新增一種Person的子類時(shí),只要確保新方法編寫正確,而不用管原來(lái)的代碼。這就是著名的“開閉”原則:
對(duì)擴(kuò)展開放(Open for extension):允許子類重寫方法函數(shù)
對(duì)修改封閉(Closed for modification):不重寫,直接繼承父類方法函數(shù)
子類重寫構(gòu)造函數(shù)
子類可以沒有構(gòu)造函數(shù),表示同父類構(gòu)造一致;子類也可重寫構(gòu)造函數(shù);現(xiàn)在,我們需要在子類 Child 中新增兩個(gè)屬性變量:mother 和 father,我們可以構(gòu)造如下(建議子類調(diào)用父類的構(gòu)造方法,參見后續(xù)代碼):
class Person(object):
def __init__(self,name,sex):
self.name = name
self.sex = sex
class Child(Person): # Child 繼承 Person
def __init__(self,name,sex,mother,father):
self.name = name
self.sex = sex
self.mother = mother
self.father = father
May = Child("May","female","April","June")
print(May.name,May.sex,May.mother,May.father)
若父類構(gòu)造函數(shù)包含很多屬性,子類僅需新增1、2個(gè),會(huì)有不少冗余的代碼,這邊,子類可對(duì)父類的構(gòu)造方法進(jìn)行調(diào)用,參考如下:
class Person(object):
def __init__(self,name,sex):
self.name = name
self.sex = sex
class Child(Person): # Child 繼承 Person
def __init__(self,name,sex,mother,father):
Person.__init__(self,name,sex) # 子類對(duì)父類的構(gòu)造方法的調(diào)用
self.mother = mother
self.father = father
May = Child("May","female","April","June")
print(May.name,May.sex,May.mother,May.father)
多重繼承
多重繼承的概念應(yīng)該比較好理解,比如現(xiàn)在需要新建一個(gè)類 baby 繼承 Child , 可繼承父類及父類上層類的屬性及方法,優(yōu)先使用層類近的方法,代碼參考如下:
class Person(object):
def __init__(self,name,sex):
self.name = name
self.sex = sex
def print_title(self):
if self.sex == "male":
print("man")
elif self.sex == "female":
print("woman")
class Child(Person):
pass
class Baby(Child):
pass
May = Baby("May","female") # 繼承上上層父類的屬性
print(May.name,May.sex)
May.print_title() # 可使用上上層父類的方法
class Child(Person):
def print_title(self):
if self.sex == "male":
print("boy")
elif self.sex == "female":
print("girl")
class Baby(Child):
pass
May = Baby("May","female")
May.print_title() # 優(yōu)先使用上層類的方法
如果要編寫的類是另一個(gè)類的特殊版本時(shí),那么就可以使用繼承 。原有的類稱為父類 , 新類稱為子類 。 子類繼承了父類的所有屬性和方法, 同時(shí)子類還可以自定義自己的屬性和方法。
定義子類的實(shí)例時(shí), 可以通過 子類的 __init__() 方法,給父類的所有屬性賦值。
假設(shè)有這樣的一個(gè) User 類:
接著,我們定義一個(gè) Admin 類,讓它繼承 User 類:
運(yùn)行結(jié)果:
super() 是一個(gè)特殊函數(shù), 它會(huì)把父類和子類關(guān)聯(lián)起來(lái)。因?yàn)楦割愐卜Q為超類 ( superclass),所以這個(gè)函數(shù)叫做 super。接著調(diào)用父類的 __init__() 方法, 讓子類包含父類的所有屬性。
子類除了擁有繼承父類而來(lái)的屬性和方法之外,還可以自定義子類自己的屬性和方法。
一般情況下,管理員賬號(hào)比普通賬號(hào),擁有更高級(jí)別的權(quán)限。因此,我們?yōu)?Admin 定義一個(gè)有別于 User 的 privileges 屬性,并定義一個(gè) “打印擁有的權(quán)限” 的新方法:
運(yùn)行結(jié)果:
對(duì)于繼承而來(lái)的父類方法, 如果它不符合子類所期望的行為,那么就可以對(duì)其重寫。 只要在子類中定義一個(gè)與父類同名的方法,即可實(shí)現(xiàn)重寫。
User 本身定義了一個(gè) “是否驗(yàn)證通過” 的方法,Admin 是管理員,所以需要在打印日志中特別標(biāo)注出來(lái),這時(shí)就需要重寫父類定義的方法:
運(yùn)行結(jié)果:
通過重寫父類方法, 我們就可以讓子類即可以保留或改寫從父類取其精華,棄其“糟粕”啦。
當(dāng)實(shí)體越來(lái)越復(fù)雜,對(duì)應(yīng)的類變得越來(lái)越大時(shí), 我們可以將這個(gè)大型類拆分成多個(gè)可協(xié)同工作的小類。
比如,賬號(hào)權(quán)限,其實(shí)即使是普通賬號(hào)也是有某些權(quán)限的。所以我們把權(quán)限定義為一個(gè)類,然后在 Admin 中使用它: