我的施工計劃圖
已完成專題包括:
1我的施工計劃
2數字專題
3字符串專題
4列表專題
5流程控制專題
6編程風格專題
7函數使用專題
今天是面向對象編程的上篇:基礎專題
Python 面向對象編程面向對象程序設計思想,首先思考的不是程序執行流程,它的核心是抽象出一個對象,然後構思此對象包括的數據,以及操作數據的行為方法。
本專題主要討論面向對象編程(OOP)的基礎和進階知識,實際開發模型中OOP的主要實踐,儘量使用最貼切的例子。
基礎專題1 類定義動物是自然界一個龐大的群體,以建模動物類為主要案例論述OOP編程。
Python語言創建動物類的基本語法如下,使用class關鍵字定義一個動物類:
class Animal():
pass類裡面可包括數據,如下所示的Animal類包括兩個數據:self.name和self.speed:
class Animal():
def __init__(self,name,speed):
self.name = name # 動物名字
self.speed = speed # 動物行走或飛行速度注意到類裡面通過系統函數__init__為類的2個數據賦值,數據前使用self保留字。
self的作用是指名這兩個數據是實例上的,而非類上的。
同時注意到__init__方法的第一個參數也帶有self,所以也表明此方法是實例上的方法。
2 實例理解什麼是實例上的數據或方法,什麼是類上的數據,需要先建立實例的概念,類的概念,如下:
# 生成一個名字叫加菲貓、行走速度8km/h的cat對象
cat = Animal('加菲貓',8)cat就是Animal的實例,也可以一次創建成千上百個實例,如下創建1000隻蜜蜂:
bees = [Animal('bee'+str(i),5) for i in range(1000)]總結:自始至終只使用一個類Animal,但卻可以創建出許多個它的實例,因此是一對多的關係。
實例創建完成後,下一步列印它看看:
In [1]: print(cat)
<__main__.Animal object at 0x7fce3a596ad0>結果顯示它是Animal對象,其實列印結果顯示實例屬性信息會更友好,那麼怎麼實現呢?
3 列印實例只需重新定義一個系統(又稱為魔法)函數__str__ ,就能讓列印實例顯示的更加友好:
class Animal():
def __init__(self,name,speed):
self.name = name # 動物名字
self.speed = speed # 動物行走或飛行速度
def __str__(self):
return '''Animal({0.name},{0.speed}) is printed
name={0.name}
speed={0.speed}'''.format(self)使用0.數據名稱的格式,這是類專有的列印格式。
現在再列印:
cat = Animal('加菲貓',8)
print(cat)列印信息如下:
Animal(加菲貓,8) is printed
name=加菲貓
speed=8以上就是想要的列印格式,看到實例的數據值都正確。
4 屬性至此,我們都稱類裡的name和speed稱為數據,其實它們有一個專業名稱:屬性。
同時,上面還有一個問題我們沒有回答完全,什麼是類上的屬性?
如下,在最新Animal類定義基礎上,再添加一個cprop屬性,它前面沒有self保留字:
class Animal():
cprop = "我是類上的屬性cprop"
def __init__(self,name,speed):
self.name = name # 動物名字
self.speed = speed # 動物行走或飛行速度
def __str__(self):
return '''Animal({0.name},{0.speed}) is printed
name={0.name}
speed={0.speed}'''.format(self)類上的屬性直接使用類便可引用:
In [1]: Animal.cprop
Out[1]: '我是類上的屬性cprop'類上的屬性,實例同樣可以引用,並且所有的實例都共用此屬性值:
In [1]: cat = Animal('加菲貓',8)
In [2]: cat.cprop
Out[2]: '我是類上的屬性cprop'Python作為一門動態語言,支持屬性的動態添加和刪除。
如下cat實例原來不存在color屬性,但是賦值時不光不會報錯,相反會直接將屬性添加到cat上:
cat.color = 'grap'那麼,如何驗證cat是否有color屬性呢?使用內置函數hasattr:
In [24]: hasattr(cat,'color') # cat 已經有`color`屬性
Out[24]: True但是注意:以上添加屬性方法僅僅為cat實例本身添加,而不會為其他實例添加:
In [26]: monkey = Animal('大猩猩',2)
In [27]: hasattr(monkey,'color')
Out[27]: Falsemonkey實例並沒有color屬性,注意與__init__創建屬性方法的區別。
5 private,protected,public像name和speed屬性,引用此實例的對象都能訪問到它們,如下:
# 模塊名稱:manager.py
import time
class Manager():
def __init__(self,animal):
self.animal = animal
def recordTime(self):
self.__t = time.time()
print('feeding time for %s(行走速度為:%s) is %.0f'%(self.animal.name,self.animal.speed,self.__t))
def getFeedingTime(self):
return '%0.f'%(self.__t,)使用以上Manager類,創建一個cat實例,xiaoming實例引用cat:
cat = Animal('加菲貓',8)
xiaoming = Manager(cat)xiaoming的recordTime方法引用裡,引用了animal的兩個屬性name和speed:
In[1]: xiaoming.recordTime()
Out[1]: feeding time for 加菲貓(行走速度為:8) is 1595681304注意看到self.__t屬性,它就是一個私有屬性,只能被Manager類內的所有方法引用,如被方法getFeedingTime方法引用。但是,不能被其他類引用。
如果我們連speed這個屬性也不想被其他類訪問,那麼只需將self.speed修改為self.__speed:
同時Manager類的self.animal.speed修改為self.animal.__speed,再次調用下面方法時:
xiaoming.recordTime()就會報沒有__speed屬性的異常,從而驗證了__speed屬性已經變為類內私有,不會暴露在外面。
總結:name屬性相當於java的public屬性,而__speed相當於java的private屬性。
下面在說繼承時,講解protected屬性,實際上它就是帶有1個_的屬性,它只能被繼承的類所引用。
6 繼承上面已經講完了OOP三大特性中的封裝性,而繼承是它的第二大特性。子類繼承父類的所有public和protected數據和方法,極大提高了代碼的重用性。
如上創建的Animal類最新版本為:
class Animal():
cprop = "我是類上的屬性cprop"
def __init__(self,name,speed):
self.name = name # 動物名字
self.__speed = speed # 動物行走或飛行速度
def __str__(self):
return '''Animal({0.name},{0.__speed}) is printed
name={0.name}
speed={0.__speed}'''.format(self)現在有個新的需求,要重新定義一個Cat貓類,它也有name和speed兩個屬性,同時還有color和genre兩個屬性,列印時只需要列印name和speed兩個屬性就行。
因此,基本可以復用基類Animal,但需要修改__speed屬性為受保護(protected)的_speed屬性,這樣子類都可以使用此屬性,而外部還是訪問不到它。
綜合以上,Cat類的定義如下:
class Cat(Animal):
def __init__(self,name,speed,color,genre):
super().__init__(name,speed)
self.color = color
self.genre = genre首先使用super()方法找到Cat的基類Animal,然後引用基類的__init__方法,這樣復用基類的方法。
使用Cat類,列印時,又復用了基類的 __str__方法:
jiafeimao = Cat('加菲貓',8,'gray','CatGenre')
print(jiafeimao)列印結果:
Animal(加菲貓,8) is printed
name=加菲貓
speed=8以上就是基本的繼承使用案例,繼承要求基類定義的數據和行為儘量標準、儘量精簡,以此提高代碼復用性。
7 多態如果說OOP的封裝和繼承使用起來更加直觀易用,那麼作為第三大特性的多態,在實踐中真正運用起來就不那麼容易。有的讀者OOP編程初期,可能對多態的價值體會不深刻,甚至都已經淡忘它的存在。
那麼問題就在:多態到底真的有用嗎?到底使用在哪些場景?
多態價值很大,使用場景很多,幾乎所有的系統或軟體,都能看到它的應用。這篇文章儘可能通過一個精簡的例子說明它的價值和使用方法。如果不用多態,方法怎麼寫;使用多態,又是怎麼寫。
為了一脈相承,做到一致性,仍然基於上面的案例,已經創建好的Cat類要有一個方法列印和返回它的爬行速度。同時需要再創建一個類Bird,要有一個方法列印和返回它的飛行速度;
如果不使用多態,為Cat類新增一個方法:
class Cat(Animal):
def __init__(self,name,speed,color,genre):
super().__init__(name,speed)
self.color = color
self.genre = genre
# 添加方法
def getRunningSpeed(self):
print('running speed of %s is %s' %(self.name, self._speed))
return self._speed重新創建一個Bird類:
class Bird(Animal):
def __init__(self,name,speed,color,genre):
super().__init__(name,speed)
self.color = color
self.genre = genre
# 添加方法
def getFlyingSpeed(self):
print('flying speed of %s is %s' %(self.name, self._speed))
return self._speed最後,上面創建的Manager類會引用Cat和Bird類,但是需要修改recordTime方法,因為Cat它是爬行的,Bird它是飛行的,所以要根據對象類型的不同做邏輯區分,如下所示:
# 模塊名稱:manager.py
import time
from animal import (Animal,Cat,Bird)
class Manager():
def __init__(self,animal):
self.animal = animal
def recordTime(self):
self.__t = time.time()
if isinstance(self.animal, Cat):
print('feeding time for %s is %.0f'%(self.animal.name,self.__t))
self.animal.getRunningSpeed()
if isinstance(self.animal,Bird):
print('feeding time for %s is %.0f'%(self.animal.name,self.__t))
self.animal.getFlyingSpeed()
def getFeedingTime(self):
return '%0.f'%(self.__t,)如果再來一個類,我們又得需要修改recordTime,再增加一個if分支,從軟體設計角度講,這種不斷破壞封裝的行為不可取。
但是,使用多態,就可以保證recordTime不被修改,不必寫很多if分支。怎麼來實現呢?
首先,在基類Animal中創建一個基類方法,然後Cat和Bird分別重寫此方法,最後傳入到Manager類的animal參數是什麼類型,在recordTime方法中就會對應調用這個animal實例的方法,這就是多態。
代碼如下:
animal2.py 模塊如下:
# animal2.py 模塊
class Animal():
cprop = "我是類上的屬性cprop"
def __init__(self,name,speed):
self.name = name # 動物名字
self._speed = speed # 動物行走或飛行速度
def __str__(self):
return '''Animal({0.name},{0._speed}) is printed
name={0.name}
speed={0._speed}'''.format(self)
def getSpeedBehavior(self):
pass
class Cat(Animal):
def __init__(self,name,speed,color,genre):
super().__init__(name,speed)
self.color = color
self.genre = genre
# 重寫方法
def getSpeedBehavior(self):
print('running speed of %s is %s' %(self.name, self._speed))
return self._speed
class Bird(Animal):
def __init__(self,name,speed,color,genre):
super().__init__(name,speed)
self.color = color
self.genre = genre
# 重寫方法
def getSpeedBehavior(self):
print('flying speed of %s is %s' %(self.name, self._speed))
return self._speedmanager2.py 模塊如下:
# manager2.py 模塊
import time
from animal2 import (Animal,Cat,Bird)
class Manager():
def __init__(self,animal):
self.animal = animal
def recordTime(self):
self.__t = time.time()
print('feeding time for %s is %.0f'%(self.animal.name,self.__t))
self.animal.getSpeedBehavior()
def getFeedingTime(self):
return '%0.f'%(self.__t,)recordTime方法非常清爽,不需要任何if邏輯,只需要調用我們定義的Animal類的基方法getSpeedBehavior即可。
在使用上面所有類時,Manager(jiafeimao)傳入Cat類實例時,recordTime方法調用就被自動指向Cat實例的getSpeedBehavior方法;
Manager(haiying)傳入Bird類實例時,自動指向Bird實例的getSpeedBehavior方法,這就是多態和它的價值,Manager類的方法不必每次都修改,保證了類的封裝性。
if __name__ == "__main__":
jiafeimao = Cat('jiafeimao',2,'gray','CatGenre')
haiying = Bird('haiying',40,'blue','BirdGenre')
Manager(jiafeimao).recordTime()
print('#'*30)
Manager(haiying).recordTime()
總結以上就是面向對象編程專題的基礎部分,大綱如下:
感謝華章出版社對原創文章的大力支持,贈送3本《機器學習:算法視角(原書第2版)》,機器學習領域暢銷教材知名媒體推薦的十大機器學習入門教材之一,作者都是名校教授大咖。
中獎讀者綜合考慮留言質量,參與度和讚賞情況,29日回覆中獎留言。長按下方二維碼查看此書詳情,明天京東有滿減活動。
全文4000多字,100%用心原創作品。歡迎點讚、在看和轉發支持,這樣我會更有動力寫好下一篇OOP編程的進階部分,謝謝。