Python面向?qū)ο笕筇卣鳎豪^承、封裝和多態(tài)的深度解析
在面向?qū)ο蟪绦蛟O(shè)計中,對象可以看做是數(shù)據(jù)(特性)以及由一系列可以存取、操作這些數(shù)據(jù)的方法所組成的集合。Python是面向?qū)ο蟮恼Z言,支持面向?qū)ο缶幊痰娜筇匦裕豪^承、封裝(隱藏)、多態(tài),本文將逐一講解Python的三大特性。
封裝
繼承的基本概念以及使用
封裝的概念:隱藏內(nèi)部實現(xiàn)的細節(jié),只對外提供操作方法(接口)。這個概念我們在上一小節(jié)中也已經(jīng)學(xué)習(xí)過了,我們主要是去了解其在代碼中是如何去實現(xiàn)的:通過有著類似 訪問修飾限定符 功能的下劃線來實現(xiàn)。
權(quán)限控制:通過對屬性或方法添加單下劃線、雙下劃線以及首尾雙下劃線來實現(xiàn)。
下劃線種類 | 功能 | |
單下劃線開頭 | 表示protected,受保護的成員,這類成員被視為僅供內(nèi)部家族使用,允許類本身和子類進行訪問,但實際上它可以被外部代碼訪問 | |
雙下劃線開頭 | 表示private,私有的成員,這類成員只允許定義該屬性或方法的類本身進行訪問 | |
首尾雙下劃線 | 一般表示特殊的方法 | |
代碼演示:
class Dog():
# 首尾雙線劃線 ——> 特殊方法
def __init__(self, name, age):
# 雙下劃線開頭 ——> private修飾,只能在類內(nèi)訪問
self.__name = name
# 單下劃線開頭 ——> protected修飾,在類內(nèi)與子類才能訪問
self._age = age
# 單下劃線開頭 ——> protected修飾,在類內(nèi)與子類才能訪問
def _fun1(self):
print('這是被protected所修飾的方法')
# 雙下劃線開頭 ——> private修飾,在類內(nèi)才能訪問
def __fun2(self):
print('這是被private所修飾的方法')
# 這里是類外了
dog = Dog('小劉', 18)
print(dog._age)
# print(dog.__name)
dog._fun1()
# dog.__fun2()
# 和上面一樣,直接去訪問的話,就會報錯,
# 但是我們可以使用 對象.__dir()__ 或者 dir(對象) 先去查看所有的屬性與方法
# 然后通過其中的"屬性"與"方法名"去調(diào)用真正的屬性與方法
# print(dog.__dir__())
# print(dir(dog))
print(dog._Dog__name)
dog._Dog__fun2()運行結(jié)果:

根據(jù)上面的訪問方式,我們可以推測出:被 protected、private 所修飾 方法 與 屬性只是在類中對應(yīng)的名稱發(fā)生了變化,而我們不知道,但是可以通過特殊手段知曉,從而繼續(xù)訪問。
但是上面的方式不是很推薦,類似與 Java中的反射機制了,有點反常規(guī)。除了上面這種方式,Python還提供了兩種方式來實現(xiàn)訪問 與 修改 私有是屬性與方法。
1、在 私有的方法 或者 屬性上,進行套殼處理。
2、在1的基礎(chǔ)上,通過 @property 裝飾器 來修飾方法,使其變?yōu)閷傩?,就變?yōu)樵L問與修改屬性了,最終也會變的很簡單。
代碼演示:
class Dog():
def __init__(self, name, age):
self.__name = name
self.age = age
# 如果想要去訪問除了,使用dir()之外,還有兩種方式:
# 使用實例方法去間接訪問與修改
def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name
# 使用@property裝飾器,將方法轉(zhuǎn)為屬性使用
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
dog = Dog('老劉', 8)
# 1、通過實例方法的形式去使用
print(dog.get_name())
dog.set_name('小劉')
print('='*18)
# 2、通過@property裝飾器,將方法轉(zhuǎn)為屬性
print(dog.name)
dog.name = '老劉' # 修改屬性的樣式,去傳參
print(dog._Dog__name)
print('='*18)
dog._Dog__name = '小劉'
print(dog._Dog__name)運行結(jié)果:

