第七章:面向對象之OOP(第二部分,封裝&繼承)
2 面向對象的三大特性
2.1 封裝
- 作用:對對象成員進行有限制的訪問
- 3個級別
- 公有成員,public
- 受保護成員,protected
- 私有成員,private
注:public,protected,private不是關鍵字,但是變量命名等儘量避開
- Python中下劃線的使用(參考:https://blog.csdn.net/g11d111/article/details/71367649)
- object #public(公有成員)
- _object #obey python coding convention, consider it as private(將其狹義的看做保護成員)
- __object #private(私有成員)
- __object__ #special, python system use, user should not define like it
(特殊成員,與公私有性質無關,如__init__表示構造函數,__doc__等)
其實在Python中並不存在保護成員的概念,如_object這樣狹義上的保護成員在類、子類以及外部都可以進行訪問。儘管可以在外部訪問,但是需要把他看做私有成員,儘量不要在外部訪問。
以雙下劃線開頭是Python中最高級別的封裝,即私有成員,只有類對象本身可以訪問該成員。但是Python中private的私有並不是真的私有,而是一種name mangling的改名策略,使用【對象名._類名__私有成員名】即可訪問
封裝案例:
class Student():
name = "xiaoming"
_age = 18
__school = "NCU"
s = Student()
print(s.name) #訪問公有成員
print(s._age) #此處雖然可以訪問,但是要將其看做私有成員
#print(s.__school) #在外部不能訪問私有成員,此處會報錯
print("*" * 20)
print(Student.__dict__) #查看類Student中的成員
s._Student__school = 19 #使用name mangling可以訪問、修改私有成員,但是最好不要這樣用
print(s._Student__school)
結果:
xiaoming
18
********************
{'__module__': '__main__', 'name': 'xiaoming', '_age': 18, '_Student__school': 'NCU', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
19
2.2 繼承
- 概念:一個類可以獲得另一個類的成員屬性和成員方法
- 作用:可以減少重複性的代碼,增加類代碼的服用功能,增強類與類之間的聯繫
- 繼承與被繼承的概念:
- 被繼承的類稱爲父類,也叫基類或者超類
- 繼承的類稱爲子類,也叫派生類
- 繼承和被繼承之間存在着is-a的關係,即
繼承的語法:參見下列代碼
#在Python中所有的類都有一個父類,即object
class Person():
name = "xiaoming"
age = 18
_petname = "mingming" #
__score = 0
def sleep(self):
print("sleeping")
class Student(Person): #類Student繼承父類Person,父類名Person寫在子類括號中
school_id = "9527"
def homework(self):
print("I must do homework")
s = Student()
print(s.name) #用子類訪問父類中的成員
print(s._petname) #用子類訪問父類中的保護成員,可以訪問但最好不要使用,將其看做私有成員
#print(s.__score) #用子類訪問父類中的私有成員,此處會報錯
print(s.sleep()) #用子類訪問父類中的方法
print("*" * 20) #分割線
print(s.school_id) #子類訪問自己的成員屬性
print(s.homework()) #子類訪問自己的成員方法
結果:
xiaoming
mingming
sleeping
None
********************
9527
I must do homework
None
- 繼承的特徵
- 所有的類都繼承自類object,即類object是所有類的父類
- 子類可以使用父類除私有成員之外的所有內容
- 子類在繼承父類後並沒有將父類成員全部傳入子類當中,即沒有另外開闢存儲空間,而是通過引用關係訪問(代碼示例1)
- 子類可以定義子類獨有的屬性和方法,也即增加代碼的功能性(代碼示例1)
- 子類和父類中的成員相同,則優先使用子類中的成員(代碼示例1)
- 子類如果想擴充父類的方法,可以在定義新方法的同時訪問父類成員進行代碼重用。有下列兩種方法:(代碼示例2)
- 父類名.父類成員
- super().父類成員
代碼實例1:
#在Python中所有的類都有一個父類,即object
class Person():
name = "xiaoming"
age = 18
_petname = "mingming" #
__score = 0
def sleep(self):
print("sleeping")
class Student(Person): #類Student繼承父類Person,父類名Person寫在子類括號中
school_id = 9527 #此school_id爲子類Student獨有的屬性
name = "DaNa"
print(Person.__dict__)
print(Student.__dict__)
print("*" * 20)
s = Student()
print(s.name) #子類和父類擁有相同的成員,則優先使用子類中的成員
結果:
{'__module__': '__main__', 'name': 'xiaoming', 'age': 18, '_petname': 'mingming', '_Person__score': 0, 'sleep': <function Person.sleep at 0x0000000002813488>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
{'__module__': '__main__', 'school_id': 9527, 'name': 'DaNa', '__doc__': None}
******************** #由此處可以看出,父類並沒有把父類中的成員全部到子類中,而是通過子類引用的方法來訪問父類中的成員
DaNa
代碼實例2:
class Person():
name = "xiaona"
age = 19
def sleep(self):
print("sleeping……………")
def work(self):
print("make some money")
class Teacher(Person):
name = "dana"
age = 21
def test(self):
print("make the test")
def work(self):
Person.work(self) #擴充父類功能的方法1
#super().work() #擴充父類功能的方法2,super表示得到父類
self.test()
t = Teacher()
t.work()
結果:
make some money
make the test
- 關於super:
- super - super不是關鍵字, 而是一個類
- super的作用是獲取MRO(MethodResolustionOrder)列表中的第一個類
- super於父類直接沒任何實質性關係,但通過super可以調用到父類
- super使用兩個方,參見在構造函數中調用父類的構造函數
繼承變量函數的查找順序問題:
- 優先查找自己的變量,沒有則往上查找父類
- 構造函數如果子類中沒有定義,則往上查找父類;如果子類中有定義,則不再往上查找父類
2.3 構造函數
2.4 單繼承與多繼承
- 單繼承(上述關於繼承的案例都是單繼承)
- 每個類只能繼承一個類
- 優點:傳承有序,語法簡單,邏輯清晰,隱患少
- 缺點:功能不能無限擴展,只能侷限於當前繼承鏈中的功能
- 多繼承
- 每個類允許繼承多個類
- 優點:類的功能擴展方便
- 缺點:繼承關係複雜,容易出錯
代碼實例3:
#多繼承代碼
class Person():
def __init__(self,name):
self.name = name
def work(self):
print("Working")
class Bird():
def __init__(self,name):
self.name = name
def fly(self):
print("Flying")
class Fish():
def __init__(self,name):
self.name = name
def swim(self):
print("Swimming")
#單繼承
class Student(Person):
def __init__(self,name):
self.name = name
stu = Student("yueyue")
stu.work()
#多繼承
class SwimMan(Person,Fish):
def __init__(self,name):
self.name = name
swi = SwimMan("yueyue")
print("*" * 20)
swi.work()
swi.swim()
#多繼承
class SuperMan(Person,Bird,Fish):
def __init__(self,name):
self.name = name
sup = SuperMan("yueyue")
print("*" * 20)
sup.work()
sup.fly()
sup.swim()
結果:
Working
********************
Working
Swimming
********************
Working
Flying
Swimming
2.4.1 菱形繼承/鑽石繼承問題
- 多個子類繼承來自同一個父類,這些子類又被同一個類繼承,形成一個菱形(鑽石)的形狀,如下圖
#菱形問題代碼
class A():
pass
class B(A):
pass
class C(A):
pass
class A(B,C):
pass
- 對於解決菱形問題,一般採用MRO方法(Method Resolution Order,MRO)
- MRO具體參考:https://www.cnblogs.com/whatisfantasy/p/6046991.html
- Python 3中採用MRO C3的方法來解決菱形問題(上面鏈接有詳解)
- MRO列表計算原則:
- 子類永遠在父類前面
- 如果有多個父類,則根據繼承語法中括號內類的書寫順序存放
- 如果多個類繼承了同一個父類,則孫子類只會選取繼承語法括號中的第一個父親的父類?