【Python基础】Python 面向对象编程(上篇)
我的施工計劃圖
已完成專題包括:
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?printedname={0.name}speed={0.speed}'''.format(self)使用0.數據名稱的格式,這是類專有的打印格式。
現在再打印:
cat?=?Animal('加菲貓',8) print(cat)打印信息如下:
Animal(加菲貓,8)?is?printedname=加菲貓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?printedname={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.pyimport?timeclass?Manager():def?__init__(self,animal):self.animal?=?animaldef?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?printedname={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?printedname=加菲貓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.pyimport?time from?animal?import?(Animal,Cat,Bird)class?Manager():def?__init__(self,animal):self.animal?=?animaldef?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?printedname={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._speedclass?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?=?animaldef?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()??總結
以上就是面向對象編程專題的基礎部分,大綱如下:
Python 面向對象編程
基礎專題
1 類定義
2 實例
3 打印實例
4 屬性
5 private,protected,public
6 繼承
7 多態
總結
總結
以上是生活随笔為你收集整理的【Python基础】Python 面向对象编程(上篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Python基础】Python 面向对
- 下一篇: 【机器学习应用】将在2020年实现盈利的