注意:我們在使用 @property 裝飾器,將屬性轉(zhuǎn)為方法時,取值的方法,必須被 @property 修飾,而修改值的方法,必須被 @取值方法名. setter修飾。
繼承
繼承的基本概念以及使用
繼承是指一個類(子類、派生類)繼承另一個類(父類、基類)的屬性與方法。
在Python中一個子類可以繼承N多個父類,這是與 Java決然不同的一點。Java屬于單繼承,而Python屬于多繼承,并且每個類都默認繼承自object類。當然,一個子類可以有多個父類,一個父類也可以有多個子類。
語法:
class 類名([父類列表]):
注意:當一個類的父類只有object類時,默認是可以不寫父類的,但是如果一個類有除object類之外的其他類,就需要將這些類全部寫到 () 中,當然這個()在只有繼承object類的情況下,也是可以不寫的,但是建議還是要寫上去。
代碼演示:
# 默認繼承object類,但是這個可以不寫
class Animal(object):
def __init__(self, name, age, gender,sort):
self.name = name
self.age = age
self.gender = gender
self.__sort = sort
class Dog(Animal):
pass
# 子類繼承父類,會將父類中除private修飾的方法與屬性全部拿過來
# 繼承了 __init__方法,以及 name、age、gender屬性
dog = Dog('小黑', 3, '雄', '哈士奇')
print(dog.name)
print(dog.age)
print(dog.gender)
# 同樣需要特殊手段才能訪問到
print(dog._Animal__sort)運行結(jié)果:

注意:我們在實例化一個對象時,會先去調(diào)用構(gòu)造方法(init 方法)給對象的屬性進行初始化賦值,如果一個類沒有構(gòu)造方法,但是由于這個類是默認繼承自object類,因此會調(diào)用object類中默認的 init 方法,但如果這個類繼承了其他類,就會先在其他類中去搜索這個方法,如果有則調(diào)用;反之,則還是去調(diào)用object類的。 而在上面的代碼中,Animal 類是有 init 方法,因此會去調(diào)用Animal 類的。
我們再看看多繼承的代碼:
class Person():
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def show(self):
print(f'我叫{self.name},今年{self.age},性別是{self.gender}')
class Father(Person):
def __init__(self,name,age,gender):
# 可以直接調(diào)用父類的初始化方法
super().__init__(name,age,gender)
# 下面這種方式是錯誤的
# super().name = name
# super().age = age
# super().gender = gender
# 下面的方式也行,但是不推薦
# self.name = name
# self.age = age
# self.gender = gender
class Mother(Person):
def __init__(self, name, age, gender):
# 可以直接調(diào)用父類的初始化方法
super().__init__(name, age, gender)
# 下面這種方式是錯誤的
# super().name = name
# super().age = age
# super().gender = gender
# 下面的方式也行,但是不推薦
# self.name = name
# self.age = age
# self.gender = gender
class Son(Father, Mother):
def __init__(self, name, age, gender):
# 可以直接調(diào)用父類的初始化方法
super().__init__(name, age, gender)
# 下面這種方式是錯誤的
# super().name = name
# super().age = age
# super().gender = gender
# 下面的方式也行,但是不推薦
# self.name = name
# self.age = age
# self.gender = gender
father = Father('張三',25,'男')
mother = Mother('紅粉佳人',28,'女')
son = Son('趙四',29,'男')
father.show()
mother.show()
son.show()運行結(jié)果:

上面的代碼中,Son類、Mother類、Father類都是繼承自Person類的屬性與方法,它們是公用Person類的屬性與方法。
注意:上述 Son類繼承了多個父類,當我們?nèi)ナ褂?super().init 方法初始化子類對象時,默認是按照父類在Son()中的順序來查找的,這里是Father類定義聲明在前,因此這里調(diào)用的就是 Father 類的 init 方法,如果想要指定某個類的 init 方法的話,就需要使用 類名.init() 方法。例如:
# 這里的self代指當前Son類的實例對象 Mother.__init__(self,name,age,gender) # 可替換Son類中是super.init
方法重寫
當子類繼承父類時,子類就擁有了父類中的 公有成員、方法和受保護的成員、方法。如果我們對父類中有些成員不滿意的話,子類就可以重新定義成員。同理,方法也是如此,但是重新定義方法麻煩了,那有什么辦法呢?方法重寫,即 偷梁換柱,表面上這個方法還是原來的方法,但是其內(nèi)部實現(xiàn)的結(jié)構(gòu)早就發(fā)生變化了。
重寫的要求:方法名必須一樣,至于方法的返回值類型,參數(shù)列表、訪問權(quán)限這些可以不一樣。
代碼演示:
class Animal():
def __init__(self,name,age):
self.name = name
self.age = age
def eat(self):
print(f'{self.name}正在吃狗糧,看小黑玩飛盤')
class Dog(Animal):
def __init__(self,name,age):
super().__init__(name,age)
# 由于Animal類的eat方法只是隨便說在吃啥,并不具體,
# 但我們想要打印出具體的食物,因此可以重寫(外殼不變,核心變化)
def eat(self):
print(f'{self.name}正在玩飛盤~')
class Cat(Animal):
def __init__(self,name,age):
super().__init__(name,age)
dog = Dog('小黑', 2)
# 子類重寫父類的方法之后,父類再去調(diào)用eat方法,就不再是調(diào)用父類的,而是自己的
dog.eat()
cat = Cat('小黃', 3)
# 子類沒有重寫父類的eat方法,因此還是去調(diào)用父類的eat方法
cat.eat()運行結(jié)果:

