1. 程式人生 > 實用技巧 >Python面向物件,站在更高的角度來思考

Python面向物件,站在更高的角度來思考

開篇

面向過程程式設計和麵向物件程式設計是兩種基本的程式設計思想。

很多人學習python,不知道從何學起。
很多人學習python,掌握了基本語法過後,不知道在哪裡尋找案例上手。
很多已經做案例的人,卻不知道如何去學習更加高深的知識。
那麼針對這三類人,我給大家提供一個好的學習平臺,免費領取視訊教程,電子書籍,以及課程的原始碼!
QQ群:1097524789



在這之前,你學習到的都是面向過程程式設計,即:首先分析出解決問題的每個步驟 ,然後編寫函式實現每個步驟,最後通過合適的順序依次呼叫函式來解決問題。

不同於面向過程程式設計,面向物件程式設計的思想其實是從自然界的機理中借鑑而來的。正所謂物以類聚,在渺渺的大自然中,有數不勝數的類別,按照不同的星球來分類,可以分為:地球類、太陽類、火星類、月球類、海王星類等等


按照地球上的不同形態的生命,可以分為植物類、動物類、細菌類等等


你還可以按照自己的喜好,劃分出自己的分類標準。

使用面向物件程式設計,可以讓你站在一個更高的視角來審視問題,此時,你所解決的不僅僅是一個問題,而是一類問題。

對比面向過程,初識面向物件

在介紹各種概念之前,讓我們先做一個小栗子,通過對比面向過程程式設計的方式去感受面向物件程式設計的奧妙。

栗子如下:

小明家裡養了3只小貓,每到中午飯點,這三隻小貓便要吃飯了。吃飯前,小貓們都會 先洗手,然後喵喵叫三聲。請寫一個程式,來分別模擬這三隻小貓完整的吃飯過程,部分細節可自行腦部。

分析問題,可以繪製出解決問題的流程圖

如果採用面向過程思想進行程式設計,可以這樣寫:


首先分別用函式實現以上流程圖中的三個步驟

importtime
#模擬吃飯
defeat(number,food):
#number:小貓編號,可取1,2,3
#food:某編號的小貓所吃食物的名稱
wash(number)#模擬洗手
miao(number)#模擬喵喵叫三聲
print("{}號小貓正在吃{}".format(number,food))
time.sleep(5)#吃飯需要5秒種
print('{}號小貓進食完畢,睡覺睡覺咯'.format(number))
print()

#模擬洗手
defwash(number):
#number:小貓編號,可取1,2,3
print('{}號小貓正在洗手...'.format(number))
time.sleep(3)#洗手花費3秒種
print('{}號小貓洗手結束'.format(number))

#模擬喵喵叫三聲
defmiao(number):
#number:小貓編號,可取1,2,3
print('第{}號小貓喵喵喵~~'.format(number))

有了函式之後,就可以按照合適的序列步驟呼叫函式來解決問題了:

#模擬第一隻小貓的吃飯過程
eat(1,'樹葉子')

#模擬第二隻小貓的吃飯過程
eat(2,'海帶絲')

#模擬第三隻小貓的吃飯過程
eat(3,'石頭')

得到如下輸出:

1號小貓正在洗手...
1號小貓洗手結束
第1號小貓喵喵喵~~
1號小貓正在吃樹葉子
1號小貓進食完畢,睡覺睡覺咯

2號小貓正在洗手...
2號小貓洗手結束
第2號小貓喵喵喵~~
2號小貓正在吃海帶絲
2號小貓進食完畢,睡覺睡覺咯

3號小貓正在洗手...
3號小貓洗手結束
第3號小貓喵喵喵~~
3號小貓正在吃石頭
3號小貓進食完畢,睡覺睡覺咯

而若採用面向物件程式設計,則可以寫出如下程式碼(稍後會對每一句程式碼進行解釋,這裡只是先直觀感受下面向物件程式設計的feel):

importtime
classCatMeal():

#number:小貓編號,可取1,2,3
#food:某編號的小貓所吃食物的名稱
def__init__(self,number,food):
self.number=number
self.food=food

#模擬吃飯
defeat(self):
self.wash()
self.miao()
print("{}號小貓正在吃{}".format(self.number,self.food))
time.sleep(5)#吃飯需要5秒種
print('{}號小貓進食完畢,睡覺睡覺咯'.format(self.number))
print()

#模擬洗手
defwash(self):
#number:小貓編號,可取1,2,3
print('{}號小貓正在洗手...'.format(self.number))
time.sleep(3)#洗手花費3秒種
print('{}號小貓洗手結束'.format(self.number))

