面向對象的三大特性
一、封裝
1.1 隱藏屬性
封裝是面向對象三大核心中最核心的一個特性。
封裝就是將類的某些屬性隱藏起來,然后通過接口對外開放,但是在外部不能直接進行查找屬性。
在類內部的變量屬性或者函數屬性名前加上兩個下划線。就實現了對該屬性的隱藏,此時,通過外界就查不到該屬性。
# 屬性的隱藏
class Student:
__school = "清華"
def __init__(self,name,age):
self.__name = name
self.__age = age
def tell_info(self):
print("學校:%s 姓名:%s 年齡:%s"%(self.__school,self.__name,self.__age))
stu_1 = Student("tom",18)
print(stu_1.__school) # 這種方法是查看不到該學校屬性的。
print(stu_1._Student__school) # 采用_類__屬性的方法就可以查看了。
stu_1.__handsome = "帥" # 定義完畢類之后在添加__屬性,該屬性是不會隱藏起來的。
通過上述的查看方式,就大概看出來,這個隱藏其實是一種變形,這個階段主要發生在定義階段,當執行類內部的代碼時候,遇到__屬性
的方式就會將其變為_類__屬性名
的形式。這樣我們就可以在外界通過這種形式調用查看該屬性。
由於是在定義階段發生的變形,因此,在外界我們在重新指定對象的屬性的時候,是不會在發生變形的。
這種隱藏的方式並不是完全的沒辦法訪問,但是如果你還想調用查看的話,請一開始就不要搞這種隱藏,多此一舉。這種隱藏其內部是可以使用的,因為其內部在定義的時候都發生了轉變,所以內部使用的方式也變成了_類__屬性
的方式。
那為什么要設置隱藏呢?
- 隱藏數據是為了限制外界對屬性的直接修改。
- 通過接口操作可以在接口上附加額外的邏輯處理來控制隱藏數據。
1.2 property裝飾器
property裝飾器的作用就是把類內部的函數屬性的調用方式轉為數據屬性的調用。
class People:
def __init__(self,name):
self.__name = name
@property
def get_name(self):
return self.__name
p1 = Prople("tom")
print(p1.get_name) # 此時get_name函數屬性的調用就可以不用括號,而是采用數據屬性的調用方式
當然還有一個更神奇的操作。
class People:
def __init__(self,name):
self.__name = name
def get_name(self):
return self.__name
def set_name(self,change_name):
self.__name = change_name
def del_name(self):
del self.__name
name = property(get_name,set_name,del_name)
p1 = People("tom")
# 調用了get_name的方法
print(p1.name)
# set_name的方法
p1.name = "Jack"
# del_name的方法
del p1.name
上述是老版的方法,下方是新版。
class People:
def __init__(self,name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self,change_name):
self.__name = change_name
@name.deleter
def name(self):
del self.__name
注意:這幾個方法都是同一個名字,而且@property位於最上方。
二、繼承
2.1 單繼承
繼承:主要是闡述子類(派生類)是父類(基類,超類)。就是一種什么是什么的關系。比如說豬是哺乳動物,那么哺乳動物的一些特征豬肯定有,我們在創建完哺乳動物類之后,在創建豬類,就可以繼承哺乳動物類的屬性,然后在創建豬類。這樣就減少了代碼的冗余。使結構更加清晰。
在Python2 中還有經典類和新式類。
- 經典類。沒有繼承了object類的子類、子子類。。。。。。。
- 新式類。繼承了object類的子類、子子類。。。。。。。
但是Python3 中 已經默認全部都是新式類了。
單繼承的話,當我們能使用子類的某些屬性的時候,查找順序就是子類--》父類--》父類的父類。。。
class Animal(Object): # 這個(Object)加不加都是默認為新式類。
def __init__(self,name):
self.name =name
def run(self):
print("動物開始跑")
def eat(self):
self.run()
print("跑完步,動物開始吃飯")
class Pig(Animal):
def__init__(self,name,weight): # 當子類有與父類同名的屬性就會重寫該屬性。
Animal.__init__(self,name) # 這種可以實現對父類屬性的調用。
self.weight =weight
def sleep(self):
print("豬開始睡覺了。")
def run(self):
print("豬跑不動,所以只能散散步。")
pig_1 = Pig("peiqi",500)
pig_1.eat() # 你猜會運行什么?
# 豬跑不動,所以只能散散步。
# 跑完步,動物開始吃飯
當佩奇執行eat屬性的時候,本身並沒有含有這個屬性,就去Animal類中找到了eat方法。然后執行到了self.run()。那么此時的self其實已經是佩奇對象的本身了,因此,在這一步調用的實際是peiqi.run()。佩奇本身是含有這個屬性的,因此執行的就是上述結果第一句。
父類也可以設置__屬性
的方式進行隱藏。
2.2 多繼承
python是支持多繼承的 ,其查找屬性的順訊主要就是新式類和經典類在菱形問題上的不同。
如果ABC都有某一個方法,那么D在使用的時候會使用哪一個?根據mro列表,就可以得到查找屬性的順序。mro列表有以下幾個原則
- 子類優先於父類。
- 多個父類按照繼承時候的順序。
- 一旦找到就使用。
如果在繼承類中不是菱形問題,那么新式類和經典類的查找順序是一樣的。
2.3 mixins機制
存在即合理。多繼承雖然可以減少代碼的冗余,但是它也容易影響可讀性和維護性。因為繼承本質是一種是的關系,當子類不是父類但是仍然繼承了其他父類的屬性,那么就不便與人類理解。為此,Python推出了一種編程范式:mixins機制。這個就是多繼承正確打開的方式。
一般情況下,某一個類賦予其子類一種函數屬性,相當於對子類功能的一種拓展,這個時候這個類就以“Mixin”結尾,一般情況下,子類都是只繼承一個父類(從屬關系),其他拓展功能位於也是父類,但是位於父類之前,便於閱讀。
class Vehicle:
pass
class FlyableMixin: # 只負責拓展飛的功能。
def fly(self):
pass
class CivilAircraft(FlyableMixin,Vehicle): # 民航飛機,飛的功能位於Vehicle之前。
pass
class Helicopter(FlyableMixin,Vehicle): # 直升飛機
pass
這就是MIXIN機制,它只是一種規范,當然也可以不按照這種規范寫,但是這種寫更便於閱讀。
三、多態
多態就是同一種事物有多種不同的表現形式,比如說同樣是狗類,但也分為二哈、中華田園犬、泰迪等等不同的類型。但是既然同屬於狗類,那么這些子類一定有共同的特征。這樣我們在使用這些特征的時候,不必考慮是什么子類,他肯定有這一個屬性。
我們還可以利用一個函數當做統一的接口去使用這些屬性。
多態性的本質就是在不同的類中定義有相同名字的屬性,這樣我們就可以用一個統一的接口去使用對象。
3.1 鴨子類型
如果看着像鴨子,走路、叫聲都像鴨子,那么他就是個鴨子。
這時不需要考慮繼承的關系,只要類之間有相同的屬性,那么就可以不用考慮類型使用對象,這就是Python推崇的“鴨子類型”。