python粗談面向對象 百日築基(八)
1、面向過程編程vs函數式編程
面向過程編程 以計算對象的元素個數為例。
str_1 = ‘abcdefg‘ count = 0 for i in str_1: # 統計字符串元素個數 count += 1 list_1 = [1,2,3,4] count = 0 for i in list_1: # 統計列表元素個數 count += 1
由上面的內容我們可以看出如果還有需要統計元素的對象,那麽我們還需要寫一個for循環。如果有一百個需要統計的對象我們就需要寫一百個for循環。但是可以發現for的功能都是一樣統計的,都是統計對象元素個數。下面以函數的形式實現上面的例子:
def func(iter): count = 0 for i in iter: count += 1 return count func(‘abcdefg‘) # 調用函數統計字符串 func([1,2,3,4]) # 調用函數統計列表
上面的代碼先定義一個函數,函數主要用於統計可叠代對象元素的個數,在調用函數時將需要統計元素個數的對象傳遞給函數即可。代碼結構相對於面向過程而言,減少了重復代碼,並且結構也更加清晰。
總結:
- 面向函數編程相對於面向過程減少了代碼冗余。
- 面向函數編程相對於面向過程代碼可讀性更高,結構更加清晰。
2、面向過程編程和面向對象的對比
面向過程編程
# auth 認證相關 def login(): pass def regisgter(): pass # account 賬戶相關 def get_user_pwd(): pass defcheck_user_pwd(): pass # 購物車相關 def shopping(username,money): pass def check_paidgoods(username,money): pass def check_unpaidgoods(username,money): pass def save(username,money): pass
面向對象編程
class LoginHandler: def login(self): pass def regisgter(self): pass class Account: def get_user_pwd(self): pass def check_user_pwd(self): pass class ShoppingCar: def shopping(username,money): pass def check_paidgoods(username,money): pass def check_unpaidgoods(username,money): pass def save(username,money): pass
從對比可以發現面向對象的編程相比於函數編程代碼的可讀性更好,結構更加清晰,明確。
3、類
先來看一下類的示例:
class Personnel: name = "小明" # 靜態屬性 age = 21 sex = "男" def work(self): # 動態方法 print(‘人類需要工作來獲取面包‘)
類:是一組相似功能的集合,讓代碼的組織結構更加清晰、規範化。
定義一個類。
class 是關鍵字與def用法相同,Personnel是此類的類名,類名使用駝峰(CamelCase)命名風格,首字母大寫,私有類可用一個下劃線開頭。
類的結構從大方向來說就分為兩部分:
屬性(靜態變量)。
方法(動態方法)。
使用類名.__dict__查看類中所有屬性、方法。還可以使用.__dict__[屬性]的方式查看單個屬性,如下:
class Personnel: name = "小明" # 靜態屬性 age = 21 def work(self): # 第二部分:方法 函數 動態屬性 print(‘人類需要工作來獲取面包‘) print(Personnel.__dict__)# 使用dict可以查看類中所有屬性,方法 print(Personnel.__dict__["name"]) # 打印類中‘name‘屬性
使用類名.的方式來操作類中的屬性和方法
class Personnel: name = "小明" # 靜態屬性 age = 21 def work(self): # 第二部分:方法 函數 動態屬性 print(‘人類需要工作來獲取面包‘) # 操作類中的屬性 print(Personnel.name) # 查 Personnel.name = "小紅" # 改 Personnel.sex = "女" # 增 del Personnel.age # 刪 # 調用類中的方法 Personnel.work(1) # 這裏需要隨便給一個參數
類在調用自己內部方法時,需要傳遞一個參數,因為在類中定義實例化方法時會有一個默認參數self,self參數是類實例化對象的指針,用於在類的方法中可以使用對象的屬性。所以類在調用方法時要傳遞一個參數給self。
類可以調用自己內部的屬性和方法,如果想要修改屬性可以用類進行操作,但是如果想調用類的實例化方法約定俗稱使用對象調用,而不是使用類去調用。
4、類的實例化:
class Personnel: name = "小明" # 靜態屬性 age = 21 def work(self): # 動態方法 print("人類需要工作來獲取面包") obj = Personnel() # 實例化類
實例化類後就可以通過對象來調用類的方法和屬性,對象是不可以修改類的屬性和方法的。如果想在類中封裝對象的私有屬性,可在類中使用__init__函數來封裝對象的屬性。如下:
class Personnel: name = "小明" # 靜態屬性 age = 21 def __init__(self,name,age): # 封裝對象屬性 self.name = name self.age = age obj = Personnel("葫蘆娃",12) # 實例化類,並傳遞屬性值 print(obj.name,obj.age) # 打印內容如下 葫蘆娃 12
實例化Personnel時,總共發生了三件事:
- 在內存中為對象開辟了一個空間。
- 自動執行類中的__init__方法,並將這個對象空間(內存地址)傳給了__init__方法的第一個位置參數self。
- 在__init__ 方法中通過self給對象封裝私有屬性。
在對象中操作屬性:
class Personnel: def __init__(self,name,age): # 封裝對象屬性 self.name = name self.age = age def work(self): # 第二部分:方法 函數 動態屬性 print(‘人類需要工作來獲取面包‘) obj = Personnel("葫蘆娃",12) # 實例化類,並傳遞屬性值 print(obj.name) # 查 obj.name = "小紅" # 改 obj.sex = "女" #增 del obj.age # 刪 obj.work() # 調用類中方法
類中的方法一般都是通過對象執行的(除類方法,靜態方法外),並且對象執行這些方法都會自動將對象空間傳給方法中的第一個參數self以便對象在調用方法時可以使用自己的屬性,所以類中的實例化方法都有一個參數self,參數名可以不是self,但是約定俗成函數的第一個參數都使用self。那self 是什麽?
self其實就是類中方法(函數)的第一個位置參數,在對象調用類中方法時,會把自己的內存地址傳給參數self,這樣就可以在類中通過使用self來操作對象的屬性了。
二、對象
對象:是類的具體體現(類的實例化 )。
對象屬性的封裝,有三種形式:
- 在類內部__init__()方法中封裝對象的屬性
- 在類內部實例化方法中封裝對象的屬性
- 在類外部添加對象屬性
下面是代碼示例:
class Personnel: def __init__(self,name,age): # 在類__init__方法中封裝對象屬性 self.name = name self.age = age def work(self): # 在類的work()方法添加對象的屬性 self.work = "掃地" print(f"{self.name}通過{self.work}來獲取面包") obj = Personnel("小明",21) # 實例化類 obj.sex = "男" # 在類外添加對象屬性
由此我們知道對象的屬性可以在類的__init__方法中封裝,也可以在類的實例化方法中進行封裝,還可以在類外面進行添加,那對象是如何找到這些屬性的呢?
通過觀察可以發現__init__(self)和類的實例化方法都有一個默認參數self。我們知道self是對象空間指針,所以我們通過self封裝對象的屬性都是在對象空間的操作。所以也就不難理解對象可以找到這些屬性了。至於可以在類外添加對象屬性並且能找到也就不難理解了。
對象查找屬性的順序:對象空間 -> 類空間 -> 父類空間 -> object
類名查找屬性的順序:本類空間 -> 父類空間 -> object
面向對象三大特性
面向對象三大特性:封裝、繼承、多態
1. 封裝: 是指將具有相似功能的屬性,方法封裝在某個對象內部,隱藏代碼的實現細節不被外界發現,外界只能通過對象使用該接口,而不能通過任何形式修改對象內部實現,正是由於封裝機制,程序在使用某一對象時不需要關心該對象的數據結構細節及實現操作的方法。使用封裝能隱藏對象實現細節,使代碼更易維護,同時因為不能直接調用、修改對象內部的私有信息,在一定程度上保證了系統安全性。類通過將函數和變量封裝在內部,實現了比函數更高一級的封裝。
2. 繼承: 如果兩個類是繼承關系,繼承的類稱為子類(Subclass),而被繼承的類稱為基類、父類或超類(Base class、Super class)。繼承最大的好處是子類獲得了父類的全部變量和方法的同時,又可以根據需要封裝自己的屬性和方法。
3. 多態: Python其實默認就支持多態的,所謂多態是指一類事物可以有多種形態,而Python最大的特性就是一個變量會隨著數據類型的改變而自我調整。如a="a" 是一個str類型,a=10就變成了int類型。
鴨子類型:看起來像鴨子就是鴨子(只是名字相似內部具體的實現不一定一樣)例如:str類型中的方法index()查詢下標,list中也有方法index()用於查下標雖然它們看起來像但是它們不是一個類中的方法。這麽做的好處就是統一標準和規範。
類的繼承
下面是一段簡單代碼示例:
class Aniaml(object): life = "野外" leg = 4 def __init__(self,name,age,color,food): self.name = name self.age = age self.color = color self.food = food def eat(self): print(f"{self.name}喜歡吃{self.food}") class Cat(Aniaml): pass class Dog(Aniaml): pass
上面的代碼首先定義一個動物類,我們可以把這個類看成一個模板類,裏面是一些動物共有的特性,然後創建小貓的類和小狗類繼承動物類,這樣小貓和小狗的類就繼承了動物類的屬性和方法,由此繼承的優點總結如下:
1、增加了類的耦合性(耦合性不宜多,宜精)。
2、減少了重復代碼。
3、使得代碼更加規範化,合理化。
繼承分為單繼承和多繼承兩類。關於繼承有個需要了解的地方,在Python2.2之前如果只是定義class A:那麽這個A類將什麽都不繼承屬於獨立的類。我們將這種類稱為經典類。在Python2.2以後面如果加上object如class A(object)這樣便是新式類。在Python3.X中只有新式類,如果沒有寫object系統隱式的幫我們繼承了object類。
單繼承:如下示例就屬於單繼承
class Aniaml(object): life = "野外" leg = 4 def __init__(self,name,age,color,food): self.name = name self.age = age self.color = color self.food = food def eat(self): print(f"{self.name}喜歡吃{self.food}") class Cat(Aniaml): pass class Dog(Aniaml): pass
類的方法重新:
類的查找順序我們知道首先是在本類中查找,如果本類沒有到父類查找以此類推。如下示例:
class Aniaml(object): life = "野外" leg = 4 def __init__(self,name,age,color,food): self.name = name self.age = age self.color = color self.food = food def eat(self): print(f"{self.name}喜歡吃{self.food}") class Cat(Aniaml): def eat(self): print(f"{self.name}在吃{self.food}") obj = Cat("小花貓",21,"黑白花","魚") obj.eat() # 調用eat方法 # 打印內容如下 小花貓在吃魚
上面代碼中父類Aniaml中有eat()方法,子類Cat中也有eat方法。執行的結果和我們預想的一樣。對象obj執行了本類中的eat方法沒有執行父類的eat方法。我們將這種情況稱為將父類的方法重寫。
當我們想既要執行父類中的eat方法又執行子類中的eat方法可以使用supper方法,如下示例:
class Aniaml(object): life = "野外" leg = 4 def __init__(self,name,age,color,food): self.name = name self.age = age self.color = color self.food = food def eat(self): print("吃東西") class Cat(Aniaml): def eat(self): # 方法重寫 print(f"{self.name}在吃{self.food}") class Dog(Aniaml): def eat(self): super().eat() # 調用父類方法 print(f"{self.name}在吃{self.food}") obj_cat = Cat("小花貓",21,"黑白花","魚") obj_cat.eat() # 調用eat方法 obj_dog = Dog("二哈",2,"黑白","西瓜") obj_dog.eat() # 打印內容如下 小花貓在吃魚 吃東西 二哈在吃西瓜
多繼承:一個類可以繼承多個父類。
類的多繼承涉及到了類的屬性和方法的查找順序。不像單繼承一條路跑到黑一直像父類查找即可。多繼承的查找順序涉及到了深度優先算法和C3算法。
深度優先算法:是經典類中的查找順序。首先從最左側的父類開始向上查找,如果父類也是繼承多個類那麽繼續從最左側繼續向上查找直到找到根然後回退查找右側的父類。如下圖示例
深度優先查找順序如下圖所示:
C3算法:主要是在新式類中體現.新式類主要體現在繼承object類上,C3算法查找順序遵循的是mro序列公式:
mro(子類(父類A,父類B))=[子類]+merge(mro(父類A),mro(父類B),[父類A,父類B])
表頭和表尾
表頭: 列表的第一個元素
表尾: 列表中除表頭以外的元素集合(可以為空)
如:列表[A,B,C,D]其中A是表頭,B,C,D是表尾.只有表頭和表尾的概念沒有其它.C3算法的核心是對比表頭。
C3繼承關系圖
根據上圖進行C3算法推算如下:
mro(A(B,C)) = [A] + merge(mro(B), mro(C), [B,C])
第一步:先計算mro(B)
mro(B) = mro(B(J)) = [B] + [J] = [B,J]
第二步:計算mro(C)
mro(C) = mro(C(D,E)) = [C] + merge(mro(D),mro(E),[D,E])
mro(C) = [C] + merge([D,F],mro(E),[D,E])
mro(E)= [E] +([G,I],[H,I],[G,H])
mro(E)= [E,G] +([I],[H,I],[H])
mro(E)= [E,G,H] +([I],[I])
mro(E)= [E,G,H,I]
mro(C) = [C] + merge([D,F],[E,G,H,I] ,[D,E])
= [C,D] + merge([F],[E,G,H,I] ,[E])
= [C,D,F] + merge([E,G,H,I] ,[E])
= [C,D,F,E] + merge([G,H,I])
= [C,D,F,E,G,H,I]
第三步計算mro(A(B,C)) = [A] + merge(mro(B), mro(C), [B,C])
mro(A) = [A] + merge([B,J], [C,D,F,E,G,H,I], [B,C])
= [A,B] + merge([J], [C,D,F,E,G,H,I], [C])
= [A,B,J] + merge( [C,D,F,E,G,H,I], [C])
= [A,B,J,C] + merge( [D,F,E,G,H,I],)
= [A,B,J,C,D,F,E,G,H,I]
最終C3查找順序圖如下:
python粗談面向對象 百日築基(八)