[TOC]
面向?qū)ο?/h1>
繼承與派生
繼承
- 什么是繼承慢逾?繼承是一種創(chuàng)建新的類的方式
class A:
pass
class B(A):
pass
class A:
pass
class B(A):
pass
在python中肥橙,新建的類可以繼承自一個或者多個父類摸恍,原始類稱為基類或者超類赶诊,新建的類稱為派生類或者子類
python中類的繼承分為吱型,單繼承和多繼承逸贾。
查看繼承的方法B.__bases__
如果沒有指定基類,python的類會默認集成object類,object是所有python類的基類铝侵。
- 如何繼承-》如何尋找繼承關(guān)系
繼承是一種‘是’的關(guān)系:
人類灼伤、豬類、狗類都繼承動物類咪鲜,因而他們都是動物
抽象:抽取類似或者說比較相似的部分
老師是這樣定義這個抽象的狐赡,并且還介紹了一個抽象類的概念abc
,但是這個抽象的意思翻譯過來就是尋找對象的相似點疟丙,將對象根據(jù)特征進行歸納總結(jié)為類的過程颖侄。
在找到相似點(也就是“抽象”)之后,其實對象往往還有細分部分的一些特征享郊,并且會有層級地可以劃分類別
例如:
動物>>>
人/豬/狗>>>
奧巴馬/梅西 // 麥兜/豬八戒 // 史努比/丁丁
由此將對象原本模糊的關(guān)注點隔離開览祖,降低復(fù)雜度
繼承:基于抽象的結(jié)果,通過編程語言去實現(xiàn)炊琉,經(jīng)歷抽象的過程展蒂,通過繼承去表達出抽象的結(jié)構(gòu)
- 為什么要用繼承?
解決代碼重用問題:
在了解了“繼承就是‘是’的關(guān)系”之后苔咪,我們會發(fā)現(xiàn)锰悼,我們定義劃分開的父子類之間,其實是有些包含的团赏、并且共有的功能箕般。
例如“人”這個類,都是可以“走”馆里、“說”的隘世;動物這個個類,都可以有“吃”的動作鸠踪。
由此丙者,我們就可以在父類中定義共同有的方法、屬性营密,并且讓所有子類都享用械媒。
class hero:
def __init__(self, name,aggressivity, life_value):
self.name = name
self.aggressivity = aggressivity
self.life_value = life_value
def attack(self,enemy):
print('hero attack')
class garen(hero):
def __init__(self,name,aggressivity,life_value,script):
hero.__init__(self, name,aggressivity, life_value)
self.script = script
這段代碼中,我們就定義了一個英雄類评汰,他的子類蓋倫可以繼承一些他定義的屬性
hero.__init__(self, name,aggressivity, life_value)
這就節(jié)約了蓋倫類定義的代碼纷捞,并且給以后定義新的子類英雄提供了便利。
但實際上被去,這種調(diào)用方法跟繼承并沒有關(guān)系主儡,這種類名.函數(shù)的調(diào)用方法其實可以調(diào)用所有其他類的方法。而且當(dāng)子類修改父類之后惨缆,代碼還要隨之更改糜值,非常麻煩丰捷,并不推薦這種方式。
這就是代碼的重用
當(dāng)然子類也可以有自己的屬性寂汇,例如上面代碼中的self.script = script
病往。
如果說子類自己定義的屬性或者方法和父類中有重名,那么就以子類自己定義的為準(zhǔn)骄瓣。
上面使用hero.__init__(self, name,aggressivity, life_value)
就是在調(diào)用父類函數(shù)的方法作為子類定義函數(shù)的參數(shù)停巷,在使用父類函數(shù)的時候,要記得不能再使用self
榕栏,而是跟上要調(diào)用的類的名稱畔勤。
派生:
子類繼承了父類的屬性,然后衍生出自己新的屬性臼膏,
如果子類衍生出的新的屬性與父類的某個屬性名字相同硼被,
那么再調(diào)用子類的這個屬性,就以子類自己這里的為準(zhǔn)了
繼承順序
class A(object):
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D,E):
# def test(self):
# print('from F')
pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有這個屬性可以查看線性列表渗磅,經(jīng)典類沒有這個屬性
#新式類繼承順序:F->D->B->E->C->A
#經(jīng)典類繼承順序:F->D->B->A->E->C
#python3中統(tǒng)一都是新式類
#pyhon2中才分新式類與經(jīng)典類
作業(yè)分析:
- 新式類的繼承順序總結(jié):
C3算法非廣度優(yōu)先,利用MRO順序進行繼承
检访?
“不徹底”始鱼,“從左往右”
向父類遍歷,到達根類的前一個類就往另外的支線遍歷 - 經(jīng)典類中的集成順序總結(jié):
深度優(yōu)先
“徹底”脆贵,“從左往右”
向父類遍歷医清,到達根類才向另外的支線遍歷
繼承原理
每定義一個淚,python會計算出一個方法解析順序(MRO)列表卖氨,這個MRO列表就是一個簡單的所有基類的線性順序列表:
PyObject* tp_mro
Tuple containing the expanded set of base types, starting with the type itself and
ending with object, in Method Resolution Order.
以元組的形式返回所有基類的擴展的集合会烙,從他本身的類開始,到object類結(jié)束
This field is not inherited; it is calculated fresh by PyType_Ready().
這個字段并不是通過繼承得到的筒捺,而是通過by PyType_Ready()方法計算更新的
>>> class tsr:
... pass
...
>>> str.__mro__
(<class 'str'>, <class 'object'>)
>>> str.mro()
[<class 'str'>, <class 'object'>]
為了實現(xiàn)繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止柏腻。
而這個MRO列表的構(gòu)造是通過一個C3線性化算法來實現(xiàn)的。我們不去深究這個算法的數(shù)學(xué)原理,它實際上就是合并所有父類的MRO列表并遵循如下三條準(zhǔn)則:
- 子類會先于父類被檢查
- 多個父類會根據(jù)它們在列表中的順序被檢查
- 如果對下一個類存在兩個合法的選擇,選擇第一個父類
子類調(diào)用父類的方法(super)
方法一:
父類名.父類方法()
不多說系吭,參考上面的引用
方法二:
super()
當(dāng)你使用super()函數(shù)時,Python會在MRO列表上繼續(xù)搜索下一個類五嫂。只要每個重定義的方法統(tǒng)一使用super()并只調(diào)用它一次,那么控制流最終會遍歷完整個MRO列表,每個方法也只會被調(diào)用一次(注意注意注意:使用super調(diào)用的所有屬性,都是從MRO列表當(dāng)前的位置往后找肯尺,千萬不要通過看代碼去找繼承關(guān)系沃缘,一定要看MRO列表)
思考題:
上圖的代碼以及運行結(jié)果已經(jīng)放在圖中,下面的代碼使用的是糾正使用super()的代碼:
class A(object):
def __init__(self):
print("enter A")
super(A, self).__init__() # new
print("leave A")
class B(object):
def __init__(self):
print("enter B")
super(B, self).__init__() # new
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")
super(E, self).__init__() # change
print("leave E")
class F(E, D):
def __init__(self):
print("enter F")
super(F, self).__init__() # change
print ("leave F")
f = F()
小結(jié):
- super并不是一個函數(shù)则吟,是一個類名槐臀,形如super(B, self)事實上調(diào)用了super類的初始化函數(shù),產(chǎn)生了一個super對象氓仲;
- super類的初始化函數(shù)并沒有做什么特殊的操作水慨,只是簡單記錄了類類型和具體實例得糜;
- super(B, self).func的調(diào)用并不是用于調(diào)用當(dāng)前類的父類的func函數(shù);
- Python的多繼承類是通過mro的方式來保證各個父類的函數(shù)被逐一調(diào)用讥巡,而且保證每個父類函數(shù)只調(diào)用一次(如果每個類都使用super)掀亩;
- 混用super類和非綁定的函數(shù)是一個危險行為,這可能導(dǎo)致應(yīng)該調(diào)用的父類函數(shù)沒有調(diào)用或者一個父類函數(shù)被調(diào)用多次欢顷。
組合
組合對比繼承來說槽棍,也是用來重用代碼,但是組合描述的是一種“有”的關(guān)系
老師有課程
學(xué)生有成績
學(xué)生有課程
學(xué)校有老師
學(xué)校有學(xué)生
#定義課程類
class Course:
def __init__(self,name,price,period):
self.name=name
self.price=price
self.period=period
#定義老師類
class Teacher:
def __init__(name,course):
self.name=name
self.course=course
#定義學(xué)生類
class Student:
def __init__(name,course):
self.name=name
self.course=course
#學(xué)生類和老師類中抬驴,都有關(guān)聯(lián)到課程這個屬性
#所以實例化一項課程
python=Course('python',15800,'7m')
#在實例化學(xué)生和老師的時候炼七, 將實例化后的課程作為參數(shù)傳入
t1=Teacher('egon',python)
s1=Student('alex',python)
#這樣學(xué)生和老師就獲得了課程實例的參數(shù),并且可以引用
print(s1.course.name)
print(s1.course.period)
上述代碼就表示了一個“組合”的過程布持。類之間通過實例化傳參的過程豌拙,來互相獲取一些需要的數(shù)據(jù),并且建立關(guān)系题暖。
接口
繼承有兩種用途:
一:繼承基類的方法按傅,并且做出自己的改變或者擴展(代碼重用)
二:聲明某個子類兼容于某基類,定義一個接口類Interface胧卤,接口類中定義了一些接口名(就是函數(shù)名)且并未實現(xiàn)接口的功能唯绍,子類繼承接口類,并且實現(xiàn)接口中的功能
接口的概念:
=================第一部分:Java 語言中的接口很好的展現(xiàn)了接口的含義: IAnimal.java
/*
* Java的Interface很好的體現(xiàn)了我們前面分析的接口的特征:
* 1)是一組功能的集合,而不是一個功能
* 2)接口的功能用于交互,所有的功能都是public,即別的對象可操作
* 3)接口只定義函數(shù),但不涉及函數(shù)實現(xiàn)
* 4)這些功能是相關(guān)的,都是動物相關(guān)的功能,但光合作用就不適宜放到IAnimal里面了 */
package com.oo.demo;
public interface IAnimal {
public void eat();
public void run();
public void sleep();
public void speak();
}
=================第二部分:Pig.java:豬”的類設(shè)計,實現(xiàn)了IAnnimal接口
package com.oo.demo;
public class Pig implements IAnimal{ //如下每個函數(shù)都需要詳細實現(xiàn)
public void eat(){
System.out.println("Pig like to eat grass");
}
public void run(){
System.out.println("Pig run: front legs, back legs");
}
public void sleep(){
System.out.println("Pig sleep 16 hours every day");
}
public void speak(){
System.out.println("Pig can not speak"); }
}
=================第三部分:Person2.java
/*
*實現(xiàn)了IAnimal的“人”,有幾點說明一下:
* 1)同樣都實現(xiàn)了IAnimal的接口,但“人”和“豬”的實現(xiàn)不一樣,為了避免太多代碼導(dǎo)致影響閱讀,這里的代碼簡化成一行,但輸出的內(nèi)容不一樣,實際項目中同一接口的同一功能點,不同的類實現(xiàn)完全不一樣
* 2)這里同樣是“人”這個類,但和前面介紹類時給的類“Person”完全不一樣,這是因為同樣的邏輯概念,在不同的應(yīng)用場景下,具備的屬性和功能是完全不一樣的 */
package com.oo.demo;
public class Person2 implements IAnimal {
public void eat(){
System.out.println("Person like to eat meat");
}
public void run(){
System.out.println("Person run: left leg, right leg");
}
public void sleep(){
System.out.println("Person sleep 8 hours every dat");
}
public void speak(){
System.out.println("Hellow world, I am a person");
}
}
=================第四部分:Tester03.java
package com.oo.demo;
public class Tester03 {
public static void main(String[] args) {
System.out.println("===This is a person===");
IAnimal person = new Person2();
person.eat();
person.run();
person.sleep();
person.speak();
System.out.println("\n===This is a pig===");
IAnimal pig = new Pig();
pig.eat();
pig.run();
pig.sleep();
pig.speak();
}
}
java中的interface
接口的概念解釋和使用:
這里有一篇解釋接口的文章枝誊,其中關(guān)于什么時候使用繼承况芒,什么時候用(組合)接口的方面,我看的模模糊糊叶撒。但是說不定以后能看懂:http://blog.csdn.net/xiaoxiongli/article/details/2791853
其中有一段解釋說的很好绝骚,用來描述接口的概念真的妙到毫顫,特意摘下來整理祠够,留作以后裝逼用:
一馬平川 19:58:54
接口是對類的抽象压汪,我如果直接跟你說接口編程,你一定不理解哪审,或者說很難理解蛾魄,因為接口本身是很抽象的東西,現(xiàn)在我舉例跟你說:
“電源插座就是接口”:
比方說湿滓,插座有兩孔的滴须,有三孔的,不同的插頭需要不同的插座叽奥。
接口就描述了能適應(yīng)的插頭范圍現(xiàn)在有一種插座是三孔的扔水,但既可以插三孔的,也可插兩孔的朝氓,知道么魔市?
那么主届,我們可以說,這個插座設(shè)計的好待德,因為他能適用更廣的范圍君丁。
當(dāng)然,這個范圍不能超出電源插座這個概念将宪。
如果是用來插筆绘闷,做筆筒用,那也不適合较坛。
如果電源插座不但能適用兩孔和三孔的插頭印蔗,還能適用筆的話,那么我們可以肯定的說丑勤,這個接口設(shè)計的太差了华嘹。因為接口(插座)的設(shè)計應(yīng)該是對某一類事物的抽象。而且法竞,接口(插座)實現(xiàn)以后耙厚,實現(xiàn)該接口的類(插頭)必須符合接口的定義(插座和插口匹配),而且需要完全符合岔霸,一點不符合都不行颜曾。
所以實現(xiàn)某個接口的類,必須重寫接口中定義的所有方法秉剑。如果你覺得該方法不需要實現(xiàn),你可以留空稠诲,但必須重寫侦鹏。
看我這句話:“接口只定義了方法的原型,即參數(shù)和方法名以及返回值臀叙,集成接口的類需要實現(xiàn)它略水。”
而且劝萤,接口(插座)實現(xiàn)以后渊涝,實現(xiàn)該接口的類(插頭)必須符合接口的定義(插座和插口匹配)。其實床嫌,你會發(fā)現(xiàn)插座生產(chǎn)出來后跨释,如果某電器的插頭和插座不匹配,那么就無法使用該電器了厌处。實際上鳖谈,你在設(shè)計一個接口的時候,很難想到要怎么去設(shè)計阔涉,盡管你知道集成這個接口的類是怎么樣的缆娃。就像如果你開一個工廠生產(chǎn)插線板捷绒,你在不知道電器,或不完全知道電器的插頭如何設(shè)計的時候贯要,你是很難生產(chǎn)出能用的插線板的暖侨。
那么,如何設(shè)計插線板呢?或者說如何設(shè)計接口呢立轧?
先看看插線板廠商是如何生產(chǎn)的吧岖研。
某天,有人生產(chǎn)一個電器是4個孔的扳肛,那就用不了了。這時候乘碑,插線板廠商為了生產(chǎn)出一種插線板挖息,能適用于目前的大部分電器,也能適用于將來的電器兽肤,他找到了一個機構(gòu)套腹。機構(gòu)是專門指定規(guī)則的,專門制定協(xié)議的资铡。機構(gòu)叫來了大部分的重要電器廠商的頭頭电禀,和插線板老板一起開了個會。大家為了共同的利益笤休,決定了一份協(xié)議尖飞。
協(xié)議是這樣的:電器廠商以后生產(chǎn)的電器的插頭,只能生產(chǎn)三孔的店雅,但為了兼容目前市場上已有的電器政基,也能生產(chǎn)兩孔的,但是盡量生產(chǎn)三孔的闹啦。而且孔的大小和之間的距離有明確的規(guī)定沮明。插線板廠商的插線板也只能有兩孔的和三孔的,而且孔的大小和之間的距離也必須按照協(xié)議來生產(chǎn)窍奋。
于是問題解決了荐健,而且插線板廠商老板很聰明,他發(fā)現(xiàn)可以生產(chǎn)出既可以插兩孔琳袄,又可以插三孔的插口江场,于是他的插線板大賣,他發(fā)財了挚歧。優(yōu)秀的接口設(shè)計扛稽,給他帶來了大大的好處,但他很聰明滑负,他沒忘記如果沒有規(guī)范協(xié)議的機構(gòu)在张,一切都是空白用含。
再補充幾句吧,不然你還是難以理解帮匾。
當(dāng)你想設(shè)計一個接口的時候啄骇,你最好先寫幾個將要繼承這個幾口的類,寫幾個只有框架而無實際內(nèi)容的類瘟斜,看看他們之間的共性缸夹,找到寫接口的點,這就正如找電器老板來開會螺句。寫接口的時候虽惭,你需要在之前對接口進行說明,說明接口的適用范圍蛇尚,以及繼承該接口的注意事項芽唇,這就好比請機構(gòu)來制定協(xié)議規(guī)范。有了這些以后取劫,你的接口在被使用的時候就不會錯用匆笤,在寫繼承該接口的類的時候,也會按照規(guī)范完全的匹配接口谱邪。
最后一句話炮捧,即使你理解了我今晚所講的每一句話,你還是不會寫接口惦银,因為你需要實踐咆课,實踐才會出真知。最后這句話才是至理名言扯俱,我說的基本都是空話(在你學(xué)會了寫接口后)傀蚌。
第一次寫接口時,第一個感覺就是蘸吓,寫接口跟沒寫一樣。定義一個接口撩幽,馬上去寫實現(xiàn)類库继!其實此時就是用著面向過程的思路寫程序,然后掛了個羊頭窜醉,說起來怎么也有個接口了宪萄!
今天看了一位老兄寫的對于接口的心得體會,真是太有同感了榨惰!
不要為了接口而接口拜英,當(dāng)你把自己不當(dāng)做是個程序員來思考時,就能把用人的思想來思考了琅催,你不會寫程序居凶,就不會考慮細節(jié)的實現(xiàn)了虫给!此時你所關(guān)注的問題就是比較抽象的了,你看這不正符合面向?qū)ο蟮脑瓌t嗎侠碧?當(dāng)年張三豐教張無忌打太極就是要把招式全忘了抹估,你要定義接口前就先忘了自己是個程序員吧!
當(dāng)然不可能有100%的抽象弄兜,最終你還是要回到實現(xiàn)細節(jié)上來的药蜻,可此時你已是學(xué)會了太極的張無忌了!
python中的接口:
單純?yōu)榱舜a重用的繼承替饿,在實際的使用中很容易造成“高耦合”的問題语泽。
而接口繼承,根據(jù)接口的概念“給使用接口的對象一個很好的抽象”视卢,將對象的一些特征進行歸一化處理踱卵,使外部調(diào)用者無需關(guān)心細節(jié),可以一視同仁地處理特定接口的所有對象腾夯。
但是在python中沒有interface的概念颊埃。在python中定義接口,僅僅是看起來像接口:
第一次寫接口時蝶俱,第一個感覺就是班利,寫接口跟沒寫一樣。定義一個接口榨呆,馬上去寫實現(xiàn)類罗标!其實此時就是用著面向過程的思路寫程序,然后掛了個羊頭积蜻,說起來怎么也有個接口了闯割!
接口提取了一群類共同的函數(shù),可以把接口當(dāng)做一個函數(shù)的集合竿拆。
然后讓子類去實現(xiàn)接口中的函數(shù)宙拉。
這么做的意義在于歸一化,什么叫歸一化丙笋,就是只要是基于同一個接口實現(xiàn)的類谢澈,那么所有的這些類產(chǎn)生的對象在使用時,從用法上來說都一樣御板。
歸一化锥忿,讓使用者無需關(guān)心對象的類是什么,只需要的知道這些對象都具備某些功能就可以了怠肋,這極大地降低了使用者的使用難度敬鬓。
抽象類
定義:
抽象類是一個特殊的類,他的特殊之處在于:只能被繼承,不能被實例化為什么要有抽象類
類是對相似的對象進行一個總結(jié)
抽象類就是對有共同點的類的一種抽取總結(jié)(不完全說是總結(jié)钉答,是因為類和類之間一定存在不同础芍,不能完全統(tǒng)一總結(jié)成一類,真的是總結(jié)關(guān)系的話希痴,應(yīng)該就是子類和父類的關(guān)系了)者甲。
從實現(xiàn)的角度來看,抽象類與普通類的不同在于:
抽象類只能有抽象方法(沒有實現(xiàn)功能)砌创,這類不能被實例化虏缸,只能被繼承,而且子類必須實現(xiàn)方法
從以上描述來看嫩实,抽象類非常類似接口刽辙,但是仍然是有差別的。
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
#一切皆文件
import abc #利用abc模塊實現(xiàn)抽象類
class All_file(metaclass=abc.ABCMeta):
all_type='file'
@abc.abstractmethod #定義抽象方法甲献,無需實現(xiàn)功能
def read(self):
'子類必須定義讀功能' #如果子類不實現(xiàn)宰缤,就會報出此錯誤,以此來實現(xiàn)類似接口的功能
pass
@abc.abstractmethod #定義抽象方法晃洒,無需實現(xiàn)功能
def write(self):
'子類必須定義寫功能'
pass
# class Txt(All_file):
# pass
#
# t1=Txt() #報錯,子類沒有定義抽象方法
class Txt(All_file): #子類繼承抽象類慨灭,但是必須定義read和write方法
def read(self):
print('文本數(shù)據(jù)的讀取方法')
def write(self):
print('文本數(shù)據(jù)的讀取方法')
class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法
def read(self):
print('硬盤數(shù)據(jù)的讀取方法')
def write(self):
print('硬盤數(shù)據(jù)的讀取方法')
class Process(All_file): #子類繼承抽象類球及,但是必須定義read和write方法
def read(self):
print('進程數(shù)據(jù)的讀取方法')
def write(self):
print('進程數(shù)據(jù)的讀取方法')
wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
#這樣大家都是被歸一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()
print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
由上面的代碼可以看出氧骤,抽象類基本上實現(xiàn)了接口的功能,但是抽象類的本質(zhì)還是一個類吃引。指的是一組淚的相似性筹陵,包括數(shù)據(jù)屬性和函數(shù)屬性,而接口只強調(diào)函數(shù)屬性的相似性镊尺。
所以朦佩,抽象類是一個介于類和接口之ijede一個概念,同時具備類和接口的部分特性庐氮,可以用來實現(xiàn)歸一化設(shè)計语稠。