#模擬喵喵叫三聲
defmiao(self):
#number:小貓編號,可取1,2,3
print('第{}號小貓喵喵喵~~'.format(self.number))

#模擬第一隻小貓的吃飯過程
miao1=CatMeal(1,'樹葉子')
miao1.eat()

##模擬第二隻小貓的吃飯過程
miao2=CatMeal(2,'海帶絲')
miao2.eat()

#模擬第三隻小貓的吃飯過程
miao3=CatMeal(3,'石頭')
miao3.eat()

得到同樣的輸出:

1號小貓正在洗手...
1號小貓洗手結束
第1號小貓喵喵喵~~
1號小貓正在吃樹葉子
1號小貓進食完畢,睡覺睡覺咯

2號小貓正在洗手...
2號小貓洗手結束
第2號小貓喵喵喵~~
2號小貓正在吃海帶絲
2號小貓進食完畢,睡覺睡覺咯

3號小貓正在洗手...
3號小貓洗手結束
第3號小貓喵喵喵~~
3號小貓正在吃石頭
3號小貓進食完畢,睡覺睡覺咯

在上面的程式碼中,我們使用關鍵字class定義了一個小貓吃飯的類,名字叫做CatMeal,然後例項化出來了3只小貓,分別模擬了它們吃飯的完整過程。你可能還是對有些程式碼,比如__init__那裡搞不清,沒關係,先彆著急,在下一部分,我們將詳解上述程式碼,在此之前,有一些東西需要講一下:

如果你閱讀過之前的文章,你會發現,那些文章的思路都是先通過介紹某種方法的不足,然後引出正題,比如由於需要用多個變數來儲存多個數字,於是引出了列表這一簡單高效的資料結構。在這裡,我們先介紹了面向過程的方法,之後才介紹了面向物件的方法,那是不是面向物件比面向過程更具有優勢呢?

並不是,這兩種程式設計思想各有千秋。

我們可以按照從簡到繁的順序來捋清這一切:

(1)對於一個非常簡單的問題,比如計算兩數之和,我們甚至可以直接在命令列中敲下a+b,點選回車即可看到問題的答案:

>>>1+2
3

(2)接著將問題變複雜些,這次也是計算兩數之和,只不過待計算的數值對不止一個,於是我們可以定義一個函式,專門用於做兩數之和:

defadd(a,b):
print('{}+{}={}'.format(a,b,a+b))
#呼叫函式計算兩數之和
>>>add(1,2)
1+2=3

(3)然後,將問題更加複雜化,需要一個簡易版計算器,可以用來計算加法和減法,並且要求使用者只需輸入運算元(待運算的數字,比如1,2,3),而無需輸入運算子(比如+,-):

採用面向過程的思想,我們需要定義加法和減法兩個函式

defadd(a,b):
print('{}+{}={}'.format(a,b,a+b))
defminus(a,b):
print('{}-{}={}'.format(a,b,a-b))
#呼叫函式
add(1,2)
minus(2,1)
#輸出
1+2=3
2-1=1

而若採用面向物件的思想,我們可以這樣寫:

classCal():
defadd(self,a,b):
print('{}+{}={}'.format(a,b,a+b))
defminus(self,a,b):
print('{}-{}={}'.format(a,b,a-b))

在上面的程式碼中,我們定義了一個計算類,名字叫做Cal

接下來從這個類中例項化出來一個物件,名字叫做z

#例項化出來一個物件z
z=Cal()

#呼叫方法
z.add(1,2)
z.minus(3,6)

輸出結果

1+2=3
3-6=-3

(4)如果現在去除了使用者不能輸入運算子的限制,那麼,使用面向過程程式設計的程式碼如下:

defadd_or_minus(a,b,op):
ifop=='+':
res=a+b
elifop=='-':
res=a-b
print('{}{}{}={}'.format(a,op,b,res))
#呼叫
>>>add_or_minus(1,2,'+')
1+2=3
>>>add_or_minus(1,2,'-')
1-2=-1

使用面向物件程式設計程式碼如下:

classCal():
defadd_or_minus(self,a,b,op):
ifop=='+':
res=a+b
elifop=='-':
res=a-b
print('{}{}{}={}'.format(a,op,b,res))
#例項化類並計算
>>>z=Cal()
>>>z.add_or_minus(1,2,'+')
1+2=3
>>>z.add_or_minus(1,2,'-')
1-2=-1

再次對比這兩者,可以看出,面向物件程式設計給人的感覺是程式碼變得麻煩了些,而面向過程程式設計則清爽很多。

