一:初識繼承
1,什麼是繼承?
繼承指的是類與類之間的關系,是一種什麼“是”什麼的關系,繼承的功能之一就是用來解決代碼重用問題
繼承是一種建立新類的方式,在python中,建立的類可以繼承一個或多個父類,父類又可以成為基類或超類,建立的類稱為派生類或子類
2,python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類
pass
class ParentClass2: #定義父類
pass
class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支援多繼承,用逗号分隔開多個繼承的類
pass
檢視繼承
方法:
__bases__則是檢視所有繼承的父類
執行個體:
>>> SubClass1.__bases__ #__base__隻檢視從左到右繼承的第一個子類,__bases__則是檢視所有繼承的父類
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
經典類與新式類
1.隻有在python2中才分新式類和經典類,python3中統一都是新式類
2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類
3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類
4.在python3中,無論是否繼承object,都預設繼承object,即python3中所有類均為新式類
提示:如果沒有指定基類,python的類會預設繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實作。
>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)
二:繼承與抽象(先抽象再繼承)
繼承描述的是子類與父類之間的關系,是一種什麼是什麼的關系,要找出這種關系,必須先抽象再繼承。抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
1.将奧巴馬和梅西這倆對象比較像的部分抽取成類;
2.将人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類别(可以隔離關注點,降低複雜度)
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLzADN1QDN2AzNx0CN1UTN0cTMyEDMzMDM4EDMy0CMxQjNyITMvw1MwgTMwIzLcBTM0YjMyEzLcd2bsJ2Lc12bj5ycn9Gbi52YugTMwIzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
繼承:是基于抽象的結果,通過程式設計語言去實作它,肯定是先經曆抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象隻是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
三:繼承與重用性
首先舉一個例子,使用繼承來重用代碼比較好的例子
==========================第一部分
例如
貓可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我們要分别為貓和狗建立一個類,那麼就需要為 貓 和 狗 實作他們所有的功能,僞代碼如下:
#貓和狗有大量相同的内容
class 貓:
def 喵喵叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
class 狗:
def 汪汪叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
==========================第二部分
上述代碼不難看出,吃、喝、拉、撒是貓和狗都具有的功能,而我們卻分别的貓和狗的類中編寫了兩次。
如果使用 繼承 的思想,如下實作:
動物:吃、喝、拉、撒
貓:喵喵叫(貓繼承動物的功能)
狗:汪汪叫(狗繼承動物的功能)
僞代碼如下:
class 動物:
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
# 在類後面括号中寫入另外一個類名,表示目前類繼承另外一個類
class 貓(動物):
def 喵喵叫(self):
print '喵喵叫'
# 在類後面括号中寫入另外一個類名,表示目前類繼承另外一個類
class 狗(動物):
def 汪汪叫(self):
print '喵喵叫'
==========================第三部分
#繼承的代碼實作
class Animal:
def eat(self):
print("%s 吃 " %self.name)
def drink(self):
print ("%s 喝 " %self.name)
def shit(self):
print ("%s 拉 " %self.name)
def pee(self):
print ("%s 撒 " %self.name)
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '貓'
def cry(self):
print('喵喵叫')
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed='狗'
def cry(self):
print('汪汪叫')
# ######### 執行 #########
c1 = Cat('小白家的小黑貓')
c1.eat()
c2 = Cat('小黑的小白貓')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
使用繼承來重用代碼比較好的例子
在開發程式的過程中,如果我們定義了一個類A,然後又想建立立另外一個類B,但是類B的大部分内容與類A的相同時
我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。
通過繼承的方式建立類B,讓B繼承A,B會‘遺傳’A的所有屬性(資料屬性和函數屬性),實作代碼重用
class Hero:
def __init__(self,nickname,aggressivity,life_value):
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value
def move_forward(self):
print('%s move forward' %self.nickname)
def move_backward(self):
print('%s move backward' %self.nickname)
def move_left(self):
print('%s move forward' %self.nickname)
def move_right(self):
print('%s move forward' %self.nickname)
def attack(self,enemy):
enemy.life_value-=self.aggressivity
class Garen(Hero):
pass
class Riven(Hero):
pass
g1=Garen('草叢倫',100,300)
r1=Riven('銳雯雯',57,200)
print(g1.life_value) #結果:300
r1.attack(g1)
print(g1.life_value) #結果:243
提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟體中的一部分設定大部分,大大節省了程式設計工作量,這就是常說的軟體重用,不僅可以重用自己的類,也可以繼承别人的,比如标準庫,來定制新的資料類型,這樣就是大大縮短了軟體開發周期,對大型軟體開發來說,意義重大.
重點!!!再看屬性查找
提示:像g1.life_value之類的屬性引用,會先從執行個體中找life_value然後去類中找,然後再去父類中找...直到最頂級的父類。那麼如何解釋下面的列印結果呢?
class Foo:
def f1(self):
print('from Foo.f1')
def f2(self):
print('from Foo.f2')
class Bar(Foo):
def f1(self):
print('from Bar.f1')
b = Bar()
print(b.__dict__) #{}
b.f1() #from Bar.f1
#找的話肯定先從自己上面找,沒有的話再找父類
class Foo:
def f1(self):
print('from Foo.f1')
def f2(self):
print('from Foo.f2')
self.f1() #b.f1()
class Bar(Foo):
def f1(self):
print('from Bar.f1')
b = Bar()
b.f2()
# from Foo.f2
# from Bar.f1
四:派生
當然子類也可以添加自己新的屬性或者在自己這裡重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那麼調用新增的屬性時,就以自己為準了。
class Riven(Hero):
camp='Noxus'
def attack(self,enemy): #在自己這裡定義新的attack,不再使用父類的attack,且不會影響父類
print('from riven')
def fly(self): #在自己這裡定義新的
print('%s is flying' %self.nickname)
在子類中,建立的重名的函數屬性,在編輯函數内功能的時候,有可能需要重用父類中重名的那個函數功能,應該是用調用普通函數的方式,即:類名.func(),此時就與調用普通函數無異了,是以即便是self參數也要為其傳值
class Riven(Hero):
camp='Noxus'
def __init__(self,nickname,aggressivity,life_value,skin):
Hero.__init__(self,nickname,aggressivity,life_value) #調用父類功能
self.skin=skin #新屬性
def attack(self,enemy): #在自己這裡定義新的attack,不再使用父類的attack,且不會影響父類
Hero.attack(self,enemy) #調用功能
print('from riven')
def fly(self): #在自己這裡定義新的
print('%s is flying' %self.nickname)
r1=Riven('銳雯雯',57,200,'比基尼')
r1.fly()
print(r1.skin)
'''
運作結果
銳雯雯 is flying
比基尼
'''
五,繼承的實作原理
1.繼承原理(python如何實作的繼承)
python到底是如何實作繼承的,對于你定義的每一個類,python會計算出一個方法解析順序(MRO)清單,這個MRO清單就是一個簡單的所有基類的線性順序清單,例如
>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>,
<class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
為了實作繼承,python會在MRO清單上從左到右開始查找基類,直到找到第一個比對這個屬性的類為止。而這個MRO清單的構造是通過一個C3線性化算法來實作的。我們不去深究這個算法的數學原理,它實際上就是合并所有父類的MRO清單并遵循如下三條準則:
- 子類會先于父類被檢查
- 多個父類會根據它們在清單中的順序被檢查
- 如果對下一個類存在兩個合法的選擇,選擇第一個父類
2.繼承順序
在Java和C#中子類隻能繼承一個父類,而Python中子類可以同時繼承多個父類,如果繼承了多個父類,那麼屬性的查找方式有兩種,分别是:深度優先和廣度優先
示範代碼
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__) #隻有新式才有這個屬性可以檢視線性清單,經典類沒有這個屬性
#新式類繼承順序:F->D->B->E->C->A
#經典類繼承順序:F->D->B->A->E->C
#python3中統一都是新式類
#pyhon2中才分新式類與經典類
六:組合與重用性
軟體重用的重要方式除了繼承之外還有另外一種方式,即:組合
組合指的是,在一個類中以另外一個類的對象作為資料屬性,稱為類的組合
>>> class Equip: #武器裝備類
... def fire(self):
... print('release Fire skill')
...
>>> class Riven: #英雄Riven的類,一個英雄需要有裝備,因而需要組合Equip類
... camp='Noxus'
... def __init__(self,nickname):
... self.nickname=nickname
... self.equip=Equip() #用Equip類産生一個裝備,指派給執行個體的equip屬性
...
>>> r1=Riven('銳雯雯')
>>> r1.equip.fire() #可以使用組合的類産生的對象所持有的方法
release Fire skill
組合與繼承都是有效地利用已有類的資源的重要方式。但是二者的概念和使用場景皆不同,
1.繼承的方式
通過繼承建立了派生類與基類之間的關系,它是一種'是'的關系,比如白馬是馬,人是動物。
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如老師是人,學生是人
2.組合的方式
用組合的方式建立了類與組合的類之間的關系,它是一種‘有’的關系,比如教授有生日,教授教python和linux課程,教授有學生s1、s2、s3...
示例:繼承與組合
class People:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
class Course:
def __init__(self,name,period,price):
self.name=name
self.period=period
self.price=price
def tell_info(self):
print('<%s %s %s>' %(self.name,self.period,self.price))
class Teacher(People):
def __init__(self,name,age,sex,job_title):
People.__init__(self,name,age,sex)
self.job_title=job_title
self.course=[]
self.students=[]
class Student(People):
def __init__(self,name,age,sex):
People.__init__(self,name,age,sex)
self.course=[]
egon=Teacher('egon',18,'male','沙河霸道金牌講師')
s1=Student('牛榴彈',18,'female')
python=Course('python','3mons',3000.0)
linux=Course('python','3mons',3000.0)
#為老師egon和學生s1添加課程
egon.course.append(python)
egon.course.append(linux)
s1.course.append(python)
#為老師egon添加學生s1
egon.students.append(s1)
#使用
for obj in egon.course:
obj.tell_info()
總結:
當類之間有顯著不同,并且較小的類是較大的類所需要的元件時,用組合比較好
七,在子類中調用父類的方法
在子類派生出的新方法中,往往需要重用父類的方法,我們有兩種方式實作
方式一:指名道姓,即父類名.父類方法()
class Vehicle: #定義交通工具類
Country='China'
def __init__(self,name,speed,load,power):
self.name=name
self.speed=speed
self.load=load
self.power=power
def run(self):
print('開動啦...')
class Subway(Vehicle): #地鐵
def __init__(self,name,speed,load,power,line):
Vehicle.__init__(self,name,speed,load,power)
self.line=line
def run(self):
print('地鐵%s号線歡迎您' %self.line)
Vehicle.run(self)
line13=Subway('中國地鐵','180m/s','1000人/箱','電',13)
line13.run()
方法二:super()
class Vehicle: #定義交通工具類
Country='China'
def __init__(self,name,speed,load,power):
self.name=name
self.speed=speed
self.load=load
self.power=power
def run(self):
print('開動啦...')
class Subway(Vehicle): #地鐵
def __init__(self,name,speed,load,power,line):
#super(Subway,self) 就相當于執行個體本身 在python3中super()等同于super(Subway,self)
super().__init__(name,speed,load,power)
self.line=line
def run(self):
print('地鐵%s号線歡迎您' %self.line)
super(Subway,self).run()
class Mobike(Vehicle):#摩拜單車
pass
line13=Subway('中國地鐵','180m/s','1000人/箱','電',13)
line13.run()
這兩種方式的差別是:方式一是跟繼承沒有關系的,而方式二的super()是依賴于繼承的,并且即使沒有直接繼承關系,super仍然會按照mro繼續往後查找(兩種方法用哪一種都可以,但是最好不要混合使用)
#A沒有繼承B,但是A内super會基于C.mro()繼續往後找
class A:
def test(self):
super().test()
class B:
def test(self):
print('from B')
class C(A,B):
pass
c=C()
c.test() #列印結果:from B
print(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
了解内容:指名道姓與super()的差別
#指名道姓
class A:
def __init__(self):
print('A的構造方法')
class B(A):
def __init__(self):
print('B的構造方法')
A.__init__(self)
class C(A):
def __init__(self):
print('C的構造方法')
A.__init__(self)
class D(B,C):
def __init__(self):
print('D的構造方法')
B.__init__(self)
C.__init__(self)
pass
f1=D() #A.__init__被重複調用
'''
D的構造方法
B的構造方法
A的構造方法
C的構造方法
A的構造方法
'''
#使用super()
class A:
def __init__(self):
print('A的構造方法')
class B(A):
def __init__(self):
print('B的構造方法')
super(B,self).__init__()
class C(A):
def __init__(self):
print('C的構造方法')
super(C,self).__init__()
class D(B,C):
def __init__(self):
print('D的構造方法')
super(D,self).__init__()
f1=D() #super()會基于mro清單,往後找
'''
D的構造方法
B的構造方法
C的構造方法
A的構造方法
'''
當你使用super()函數時,Python會在MRO清單上繼續搜尋下一個類。隻要每個重定義的方法統一使用super()并隻調用它一次,那麼控制流最終會周遊完整個MRO清單,每個方法也隻會被調用一次(注意注意注意:使用super調用的所有屬性,都是從MRO清單目前的位置往後找,千萬不要通過看代碼去找繼承關系,一定要看MRO清單)
八:接口與歸一化設計
1.什麼是接口
hi boy,給我開個查詢接口。。。此時的接口指的是:自己提供給使用者來調用自己功能的方式\方法\入口,java中的interface使用如下
=================第一部分:Java 語言中的接口很好的展現了接口的含義: IAnimal.java
/*
* Java的Interface接口的特征:
* 1)是一組功能的集合,而不是一個功能
* 2)接口的功能用于互動,所有的功能都是public,即别的對象可操作
* 3)接口隻定義函數,但不涉及函數實作
* 4)這些功能是相關的,都是動物相關的功能,但光合作用就不适宜放到IAnimal裡面了 */
package com.oo.demo;
public interface IAnimal {
public void eat();
public void run();
public void sleep();
public void speak();
}
=================第二部分:Pig.java:豬”的類設計,實作了IAnnimal接口
package com.oo.demo;
public class Pig implements IAnimal{ //如下每個函數都需要詳細實作
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
/*
*實作了IAnimal的“人”,有幾點說明一下:
* 1)同樣都實作了IAnimal的接口,但“人”和“豬”的實作不一樣,為了避免太多代碼導緻影響閱讀,這裡的代碼簡化成一行,但輸出的内容不一樣,實際項目中同一接口的同一功能點,不同的類實作完全不一樣
* 2)這裡同樣是“人”這個類,但和前面介紹類時給的類“Person”完全不一樣,這是因為同樣的邏輯概念,在不同的應用場景下,具備的屬性和功能是完全不一樣的 */
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
PS:hi boy,給我開個查詢接口。。。此時的接口指的是:自己提供給使用者來調用自己功能的方式\方法\入口
2.為什麼要用接口
接口提取了一群類共同的函數,可以把接口當做一個函數的集合。
然後讓子類去實作接口中的函數。
這麼做的意義在于歸一化,什麼叫歸一化,就是隻要是基于同一個接口實作的類,那麼所有的這些類産生的對象在使用時,從用法上來說都一樣。
歸一化的好處在于:
1. 歸一化讓使用者無需關心對象的類是什麼,隻需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
2. 歸一化使得高層的外部使用者可以不加區分的處理所有接口相容的對象集合
2.1:就好象linux的泛檔案概念一樣,所有東西都可以當檔案處理,不必關心它是記憶體、磁盤、網絡還是螢幕(當然,對底層設計者,當然也可以區分出“字元裝置”和“塊裝置”,然後做出針對性的設計:細緻到什麼程度,視需求而定)。
2.2:再比如:我們有一個汽車接口,裡面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大衆汽車的類,他們都實作了汽車接口,這樣就好辦了,大家隻需要學會了怎麼開汽車,那麼無論是本田,還是奧迪,還是大衆我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數調用)都一樣
3.模仿interface
在python中根本就沒有一個叫做interface的關鍵字,如果非要去模仿接口的概念
可以借助第三方子產品:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py裡使用zope.interface
文檔https://zopeinterface.readthedocs.io/en/latest/
設計模式:https://github.com/faif/python-patterns
也可以使用繼承:
繼承的兩種用途
一:繼承基類的方法,并且做出自己的改變或者擴充(代碼重用):實踐中,繼承的這種用途意義并不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。
二:聲明某個子類相容于某基類,定義一個接口類(模仿java的Interface),接口類中定義了一些接口名(就是函數名)且并未實作接口的功能,子類繼承接口類,并且實作接口中的功能
class Interface:#定義接口Interface類來模仿接口的概念,python中壓根就沒有interface關鍵字來定義一個接口。
def read(self): #定接口函數read
pass
def write(self): #定義接口函數write
pass
class Txt(Interface): #文本,具體實作read和write
def read(self):
print('文本資料的讀取方法')
def write(self):
print('文本資料的讀取方法')
class Sata(Interface): #磁盤,具體實作read和write
def read(self):
print('硬碟資料的讀取方法')
def write(self):
print('硬碟資料的讀取方法')
class Process(Interface):
def read(self):
print('程序資料的讀取方法')
def write(self):
print('程序資料的讀取方法')
上面的代碼隻是看起來像接口,其實并沒有起到接口的作用,子類完全可以不用去實作接口 ,這就用到了抽象類
九,抽象類
1.什麼是抽象類
與java一樣,python也有抽象類的概念但是同樣需要借助子產品實作,抽象類是一個特殊的類,它的特殊之處在于隻能被繼承,不能被執行個體化
2.為什麼要有抽象類
如果說類是從一堆對象中抽取相同的内容而來的,那麼抽象類就是從一堆類中抽取相同的内容而來的,内容包括資料屬性和函數屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的内容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。
從設計角度去看,如果類是從現實對象抽象而來的,那麼抽象類就是基于類抽象而來的。
從實作角度來看,抽象類與普通類的不同之處在于:抽象類中隻能有抽象方法(沒有實作功能),該類不能被執行個體化,隻能被繼承,且子類必須實作抽象方法。這一點與接口有點類似,但其實是不同的,即将揭曉答案
3.在python中實作抽象類
#_*_coding:utf-8_*_
#一切皆檔案
import abc #利用abc子產品實作抽象類
class All_file(metaclass=abc.ABCMeta):
all_type='file'
@abc.abstractmethod #定義抽象方法,無需實作功能
def read(self):
'子類必須定義讀功能'
pass
@abc.abstractmethod #定義抽象方法,無需實作功能
def write(self):
'子類必須定義寫功能'
pass
# class Txt(All_file):
# pass
#
# t1=Txt() #報錯,子類沒有定義抽象方法
class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法
def read(self):
print('文本資料的讀取方法')
def write(self):
print('文本資料的讀取方法')
class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法
def read(self):
print('硬碟資料的讀取方法')
def write(self):
print('硬碟資料的讀取方法')
class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法
def read(self):
print('程序資料的讀取方法')
def write(self):
print('程序資料的讀取方法')
wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
#這樣大家都是被歸一化了,也就是一切皆檔案的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()
print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
4.抽象類與接口
抽象類的本質還是類,指的是一組類的相似性,包括資料屬性(如all_type)和函數屬性(如read ,write),而接口隻強調函數屬性的相似性
抽象類是一個介于類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實作歸一化設計
參考内容:https://www.cnblogs.com/linhaifeng/p/6295875.html;寫在此處的目的是鞏固老師所講知識,課後為了友善複習
不經一番徹骨寒 怎得梅花撲鼻香