其實,重寫就是在子類中定義了一個與父類重名的方法,只不過因為 Python中語法的檢查不是很嚴格,因此這里就很容易不理解。
在Python中,我們就將重寫看作是子類定義了一個與父類重名的方法即可,在調(diào)用時,如果子類沒有這個方法,那么就是調(diào)用父類的同名方法,如果還沒有就會報錯。但如果子類有這個方法,那么就是直接調(diào)用子類的方法,即使父類有,也不去調(diào)用("地頭蛇原則")。
多態(tài)
多態(tài)的概念以及基本使用
多態(tài)是指多種形態(tài),當去完成某個行為時,不同的對象可能會產(chǎn)生不同的形態(tài)。
例如,我們在類和對象中說的小故事:當劉建明 與 陳永仁 遇到 韓琛 時,兩人打招呼的方式不一樣,這就是不同對象 在 完成同一個行為時,兩者所產(chǎn)生的形態(tài)不同。
再比如,我們每天都會吃早餐,但是不同的人 在 面對這件事情時,所表現(xiàn)出的行為就不一樣,A 可能吃面條,B可能吃包子、餃子,C可能吃大米飯等。
Python中的多態(tài)與Java、C++不同,Python中的多態(tài)只需要滿足有該方法,然后讓不同的對象去調(diào)用即可,而 Java 中的多態(tài)需要滿足三個條件:向上轉(zhuǎn)型(父類引用指向子類對象)、子類重寫父類的方法、向上轉(zhuǎn)型的父類調(diào)用該方法(被重寫的方法),這時就不再是調(diào)用父類的方法,而是調(diào)用子類的方法。但 Python中,只要這兩個類有相同的方法,當兩者賦值給同一個對象時,去調(diào)用同一個方法就會表現(xiàn)出不同的行為。
其實,多態(tài)就是表層對象一樣,內(nèi)部實際的對象不一樣,那么在調(diào)用同一個方法時,雖然看似是這個表層的對象所調(diào)用的,但實際是內(nèi)部的對象所調(diào)用并執(zhí)行的。
代碼演示:
class Animal():
def eat(self):
print('在玩飛盤')
class Person():
def eat(self):
print('到處溜達')
class Dog():
def eat(self):
print('瘋狂奔跑')
class Cat():
def eat(self):
print('偷吃罐頭')
# 定義一個函數(shù),傳入obj對象,并調(diào)用該對象的eat方法
def eat(obj):
obj.eat()
# 方法得通過類或者對象去"."調(diào)用,而函數(shù)是直接傳參調(diào)用,不要弄混了
eat(Animal()) # 這里需要傳入對象,即:類名()
eat(Person())
eat(Dog())
eat(Cat())運行結(jié)果:

總結(jié)
到此這篇關(guān)于Python面向?qū)ο笕筇卣鳎豪^承、封裝(隱藏)、多態(tài)的文章就介紹到這了,更多相關(guān)Python面向?qū)ο笕筇卣鲀?nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
對python 中re.sub,replace(),strip()的區(qū)別詳解
今天小編就為大家分享一篇對python 中re.sub,replace(),strip()的區(qū)別詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07
Python面向?qū)ο缶幊讨嘘P(guān)于類和方法的學(xué)習(xí)筆記
類與類方法是面向?qū)ο蟮木幊陶Z言中必不可少的特性,本文總結(jié)了Python面向?qū)ο缶幊讨嘘P(guān)于類和方法的學(xué)習(xí)筆記,需要的朋友可以參考下2016-06-06
使用Pytorch實現(xiàn)two-head(多輸出)模型的操作
這篇文章主要介紹了使用Pytorch實現(xiàn)two-head(多輸出)模型的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-05-05