原因在於,實現加法和減法是個很簡單的問題,在這個問題上使用面向物件的程式設計方式不能很好的體現出面向物件程式設計的靈活性和規範性,反而簡單的面向過程程式設計更加易懂、易實現。

在今後,你會看到,當問題規模變大時,面向物件程式設計將會體現出來顯著的優勢。

總結來說:面向過程和麵向物件這兩種程式設計方式各有千秋,在解決規模較小的問題時使用前者更為方便,而對於大規模問題,後者具有顯著優勢。當然這也不是絕對的。在效能和維護成本等方面,兩者也是不同的。所以,對於不同的應用場景,要學會靈活選用不同的程式設計方式。

面向物件程式設計的語法

在這一部分,我們將對之前栗子中面向物件程式碼進行拆分講解。

定義一個類

class CatMeal():中:

class是一個Python關鍵字,表明要定義一個類,它的作用就好比定義函式時用到的關鍵字def

CatMeal是類的名字,一般約定首字母大寫的代表類名,而首字母為小寫的代表變數命

在還沒有學習後面的內容之前,當你想建立一個新的類時,只需修改類名即可,其餘照抄,比如定義一個動物類,可以寫class Animal():。當然,在後面的學習中,你會解鎖關於這一句程式碼的更高階用法。

在類中定義方法

我們知道,以關鍵字def定義的一個程式碼塊叫做函式,用於完成特定的功能。其實,在類裡面,也是使用def定義一個程式碼塊,同樣是用於完成特定的功能,只不過此時的名字不再是“函式”,而是“方法”(本質上還是函式)。在這個栗子中,定義的__init__eatwashmiao都是方法。

__init__方法預設第一個引數是self(約定俗成的叫法),後面的引數可以自行指定,就像給函式傳參那樣。

__init__方法裡,以引數形式定義特徵,稱之為屬性。

比如本慄中的numberfood就是類中定義的兩個屬性,你可以類比著新增更多屬性。

當然,如果像之前的簡易計算器栗子一樣,不要求在建立類時手動指定屬性,那麼就可以不顯式地寫出__init__方法(可以回看那個栗子來加深理解)。

觀察eatwashmiao方法,你會發現,每個方法裡面都和__init__方法一樣,第一個引數為self

其實,無論是__init__方法也好,其餘自定義的方法也罷,它們的第一個引數都必須是self,它代表一個完整的例項化物件。與類相關聯的屬性和方法都會自動傳遞給self,通過使用self,可以輕鬆呼叫類中的一些屬性和方法:

比如在eat方法中,我們傳入了self,於是便可以在該方法中使用self.number獲取在__init__方法中定義好的number具體值,同理可以使用self.food獲取food的具體值。

從類中例項化物件

在編寫完成這個CatMeal類之後,便可以通過類來例項化物件了,比如:

miao1=CatMeal(1,'樹葉子')

執行這一句程式碼,會從抽象的類中例項化出來一個物件,名字叫做miao1,同時自動呼叫__init__方法,並將1樹葉子分別傳給__init__方法中的numberfood

其內部具體實現過程如下:先在記憶體中開闢一塊空間,然後呼叫__init__方法,並讓self指向那塊記憶體空間,最後讓例項化的物件miao1也指向那塊記憶體空間

這個例項化的物件miao1可以通過.來訪問類中的屬性和呼叫類中的方法,比如訪問food屬性,可以寫miao1.food;呼叫wash方法,可以寫miao1.wash()

總結

初學者很容易被一堆self所困擾,所以這裡儘量使用精煉的語言來總結第二部分所講內容:

在定義類之後,例項化之前,self這個抽象的東西具有訪問類中屬性和呼叫類中方法的許可權,所以無論是增加屬性,還是增加方法,都離不開self。增加屬性可以寫self.new_attribute=new_attribute,增加方法可以寫

defnew_method(self):
pass

(可以根據需要自行設定方法中的引數)

比如在定義eatwashmiao方法時,由於還沒有例項化物件,因此若要訪問類中的屬性numberfood,必須藉助具有許可權的self。因此,每個方法都需要傳入self這個引數,以使用self.來進行訪問。self彷彿就是一把鑰匙。

而在例項化(比如miao1=CatMeal(1,'樹葉子'))之後,原先self具有的許可權將會複製一份給這個例項化的物件。

之前需要使用self.number來訪問number屬性,現在依然可以,但同時也支援使用例項化物件.number來進行訪問了,比如miao1.number


以上就是本期全部內容了,大家好好消化一下吧