python面向物件的特徵
面向物件的語言的特徵
- 繼承
- 多型
- 封裝
繼承 inheritance
繼承 inheritance / 派生 derived
什麼繼承/派生
繼承是指從已有的類中派生出新的類,新類具有原類的行為,並能擴充套件新的行為
派生類就是從一個已有類中衍生成新類,在新類上可以新增新的屬性和行為
作用:
1.用繼承派生機制,可以將一些共有功能加在基類中,實現程式碼的共享
2.在不改變基類的程式碼的基礎上改變原有的功能名語:
基類(base class) /超類(super class)/ 父類(father class)
派生類(derived class) / 子類(child class)
繼承有兩種用途:
- 繼承基類的方法,並且做出自己的改變或者擴充套件(程式碼重用)
- 宣告某個子類兼容於某基類,定義一個介面類Interface,介面類中定義了一些介面名(就是函式名)且並未實現介面的功能,子類繼承介面類,並且實現介面中的功能
單繼承:
語法:
class 類名(基類名):
語句塊
說明:
單繼承是指由一個基類衍生出新的類繼承說明:
python3 任何類都直接或間接的繼承自object類
object 類是一切類的超類類的__base__屬性
__base__屬性用來記錄此類的基類
覆蓋
覆蓋是指在有繼承關係的類中,子類中實現了與基類同名的方法,在子類的例項呼叫該方法時,實際呼叫的是子類中的覆蓋版本,這種現象叫做覆蓋
子類物件顯式呼叫基類(被覆蓋)方法的方式:
基類名.方法名(例項, 實際呼叫傳參)
super 函式
super(cls, obj) 返回繫結超類的例項(要求obj必須是cls型別的例項)
super() 返回繫結超類的例項,等同於: super(__class__, 例項方法的第一個引數), 必須在方法內呼叫作用:
藉助super() 返回的例項間接呼叫其父類的覆蓋方法示例:
# 此示例示意用super函式返回的物件呼叫父類的覆蓋方法 class A: def works(self): print("A.works被呼叫") class B(A): ''' B類繼承自A類''' def works(self): print("B.works被呼叫") def super_work(self): self.works() # B.works被呼叫 super(B, self).works() # A.works被呼叫 super().works() # A.works被呼叫 b = B() # b.works() # B.works被呼叫 # super(B, b).works() # A.works被呼叫 b.super_work() # ... # super().works() # 出錯,只能在方法內呼叫
顯式呼叫基類的初始化方法:
當子類中實現了 __init__ 方法,基類的構造方法並不會被呼叫
def __init__(self, ...)示例:
# 此示例示意 用super函式顯示呼叫基類__init__初始化方法 class Human: def __init__(self, n, a): self.name, self.age = n, a print("Human的__init__方法被呼叫") def infos(self): print("姓名:", self.name) print("年齡:", self.age) class Student(Human): def __init__(self, n, a, s=0): super().__init__(n, a) # 顯式呼叫父類的初始化方法 self.score = s # 新增成績屬性 print("Student類的__init__方法被呼叫") def infos(self): super().infos() # 呼叫父類的方法 print("成績:", self.score) s1 = Student('小張', 20, 100) s1.infos()
子類呼叫父類
子類繼承了父類的方法,然後想進行修改,注意了是基於原有的基礎上修改,那麼就需要在子類中呼叫父類的方法
方法一:父類名.父類方法
1 class A: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 def test(self): 7 print('test function') 8 9 class B(A): #新建類B繼承類A,類A中的屬性全部被類B繼承 10 def __init__(self, name, age, country): 11 A.__init__(self, name, age) #引用父類的屬性 12 self.country = country #增加自身獨有的屬性 13 14 def test(self): 15 print('test function B') 16 pass 17 18 b1 = B('jack', 21, 'China') #類B的例項可引用父類B的屬性,如果有重名屬性,以自身類的屬性優先 19 print(b1.name) 20 print(b1.age) 21 print(b1.country) 22 b1.test() 23 24 #執行結果 25 #jack 26 #21 27 #China 28 #test function B
方法二:super()函式
1 #super的用法:只能在新式類中使用 2 #python3中: 3 class People: 4 def __init__(self, name, sex, age): 5 self.name = name 6 self.sex = sex 7 self.age = age 8 9 def walk(self): 10 print('%s is walking' %self.name) 11 12 class Chinese(People): 13 country = 'China' 14 def __init__(self, name, sex, age, language = 'Chinese'): 15 super().__init__(name, sex, age) #super(). 16 self.language = language 17 def walk(self,x): 18 super().walk() #super(). 19 print('subclass: %s' % x) 20 21 c = Chinese('egon', 'male', 18) 22 print(c.name, c.sex, c.age, c.language) 23 c.walk('walk')
多繼承 multiple inheritance
多繼承是指一個子類繼承自兩個或兩個以上的基類
語法:
class 類名(基類名1, 基類名2, ...):
...
說明:
1. 一個子類同時繼承自多個父類,父類中的方法可以同時被繼承下來
2. 如果兩個父類中有同名的方法,而在子類中又沒有覆蓋此方法時,呼叫結果難以確定
python的類如果繼承了多個類,那麼其尋找方法的方式有兩種:深度優先和廣度優先(python3中統一都為新式類)
- 當類是經典類時,多繼承情況下,深度優先
- 當類是新式類時,多繼承情況下,廣度優先
多繼承的問題(缺陷):
識別符號(名字空間)衝突的問題(在名字空間衝突時呼叫前面的基類)
要謹慎使用繼承示例:
# 此示例示意多繼承名字衝突問題 # 小張寫了一個類A class A: def m(self): print('A.m()被呼叫') # 小李寫了一個類B class B: def m(self): print('B.m()被呼叫') # 小王感覺小張和小李寫的兩個類自己可以用 class AB(A, B): pass ab = AB() ab.m() # A.m()被呼叫
多繼承的 MRO (Method Resolution Order) 問題
類的 __mro__ 屬性
此屬性用來記錄類的方法查詢順序1 class A(object): 2 def test(self): 3 print('from A') 4 5 class B(A): 6 def test(self): 7 print('from B') 8 9 class C(A): 10 def test(self): 11 print('from C') 12 13 class D(B): 14 def test(self): 15 print('from D') 16 17 class E(C): 18 def test(self): 19 print('from E') 20 21 class F(D,E): 22 # def test(self): 23 # print('from F') 24 pass 25 f1=F() 26 f1.test() 27 print(F.__mro__) #只有新式類才有這個屬性可以檢視線性列表,經典類沒有這個屬性 28 29 #新式類繼承順序:F->D->B->E->C->A 30 #經典類繼承順序:F->D->B->A->E->C 31 #python3中統一都是新式類
以下摘自 https://www.cnblogs.com/OldJack/p/6734493.html
一、繼承
繼承是一種建立新的類的方式,在python中,新建的類可以繼承自一個或者多個父類,原始類稱為基類或超類,新建的類稱為派生類或子類。
python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類 pass class ParentClass2: #定義父類 pass class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass1 pass class SubClass2(ParentClass1,ParentClass2): #python支援多繼承,用逗號分隔開多個繼承的類 pass
使用'__bases__'方法檢視繼承
>>> SubClass1.__bases__ (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果沒有指定基類,python的類會預設繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)
二、繼承與抽象(先抽象再繼承)
抽象即抽取類似或者說比較像的部分。是一種歸類的方法。
抽象分成兩個層次:
1. 將奧巴馬和梅西這倆物件比較像的部分抽取成類;
2. 將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關注點,降低複雜度)
繼承:是基於抽象的結果,通過程式語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類。
三、 繼承與重用性
在開發程式的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B的大部分內容與類A的相同時,我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。
通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(資料屬性和函式屬性),實現程式碼重用。
class A: def test(self): print('test function') class B(A): #新建類B繼承類A,類A中的屬性全部被類B繼承 pass b1 = B() #類B的例項可引用父類B的'test'方法 b1.test() #執行結果 #test function
用已經有的類建立一個新的類,這樣就重用了已經有的軟體中的一部分設定,大大減少了程式設計工作量,這就是常說的軟體重用。不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定製新的資料型別,這樣就大大縮短了軟體開發週期,對大型軟體開發來說,意義重大。
當然子類也可以新增自己新的屬性或者在自己這裡重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那麼呼叫新增的屬性時,就以自己為準了。
class A: def test(self): print('test function') class B(A): #新建類B繼承類A,類A中的屬性全部被類B繼承 def test(self): print('test function B') pass b1 = B() #類B的例項可引用父類B的'test'方法,但自身類下也有重名的’test‘方法,以自身優先 b1.test() #執行結果 #test function B
在子類中,新建的重名的函式屬性,在編輯函式內功能的時候,有可能需要重用父類中重名的那個函式功能,應該是用呼叫普通函式的方式,即:類名.func(),此時就與呼叫普通函式無異了,因此即便是self引數也要為其傳值
class A: def __init__(self, name, age): self.name = name self.age = age def test(self): print('test function') class B(A): #新建類B繼承類A,類A中的屬性全部被類B繼承 pass b1 = B('jack', 21) #類B的例項可引用父類B的'test'方法 print(b1.name) print(b1.age) b1.test() #執行結果 #jack #21 #test function
class A: def __init__(self, name, age): self.name = name self.age = age def test(self): print('test function') class B(A): #新建類B繼承類A,類A中的屬性全部被類B繼承 def __init__(self, name, age, country): A.__init__(self, name, age) #引用父類的屬性 self.country = country #增加自身獨有的屬性 def test(self): print('test function B') pass b1 = B('jack', 21, 'China') #類B的例項可引用父類B的屬性,如果有重名屬性,以自身類的屬性優先 print(b1.name) print(b1.age) print(b1.country) b1.test() #執行結果 #jack #21 #China #test function B
四、組合與重用性
組合指的是,在一個類中以另外一個類的物件作為資料屬性,稱為類的組合
class Teacher: def __init__(self, name, gender, course): self.name = name self.gender = gender self.course = course class Course: def __init__(self, name, price, period): self.name = name self.price = price self.period = period course_obj = Course('Python', 15800, '5months') #新建課程物件 #老師與課程 t_c = Teacher('egon', 'male', course_obj) #新建老師例項,組合課程物件 print(t_c.course.name) #列印該老師所授的課程名 #執行結果 #Python
組合與繼承都是有效地利用已有類的資源的重要方式,但是二者的概念和使用場景皆不同。
1. 繼承的方式
通過繼承建立了派生類與基類之間的關係,它是一種'是'的關係,比如白馬是馬,人是動物。
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如教授是老師
>>> class Teacher: ... def __init__(self,name,gender): ... self.name=name ... self.gender=gender ... def teach(self): ... print('teaching') ... >>> >>> class Professor(Teacher): ... pass ... >>> p1=Professor('egon','male') >>> p1.teach() teaching
2. 組合的方式
用組合的方式建立了類與組合的類之間的關係,它是一種‘有’的關係,比如教授有生日,教授教python課程
class BirthDate: def __init__(self,year,month,day): self.year=year self.month=month self.day=day class Couse: def __init__(self,name,price,period): self.name=name self.price=price self.period=period class Teacher: def __init__(self,name,gender): self.name=name self.gender=gender def teach(self): print('teaching') class Professor(Teacher): def __init__(self,name,gender,birth,course): Teacher.__init__(self,name,gender) self.birth=birth self.course=course p1=Professor('egon','male', BirthDate('1995','1','27'), Couse('python','28000','4 months')) print(p1.birth.year,p1.birth.month,p1.birth.day) print(p1.course.name,p1.course.price,p1.course.period) #執行結果: #1 27 #python 28000 4 months
組合例項:
1 #組合重用程式碼 2 class Teacher: 3 def __init__(self, name, sex, args): 4 self.name = name 5 self.sex = sex 6 self.args = args 7 8 class Student: 9 def __init__(self, name, sex, args): 10 self.name = name 11 self.sex = sex 12 self.args = args 13 14 class Course: 15 def __init__(self, name, price, period): 16 self.name = name 17 self.price = price 18 self.period = period 19 20 class Birth: 21 def __init__(self, year, month, day): 22 self.year = year 23 self.month = month 24 self.day = day 25 26 class Score: 27 def __init__(self, score): 28 self.score = score 29 30 def score_grade(self): 31 if self.score > 90: 32 g = 'A' 33 elif self.score > 80: 34 g = 'B' 35 elif self.score > 70: 36 g = 'C' 37 elif self.score > 60: 38 g = 'D' 39 else: 40 g = 'F' 41 return g 42 course_obj = Course('Python', 15800, '5months') #課程 43 birth_obj_t = Birth(2000, 4, 19) #老師生日 44 birth_obj_s = Birth(2009, 9, 21) #學生生日 45 score_obj = Score(91) #學生成績 46 #老師與課程 47 t_c = Teacher('egon', 'male', course_obj) 48 print('%s老師教授%s' % (t_c.name, t_c.args.name)) #列印該老師所授的課程名 49 #學生與課程 50 s_c = Student('jack', 'male', course_obj) 51 print('%s學習%s' % (s_c.name, s_c.args.name)) 52 #老師與生日 53 t_b = Teacher('egon', 'male', birth_obj_t) 54 print('%s老師生日為:%s年 %s月 %s日'%(t_b.name, t_b.args.year, t_b.args.month, t_b.args.day)) 55 #學生與生日 56 s_b = Student('jack', 'male', birth_obj_s) 57 print('%s學生生日為:%s年 %s月 %s日'%(s_b.name, s_b.args.year, s_b.args.month, s_b.args.day)) 58 #學生和分數 59 s_s = Student('jack', 'male', score_obj) 60 print('%s學生的成績為%s,等級為%s' % (s_s.name, s_s.args.score, s_s.args.score_grade())) 61 62 63 #執行結果: 64 #egon老師教授Python 65 #jack學習Python 66 #egon老師生日為:2000年 4月 19日 67 #jack學生生日為:2009年 9月 21日 68 #jack學生的成績為91,等級為A
封裝 enclosure
封裝是指隱藏類的實現細節,讓使用者不關心這些細節
封裝的目的是讓使用者通過儘可能少的方法(或屬性)操作物件封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部呼叫者的程式碼;而外部使用用者只知道一個介面(函式),只要介面(函式)名、引數不變,使用者的程式碼永遠無需改變
私有屬性和方法
python類中以雙下劃線('__') 開頭,不以雙下劃線結尾的識別符號為私有成員,私有成員或只能用類內的方法進行訪問和修改
以__開頭的例項變數有私有屬性
以__開頭的方法為私有方法示例:
# 此示例示意私有屬性和私有方法 class A: def __init__(self): self.__p1 = 100 # 私有屬性 def show_A(self): print('self.__p1: ', self.__p1) self.__m1() # 呼叫自己的方法 def __m1(self): # 私有方法 print("__m1(self)方法被呼叫") a = A() a.show_A() # a.__p1: 100 # print(a.__p1) # 出錯,在類外部不能訪問a的私有屬性__p1 # a.__m1() # 出錯,不能呼叫私有方法 class B(A): pass b = B() # print(b.__p1) # 出錯, 子類物件不能訪問父類中的私有成員 # b.__m1() # 出錯
單下劃線、雙下劃線、頭尾雙下劃線說明:
__foo__: 定義的是特殊方法,一般是系統定義名字 ,類似 __init__() 之類的。
_foo: 以單下劃線開頭的表示的是 protected 型別的變數,即保護型別只能允許其本身與子類進行訪問,不能用於 from module import *
__foo: 雙下劃線的表示的是私有型別(private)的變數, 只能是允許這個類本身進行訪問了。
多型 polymorphic
多型:
字面意思: 多種狀態
多型是指在有繼承/派生關係的類中,呼叫基類物件的方法,實際能呼叫子類的覆蓋方法的現象叫多型狀態:
靜態(編譯時狀態)
動態(執行時狀態)
說明:
多型呼叫方法與物件相關,不與類相關
Python的全部物件都只有"執行時狀態(動態)", 沒有"C++語言"裡的"編譯時狀態(靜態)"
# 此示例示意python中的執行時狀態
class Shape:
def draw(self):
print('Shape的draw方法被呼叫')
class Point(Shape):
def draw(self):
print("正在畫一個點")
class Circle(Shape):
def draw(self):
super().draw()
print("正在畫一個圓")
def my_draw(s):
s.draw() #此處顯示出'動態'
s1 = Circle()
s2 = Point()
my_draw(s2)
my_draw(s1)
多型與多型性
1.多型指的是一類事物有多種形態,(一個抽象類有多個子類,因而多型的概念依賴於繼承)。——類的定義階段
- 序列型別有多種形態:字串,列表,元組。
- 動物有多種形態:人,狗,豬
- 檔案有多種形態:文字檔案,可執行檔案
2. 多型性是指具有不同功能的函式可以使用相同的函式名,這樣就可以用一個函式名呼叫不同內容的函式。——類的呼叫階段
在面向物件方法中一般是這樣表述多型性:向不同的物件傳送同一條訊息,不同的物件在接收時會產生不同的行為(即方法)。也就是說,每個物件可以用自己的方式去響應共同的訊息。所謂訊息,就是呼叫函式,不同的行為就是指不同的實現,即執行不同的函式。
3. 多型性的好處
- 增加了程式的靈活性,以不變應萬變,不論物件千變萬化,使用者都是同一種形式去呼叫,如func(animal)
- 增加了程式的可擴充套件性,通過繼承animal類建立了一個新的類,使用者無需更改自己的程式碼,還是用func(animal)去呼叫