Python 全棧開發:python面向對象三大特征
目錄:
-
繼承
-
封裝
-
多態
一、繼承
1.什麽繼承
繼承是一種創建新類的方式,新建的類可以繼承一個或多個父類(python支持多繼承),父類又可稱為基類或超類,新建的類稱為派生類或子類。
2.為什麽要有繼承
子類會“”遺傳”父類的屬性,從而解決代碼重用問題,減少代碼的冗余
3.怎麽應用繼承
eg:
class ParentClass1: # 定義父類1 pass class ParentClass2: # 定義父類2 pass class Subclass1(ParentClass1): # 單繼承 父類1 pass class Subclass2(ParentClass1,ParentClass2): #多繼承多個父類 父類1 父類2 pass print(Subclass1.__bases__) # 查看所有父類信息 print(Subclass2.__bases__) # 查看所有父類信息
結果:以元組的形式返回
(<class ‘__main__.ParentClass1‘>,)
(<class ‘__main__.ParentClass1‘>, <class ‘__main__.ParentClass2‘>)
4.繼承與抽象(先抽象再繼承)
先抽象:抽取對象之間相似之處得到了類,在總結類與類之間的相似得到父類
再繼承:(子類繼承父類,子類可以遺傳父類屬性)是基於抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
5.派生與重用
派生:子類定義自己新的屬性,如果與父類同名,以子類自己的為準
# 父類 class People: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def func1(self): print(‘People.func1‘) # 子類 派生自己的屬性 class Teacher(People): def __init__(self, name, age, sex, level, salary): self.name示例代碼= name self.age = age self.sex = sex self.level = level self.salary = salary def func1(self): print(‘Teacher.func1‘) # 實例化 tea1 = Teacher(‘fixd‘, 18, ‘male‘, 9, 3.1) print(tea1.name, tea1.age, tea1.sex, tea1.level, tea1.salary) # 結果 fixd 18 male 9 3.1
重用:在子類派生出的新方法中重用父類的功能
方式一:指名道姓地調用(其實與繼承沒有什麽關系的)
# 父類 class People: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex # 子類 "指名道姓" 調用父類的屬性 class Teacher(People): def __init__(self, name, age, sex, level, salary): People.__init__(self, name, age, sex) self.level = level self.salary = salary
方式二、super()調用(嚴格依賴於繼承)
ps:super()的返回值是一個特殊的對象,該對象專門用來調用父類中的屬性
了解:在python2中,需要super(自己的類名,self)
# 父類 class People: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex # 子類 super() 調用父類的屬性 class Teacher(People): def __init__(self, name, age, sex, level, salary): super().__init__(name, age, sex) self.level = level self.salary = salary
註意:以上兩種方式都可以使用,在實際的編碼工作中,推薦使用統一的一種方式
#super()會嚴格按照mro列表從當前查找到的位置繼續往後查找 class A: def test(self): print(‘A.test‘) super().f1() class B: def f1(self): print(‘from B‘) class C(A,B): pass c=C() print(C.mro()) #C->A->B->object c.test()mro列表
5.屬性查找
‘‘‘ 1、新式類: 繼承object的類,以及該類的子類,都是新式類 在python3中,如果一個類沒有指定繼承的父類,默認就繼承object 所以說python3中所有的類都是新式類 2、經典類(只有在python2才區分經典類與新式類): 沒有繼承object的類,以及該類的子類,都是經典類 ‘‘‘
單繼承名稱空間的查找順序:
對象自身-------->>當前類-------->>父類-------->>object # 查找不到,報錯
多繼承名稱空間的查找順序:
在菱形繼承的背景下,查找屬性
1、經典類:深度優先
2、新式類:廣度優先
二、封裝
1.什麽是封裝
字面上的意思就是,把東西隱藏起來了。
在python中的封裝就是把 類中的屬性(變量、函數)隱藏起來
2.為什麽要用封裝
封裝的真諦在於明確內外
對外隱藏(類的外部只能通過我們提供的接口對類內部的隱藏屬性就行訪問)
對內開放(在類的內部可以直接使用隱藏屬性);
封裝數據(變量):將數據隱藏並不是我們的目的,可以通過接口的方式將數據暴露給類外面使用,在接口中我們可以對數據進行限制,完成對數據的控制
class Teacher: def __init__(self,name,age): # self.__name=name # self.__age=age self.set_info(name,age) def tell_info(self): print(‘姓名:%s,年齡:%s‘ %(self.__name,self.__age)) def set_info(self,name,age): if not isinstance(name,str): raise TypeError(‘姓名必須是字符串類型‘) if not isinstance(age,int): raise TypeError(‘年齡必須是整型‘) self.__name=name self.__age=age t=Teacher(‘egon‘,18) t.tell_info() t.set_info(‘egon‘,19) t.tell_info()示例代碼
封裝方法(函數):主要目的隔離復雜度,將類中多個函數組合,提供一個對外封裝好的接口,供使用者調用,而調用者無需考慮接口內復雜的實現過程,簡化調用
#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢 #對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這麽做 #隔離了復雜度,同時也提升了安全性 class ATM: def __card(self): print(‘插卡‘) def __auth(self): print(‘用戶認證‘) def __input(self): print(‘輸入取款金額‘) def __print_bill(self): print(‘打印賬單‘) def __take_money(self): print(‘取款‘) def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw() 隔離復雜度的例子示例代碼
3.怎麽用封裝
在python中用雙下劃線開頭的方式將屬性隱藏起來(設置成私有的)
#其實這僅僅這是一種變形操作且僅僅只在類定義階段發生變形 #類中所有雙下劃線開頭的名稱如__x都會在類定義時自動變形成:_類名__x的形式: class A: __N=0 #類的數據屬性就應該是共享的,但是語法上是可以把類的數據屬性設置成私有的如__N,會變形為_A__N def __init__(self): self.__X=10 #變形為self._A__X def __foo(self): #變形為_A__foo print(‘from A‘) def bar(self): self.__foo() #只有在類內部才可以通過__foo的形式訪問到. #A._A__N是可以訪問到的, #這種,在外部是無法通過__x這個名字訪問到。
PS:
1.這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然後就可以訪問了,如a._A__N,即這種操作並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形,主要用來限制外部的直接訪問。
2.變形的過程只在類的定義時發生一次,在定義後的賦值操作,不會變形
3.在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的
#正常情況 >>> class A: ... def fa(self): ... print(‘from A‘) ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print(‘from B‘) ... >>> b=B() >>> b.test() from B #把fa定義成私有的,即__fa >>> class A: ... def __fa(self): #在定義時就變形為_A__fa ... print(‘from A‘) ... def test(self): ... self.__fa() #只會與自己所在的類為準,即調用_A__fa ... >>> class B(A): ... def __fa(self): ... print(‘from B‘) ... >>> b=B() >>> b.test() from A示例代碼
4.特性(property)
什麽是特性
property是一種特殊的屬性,訪問它時會執行一段功能(函數)然後返回值
eg:
BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便於理解) 成人的BMI數值: 過輕:低於18.5 正常:18.5-23.9 過重:24-27 肥胖:28-32 非常肥胖, 高於32 體質指數(BMI)=體重(kg)÷身高^2(m) EX:70kg÷(1.75×1.75)=22.86功能描述
class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property def bmi(self): return self.weight / (self.height * self.height) # egon=People(‘egon‘,75,1.80) # # egon.bmi=egon.weight / (egon.height * egon.height) # print(egon.bmi) # # yl=People(‘yangli‘,85,1.74) # yl.bmi=yl.weight / (yl.height * yl.height) # print(yl.bmi) # 首先需要明確。bmi是算出來的,不是一個固定死的值,也就說我們必須編寫一個功能,每次調用該功能 #都會立即計算一個值 egon=People(‘egon‘,75,1.80) yl=People(‘yangli‘,85,1.74) # 但很明顯人的bmi值聽起來更像一個名詞而非動詞 # print(egon.bmi()) # print(yl.bmi()) # 於是我們需要為bmi這個函數添加裝飾器,將其偽裝成一個數據屬性 # egon.weight=70 # print(egon.bmi) #21.604938271604937,調用egon.bmi本質就是觸發函數bmi的執行,從而拿到其返回值 # print(yl.bmi)示例代碼
為什麽要用特性
將一個類的函數定義成特性以後,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然後計算出來的,這種特性的使用方式遵循了統一訪問的原則
PS:
【public】
這種其實就是不封裝,是對外公開的
【protected】
這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,
但我不知道為什麽大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開
【private】
這種封裝對誰都不公開
python並沒有在語法上把它們三個內建到自己的class機制中,可以通過property方法實現
class Foo: def __init__(self,val): self.__NAME=val #將所有的數據屬性都隱藏起來 @property def name(self): return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): #在設定值之前進行類型檢查 raise TypeError(‘%s must be str‘ %value) self.__NAME=value #通過類型檢查後,將值value存放到真實的位置self.__NAME @name.deleter def name(self): raise TypeError(‘Can not delete‘) f=Foo(‘egon‘) print(f.name) # f.name=10 #拋出異常‘TypeError: 10 must be str‘ del f.name #拋出異常‘TypeError: Can not delete‘示例代碼
三、多態
1、什麽是多態
多態指的是同一種事物多種形態
2、為什麽要用多態
用基類創建一套統一的規則,強制子類去遵循(使用抽象類實現),這樣便可以
在不用考慮對象具體類型的前提下而直接使用對象下的方法
3、如何用多態
動物有多種形態:人,狗,豬
import abc class Animal(metaclass=abc.ABCMeta): #同一類事物:動物 @abc.abstractmethod def talk(self): pass class People(Animal): #動物的形態之一:人 def talk(self): print(‘say hello‘) class Dog(Animal): #動物的形態之二:狗 def talk(self): print(‘say wangwang‘) class Pig(Animal): #動物的形態之三:豬 def talk(self): print(‘say aoao‘)Animal
文件有多種形態:文本文件,可執行文件
import abc class File(metaclass=abc.ABCMeta): #同一類事物:文件 @abc.abstractmethod def click(self): pass class Text(File): #文件的形態之一:文本文件 def click(self): print(‘open file‘) class ExeFile(File): #文件的形態之二:可執行文件 def click(self): print(‘execute file‘)File
PS:
1.抽象基類是不能實例化的
2.繼承抽象基類的類,必須重寫其抽象父類中的抽象方法
4、多態性
1.什麽是多態動態綁定(在繼承的背景下使用時,又稱為多態性)
多態性是指在不考慮實力類型的情況下使用實例
在面向對象方法中一般是這樣表述多態性:向不同的對象發送同一條消息(!!!obj.func():是調用了obj的方法func,又稱為向obj發送了一條消息func),不同的對象在接收時會產生不同的行為(即方法)。也就是說,每個對象可以用自己的方式去響應共同的消息。所謂消息,就是調用函數,不同的行為就是指不同的實現,即執行不同的函數。
比如:老師.下課鈴響了(),學生.下課鈴響了(),老師執行的是下班操作,學生執行的是放學操作,雖然二者消息一樣,但是執行的效果不同
解釋
2.為什麽要用多態性(多態性的好處)
- 增加了程序的靈活性
以不變應萬變,不論對象千變萬化,使用者都是同一種形式去調用
- 增加了程序的可擴展性
通過繼承animal類創建了一個新的類,使用者無需更改自己的代碼,還是用func(animal)去調用
>>> class Cat(Animal): #屬於動物的另外一種形態:貓 ... def talk(self): ... print(‘say miao‘) ... >>> def func(animal): #對於使用者來說,自己的代碼根本無需改動 ... animal.talk() ... >>> cat1=Cat() #實例出一只貓 >>> func(cat1) #甚至連調用方式也無需改變,就能調用貓的talk功能 say miao ‘‘‘ 這樣我們新增了一個形態Cat,由Cat類產生的實例cat1,使用者可以在完全不需要修改自己代碼的情況下。使用和人、狗、豬一樣的方式調用cat1的talk方法,即func(cat1) ‘‘‘示例代碼
3.鴨子類型
Python中崇尚鴨子類型(如果看起來像,叫聲像而且走起路像鴨子,那麽它就是鴨子)
通過繼承實現的多態性,具有強耦合性;
鴨子類型,通過創建一個外觀和行為像,但與原類型毫無關系的全新對象,具有松耦合性;
#其實大家一直在享受著多態性帶來的好處, #比如Python的序列類型有多種形態:字符串,列表,元組,多態性的體現 # #str,list,tuple都是序列類型 s=str(‘hello‘) l=list([1,2,3]) t=tuple((4,5,6)) #我們可以在不考慮三者類型的前提下使用s,l,t s.__len__() l.__len__() t.__len__() len(s) len(l) len(t)示例代碼
Python 全棧開發:python面向對象三大特征