編程語言分為面向過程編程、函數(shù)式編程和面向對象編程。其實 python 就是一種面向對象編程,那么我們先了解一下它們的特點和優(yōu)缺點以及它們的區(qū)別是什么。
面向過程編程:“面向過程”(Procedure Oriented)是一種以過程為中心的編程思想。這些都是以什么正在發(fā)生為 目標進行編程,不同于面向對象的是誰在受影響。與面向對象明顯的不同就是封裝、繼承、類。
面向過程編程最易被初學者接受,其往往用一長段代碼來實現(xiàn)指定功能,開發(fā)過程的思路是將數(shù)據(jù)與函數(shù)按照執(zhí)行的邏輯順序組織在一起,數(shù)據(jù)與函數(shù)分開考慮。
- 特性:模塊化 流程化
- 優(yōu)點:性能比面向對象高,因為類調用時需要實例化,開銷比較大,比較消耗資源;單片機、嵌入式開發(fā)、Linux/Unix 等一般采用面向過程開發(fā),性能是最重要的因素。
- 缺點:沒有面向對象易維護、易復用、易擴展
函數(shù)式編程: 函數(shù)式編程也是種編程方式,它將電腦運算視為函數(shù)的計算。函數(shù)編程語言最重要的基礎是λ演算(lambda calculus),而且 λ 演算的函數(shù)可以接受函數(shù)當作輸入(參數(shù))和輸出(返回值)。
它的主要思想是把運算過程盡量寫成一系列嵌套的函數(shù)調用。
Python 不是也不大可能會成為一種函數(shù)式編程語言,但是它支持許多有價值的函數(shù)式編程語言構建。也有些表現(xiàn)得像函數(shù)式編程機制但是從傳統(tǒng)上也不能被認為是函數(shù)式編程語言的構建。
Python內建函數(shù) : filter()、map()、reduce()、max()、min()
面向對象編程:面向對象是按人們認識客觀世界的系統(tǒng)思維方式,采用基于對象(實體)的概念建立模型,模擬客觀世界分析、設計、實現(xiàn)軟件的辦法。通過面向對象的理念使計算機軟件系統(tǒng)能與現(xiàn)實世界中的系統(tǒng)一一對應。
面向對象編程可以將數(shù)據(jù)與函數(shù)綁定到一起,進行封裝,這樣能夠更快速的開發(fā)程序,減少了重復代碼的重寫過程。
- 特性:抽象 封裝 繼承 多態(tài)
- 優(yōu)點:易維護、易復用、易擴展,由于面向對象有封裝、繼承、多態(tài)性的特性,可以設計出低耦合 的系統(tǒng),使系統(tǒng)更加靈活、更加易于維護
- 缺點:性能比面向過程低
可以拿生活中的實例來理解面向過程與面向對象,例如五子棋,面向過程的設計思路就是首先分析問題的步驟:1、開始游戲,2、黑子先走,3、繪制畫面,4、判斷輸贏,5、輪到白子,6、繪制畫面,7、判斷輸贏,8、返回步驟2,9、輸出最后結果。把上面每個步驟用不同的方法來實現(xiàn)。
如果是面向對象的設計思想來解決問題。面向對象的設計則是從另外的思路來解決問題。整個五子棋可以分為1、黑白雙方,這兩方的行為是一模一樣的,2、棋盤系統(tǒng),負責繪制畫面,3、規(guī)則系統(tǒng),負責判定諸如犯規(guī)、輸贏等。第一類對象(玩家對象)負責接受用戶輸入,并告知第二類對象(棋盤對象)棋子布局的變化,棋盤對象接收到了棋子的變化就要負責在屏幕上面顯示出這種變化,同時利用第三類對象(規(guī)則系統(tǒng))來對棋局進行判定。
可以明顯地看出,面向對象是以功能來劃分問題,而不是步驟。同樣是繪制棋局,這樣的行為在面向過程的設計中分散在了多個步驟中,很可能出現(xiàn)不同的繪制版本,因為通常設計人員會考慮到實際情況進行各種各樣的簡化。而面向對象的設計中,繪圖只可能在棋盤對象中出現(xiàn),從而保證了繪圖的統(tǒng)一。
面向對象編程三大特性
在看面向對象編程三大特性之前,先了解一下對象和類的區(qū)別。
對象和類
類(Class)是現(xiàn)實或思維世界中的實體在計算機中的反映,它將數(shù)據(jù)以及這些數(shù)據(jù)上的操作封裝在一起。類實際上是創(chuàng)建實例的模板。
對象(Object)是具有類類型的變量。類和對象是面向對象編程技術中的最基本的概念。 而對象就是一個一個具體的實例。
定義類的方法:
class 類(): pass
那么如何將類轉換成對象呢?
實例化是指在面向對象的編程中,把用類創(chuàng)建對象的過程稱為實例化。是將一個抽象的概念類,具體到該類實物的過程。實例化過程中一般由類名 對象名 = 類名(參數(shù)1,參數(shù)2...參數(shù)n)構成。
定義類之后一般會用到構造方法 init 與其他普通方法不同的地方在于,當一個對象被創(chuàng)建后,會立即調用構造方法(也稱為魔術方法)。自動執(zhí)行構造方法里面的內容。
1.封裝特性
封裝,顧名思義就是將內容封裝到某個地方,以后再去調用被封裝在某處的內容。 所以,在使用面向對象的封裝特性時,需要:
- 將內容封裝到某處
- 從某處調用被封裝的內容,調用方法如下:
- 通過對象直接調用被封裝的內容: 對象.屬性名
- 通過 self 間接調用被封裝的內容: self.屬性名
- 通過 self 間接調用被封裝的內容: self.方法名()
對于面向對象的封裝來說,其實就是使用構造方法將內容封裝到 對象 中,然后通過 對象直接或者 self 間接獲取被封裝的內容。
具體例子如下:
# 1). 類的定義
class People:
# 構造方法(魔術方法): 當創(chuàng)建對象時會自動調用并執(zhí)行;
# self實質上是實例化出來的對象, e.g: xiaoming, xiaohong;
def __init__(self, name, age, gender):
# print("正在創(chuàng)建對象")
# print(self) # 實質上是一個對象
# 將創(chuàng)建對象的屬性(name, age, gender)封裝到self(就是實例化的對象)變量里面;
# 在類里面定義的變量: 屬性
self.name = name
self.age = age
self.gender = gender
# 在類里面定義的函數(shù): 方法
def eat(self):
print('%s eating......' %(self.name))
def sleep(self):
# 獲取對象self封裝的屬性(name, age, gender)
print('%s sleep.......' %(self.name))
# 2). 實例化: 通過類實現(xiàn)對象的創(chuàng)建
xiaoming = People("小明", 20, '男')
# 將對象self/xiaoming封裝的屬性(name, age, gender)從里面拿出來;
print(xiaoming.name)
xiaoming.eat()
xiaoming.sleep()
xiaohong = People("小紅", 20, '女')
print(xiaohong.name)
xiaohong.eat()
xiaohong.sleep()
2、繼承特性
1)繼承
繼承描述的是事物之間的所屬關系,當我們定義一個 class 的時候,可以從某個現(xiàn)有的 class 繼承,新的 class 稱為子類、擴展類(Subclass),而被繼承的class稱為基類、父類或超類(Baseclass、Superclass)。
問題一:如何實現(xiàn)繼承呢?
子類在繼承的時候,在定義類時,小括號( )中為父類的名字,例如: class son(father): 這里father就是son的父類。
問題二:繼承的工作機制是什么?
父類的屬性、方法,會被繼承給子類。 舉例如下:如果子類沒有定義 init 方法,父類有,那么在子類繼承父類的時候這個方法就被繼承了,所以只要創(chuàng)建對象,就默認執(zhí)行了那個繼承過來的 init 方法。
重寫父類方法:就是在子類中,有一個和父類相同名字的方法,那么在子類中的方法就會覆蓋掉父類中同名的方法,就實現(xiàn)了對父類方法的重寫。
調用父類的方法:
- 在子類中,直接利用 父類名.父類的方法名()
- super() 方法: python2.2+的功能,格式為: super(子類名稱, self).父類的方法名() (建議用此方法)
2)多繼承
多繼承,即子類有多個父類,并且具有它們的特征。
新式類與經(jīng)典類的區(qū)別:
在 Python 2及以前的版本中,由任意內置類型派生出的類,都屬于“新式類”,都會獲得所有“新式類”的特性;反之,即不由任意內置類型派生出的類,則稱之為“經(jīng)典類”。
新式類:
class 類名(object):
pass
經(jīng)典類:
class 類名:
pass
“新式類”和“經(jīng)典類”的區(qū)分在 Python 3之后就已經(jīng)不存在,在 Python 3.x之后的版本,因為所有的類都派生自內置類型 object(即使沒有顯示的繼承 object 類型),即所有的類都是“新式類”。
它們兩個最明顯的區(qū)別在于繼承搜索的順序不同,即:
經(jīng)典類多繼承搜索順序(深度優(yōu)先算法):先深入繼承樹左側查找,然后再返回,開始查找右側。
新式類多繼承搜索順序(廣度優(yōu)先算法):先在水平方向查找,然后再向上查找。
圖示如下:
具體例子如下:
# python2:
# 經(jīng)典類: class Father:
# 新式類: class Father(object):
# python3:
# 所有的都是新式類
# 新式類: 廣度優(yōu)先
# 經(jīng)典類: 深度優(yōu)先
class D:
a = 'd'
class B(D):
pass
class C(D):
a = 'c'
class A(B, C):
pass
obj = A()
print(obj.a)
運行結果是 c,由此可見為廣度優(yōu)先搜索的結果。
3)私有屬性與私有方法
默認情況下,屬性在 Python 中都是“public”, 大多數(shù) OO (面向對象)語言提供“訪問控制符”來限定成員函數(shù)的訪問。
在 Python 中,實例的變量名如果以 __ (雙下劃線)開頭,就變成了一個私有變量/屬性(private),實例的函數(shù)名如果以 __ 開頭,就變成了一個私有函數(shù)/方法(private)只有內部可以訪問,外部不能訪問。
那么私有屬性一定不能從外部訪問嗎?
python2版本不能直接訪問 屬性名,是因為 Python 解釋器對外把 屬性名 改成了 _類名屬性名 ,所以,仍然可以通過 _類名屬性名 來訪問 屬性名 。 但是不同版本的 Python 解釋器可能會把 屬性名 改成不同的變量名,因此不建議用此類方法訪問私有屬性。
私有屬性和方法的優(yōu)勢:
- 確保了外部代碼不能隨意修改對象內部的狀態(tài),這樣通過訪問限制的保護,代碼更加健壯。
- 如果有要允許外部代碼修改屬性怎么辦?可以給類增加專門設置屬性方法。 為什么大費周折?因為在方法中,可以對參數(shù)做檢查,避免傳入無效的參數(shù)。
具體實例如下:
class Student(object):
__country = 'china'
def __init__(self, name, age, score):
self.name = name
# 年齡和分數(shù)是私有屬性
self.__age = age
self.__score = score
# 私有方法, 只能在類內部執(zhí)行;
def __modify_score(self, scores):
self.__score = scores
print(self.__score)
def set_age(self, age):
if 0<age <150:
self.__age = age
print("當前年齡:", self.__age)
else:
raise Exception("年齡錯誤")
def set_score(self, scores):
if len(scores) == 3:
self.__score = scores
else:
raise Exception("成績異常")
class MathStudent(Student):
pass
student = Student("Tom", 10, [100, 100, 100])
# 在類的外部是不能直接訪問的;
print(student.name)
student.set_age(15)
# Python 解釋器對外把 __屬性名 改成了 _類名__屬性名
# print(student._Student__age)
3.多態(tài)特性
多態(tài)(Polymorphism)按字面的意思就是“多種狀態(tài)”。在面向對象語言中,接口的多種不同的實現(xiàn)方式即為多態(tài)。通俗來說: 同一操作作用于不同的對象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結果。
多態(tài)的好處就是,當我們需要傳入更多的子類,只需要繼承父類就可以了,而方法既可以直接不重寫(即使用父類的),也可以重寫一個特有的。這就是多態(tài)的意思。調用方只管調用,不管細節(jié),而當我們新增一種的子類時,只要確保新方法編寫正確,而不用管原來的代碼。這就是著名的“開放封閉”原則:
- 對擴展開放(Open for extension):允許子類重寫方法函數(shù)
- 對修改封閉(Closed for modification):不重寫,直接繼承父類方法函數(shù)