前言
師門中每逢成人禮就要下山歷練一番,小掌門今年成人禮,下山也有一段時日。可恰逢年底,今年百姓收成都不是很好,各大店鋪也很少招人,再加上學藝不精,小掌門無事可做就只能餓肚子了。后來小掌門餓的實在不行,只好沿街乞討。其時慘狀如圖:

初識面向對象
就這樣每天風吹日曬地乞討,后來小掌門發現自己每天所獲得的饅頭都比其他同行的小乞丐少好多,他發現其他小乞丐都有自己的廣告語,然后小掌門稍一改進,便成了如下模樣

每次要飯前小掌門都在地上寫上幾個字
def beg():#定義一個要飯函數 print('求各位給個饅頭吃') if __name__ == '__main__': beg()
就這樣,小掌門的饅頭比以往多了起來,時不時也分點饅頭給其他乞丐。
一天,過來一個老叫花對小掌門說:“孩子啊,你這樣每天要飯前還得找個樹枝在地上寫一遍字,其實你可以自己做個招牌啊,就跟那人一樣”,老叫花指了指對面走過來的另一個乞討的年輕人。

小掌門一拍腦門,是啊,我做一個招牌就不用每次都寫一遍了,小掌門找了一塊破木板,在上面好好提了幾筆
class Begger: def __init__(self,name,age,money): self.name=name self.age=age self.money=money def beg(self): # 定義一個要飯函數 print('求各位給個饅頭吃') if __name__ == '__main__': zm=Begger('古墓派掌門','18',-10) zm.beg()

老叫花見小掌門挺聰明,笑着說:“看來你已經理解面向對象了”。
“前輩,什么叫面向對象啊”,小掌門瞪着水汪汪的大眼睛問道。
“說起面向對象,還得從面向過程說起”
面向對象vs面向過程
面向過程的程序設計的核心是過程(流水線式思維),過程即解決問題的步驟,面向過程的設計就好比精心設計好一條流水線,考慮周全什么時候處理什么東西。
優點是:極大的降低了寫程序的復雜度,只需要順着要執行的步驟,堆疊代碼即可。
缺點是:一套流水線或者流程就是用來解決一個問題,代碼牽一發而動全身。
應用場景:一旦完成基本很少改變的場景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向對象的程序設計的核心是對象(上帝式思維),要理解對象為何物,必須把自己當成上帝,上帝眼里世間存在的萬物皆為對象,不存在的也可以創造出來。面向對象的程序設計好比如來設計西游記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題需要四個人:唐僧,沙和尚,豬八戒,孫悟空,每個人都有各自的特征和技能(這就是對象的概念,特征和技能分別對應對象的屬性和方法),然而這並不好玩,於是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經路上被搞死,又安排了一群神仙保駕護航,這些都是對象。然后取經開始,師徒四人與妖魔鬼怪神仙互相纏斗着直到最后取得真經。如來根本不會管師徒四人按照什么流程去取。
面向對象的程序設計的
優點是:解決了程序的擴展性。對某一個對象單獨修改,會立刻反映到整個體系中,如對游戲中一個人物參數的特征和技能修改都很容易。
缺點:可控性差,無法向面向過程的程序設計流水線式的可以很精准的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題,即便是上帝也無法預測最終結果。於是我們經常看到一個游戲人某一參數的修改極有可能導致陰霸的技能出現,一刀砍死3個人,這個游戲就失去平衡。
應用場景:需求經常變化的軟件,一般需求的變化都集中在用戶層,互聯網應用,企業內部軟件,游戲等都是面向對象的程序設計大顯身手的好地方。
在python 中面向對象的程序設計並不是全部。
面向對象編程可以使程序的維護和擴展變得更簡單,並且可以大大提高程序開發效率 ,另外,基於面向對象的程序可以使它人更加容易理解你的代碼邏輯,從而使團隊開發變得更從容。
了解一些名詞:類、對象、實例、實例化
類:具有相同特征的一類事物(人、狗、老虎)
對象/實例:具體的某一個事物(隔壁阿花、樓下旺財)
實例化:類——>對象的過程(這在生活中表現的不明顯,我們在后面再慢慢解釋)
類的聲明
1 #class 類名: 2 # '類的文檔字符串' 3 # 類體 4 #如: 5 class Begger: 6 def __init__(self,name,age,money): 7 self.name=name 8 self.age=age 9 self.money=money 10 def beg(self): # 定義一個要飯函數 11 print('求各位給個饅頭吃')
在對象實例化時,會自動調用__init__函數,完成對象初識化,相當於c++,java中的構造函數
上述代碼定義了一個Begger類,在實例化對象時,會自動初始化name,age,money實例屬性
上面提到兩個名詞‘實例化對象’,‘實例屬性’,下面我一一道來
實例化對象
類名加括號就是實例化,會自動觸發__init__函數的運行,可以用它來為每個實例定制自己的特征
如
zm=Begger('古墓派掌門','18',-10)
實例屬性與類屬性及私有屬性
1:實例屬性: 最好在__init__(self,...)中初始化 內部調用時都需要加上self. 外部調用時用instancename.propertyname 2:類屬性: 在__init__()外初始化 在內部用classname.類屬性名調用 外部既可以用classname.類屬性名又可以用instancename.類屬性名來調用 3:私有屬性: 1):單下划線_開頭:只是告訴別人這是私有屬性,外部依然可以訪問更改 2):雙下划線__開頭:外部不可通過instancename.propertyname來訪問或者更改 實際將其轉化為了_classname__propertyname
如我們在乞丐類(Begger)中加一個count類屬性,用來計算小乞丐的個數,每個小乞丐加一個婚姻狀況(marriage)私有屬性:
(內心戲:沒錢還想結婚,做夢吧)
1 class Begger: 2 count=0 #這是一個類屬性 3 def __init__(self,name,age,money,single=True): 4 self.name=name #實例屬性 5 self.age=age 6 self.money=money 7 self._single=True #私有屬性 8 Begger.count+=1 9 def beg(self): # 定義一個要飯函數 10 print('求各位給個饅頭吃') 11 12 13 14 if __name__ == '__main__': 15 zm=Begger('古墓派掌門','18',-10) 16 zm.beg() 17 print("小乞丐個數:%s"%Begger.count)
類方法與靜態方法
1:普通類方法: def fun_name(self,...): pass 外部用實例調用 2:靜態方法:@staticmethod 不能訪問實例屬性!!! 參數不能傳入self!!! 與類相關但是不依賴類與實例的方法!! 3:類方法:@classmethod 不能訪問實例屬性!!! 參數必須傳入cls!!! 必須傳入cls參數(即代表了此類對象-----區別------self代表實例對象),並且用此來調用類屬性:cls.類屬性名 *靜態方法與類方法都可以通過類或者實例來調用。其兩個的特點都是不能夠調用實例屬性
示例如下:
1 class Begger: 2 count=0 #這是一個類屬性 3 def __init__(self,name,age,money,single=True): 4 self.name=name #實例屬性 5 self.age=age 6 self.money=money 7 self._single=True #私有屬性 8 Begger.count+=1 9 def beg(self): # 定義一個要飯函數,普通類方法 10 print('求各位給個饅頭吃') 11 12 @staticmethod 13 def beg_skill():#定義一個乞討技巧函數,靜態方法 14 print("乞討技巧") 15 16 17 @classmethod 18 def beg_counter(cls): 19 print("小乞丐個數:%s" % Begger.count) 20 21 22 if __name__ == '__main__': 23 zm=Begger('古墓派掌門','18',-10) 24 zm.beg() 25 zm.beg_skill() 26 Begger.beg_counter()
tip:類屬性與類方法是類固有的方法與屬性,不會因為實例不同而改變,寫他們的目的是減少多實例時所創造出來的內存空間,加快運行速度
封裝、繼承、多態
"封裝、繼承、多態並稱為類的三大特性,也是面向對象的一個主要特點"老叫花說道“下面我就一一跟你講”
封裝
封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏
“封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足為慮。你用的那個乞討類中的乞討技巧就是很好的體現,別人只看到你表現出的乞討的形式,並不知道你在背后所做的何種工作” 老叫花意味深長地說。
property特性
property是一種特殊的屬性,訪問它時會執行一段功能(函數)然后返回值
將一個類的函數定義成特性以后,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然后計算出來的,這種特性的使用方式遵循了統一訪問的原則 ps:面向對象的封裝有三種方式: 【public】 這種其實就是不封裝,是對外公開的 【protected】 這種封裝方式對外不公開,但對朋友(friend)或者子類公開 【private】 這種封裝對誰都不公開
python並沒有在語法上把它們三個內建到自己的class機制中,在C++或java里一般會將所有的所有的數據都設置為私有的,然后提供set和get方法(接口)去設置和獲取,在python中通過property方法可以實現
用法如下
class Begger: count=0 #這是一個類屬性 def __init__(self,name,age,money,single=True): self._name=name self._age=age self._money=money self._single=True #私有屬性 Begger.count+=1 def beg(self): # 定義一個要飯函數,普通類方法 print('求各位給個饅頭吃') @property def age(self): return self._age @age.setter def age(self,value): self._age=value @age.deleter def age(self): del self._age @staticmethod def beg_skill():#定義一個乞討技巧函數,靜態方法 print("乞討技巧") @classmethod def beg_counter(cls): print("小乞丐個數:%s" % Begger.count) if __name__ == '__main__': zm=Begger('古墓派掌門','18',-10) print(zm.age)#獲取年齡屬性 zm.age=20#設置年齡屬性 print(zm.age) del zm.age#刪除年齡屬性
繼承
繼承,以通用的類為基礎建立專門的類對象
繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為基類或超類,新建的類稱為派生類或子類
“說起繼承,老叫花子我可就有話說了,什么叫繼承,就是兒子繼承老子的東西。‘龍生龍,鳳生鳳,老鼠的兒子會打洞’。雖然說古人有雲,‘王侯將相寧有種乎’ ,但有錢真的可以為所欲為,地主的兒子生下來就會繼承老子的東西成為富二代,跟咱們這些臭要飯的就不一樣了。我來給你舉幾個例子 ”
1 class Person: #定義一個人類 2 def __init__(self,name,age,money,single=True): 3 self.name=name #實例屬性 4 self.age=age 5 self.money=money 6 self._single=True #私有屬性 7 class Begger(Person):#定義一個乞丐類,繼承Person 8 # count=0 #這是一個類屬性 9 10 def beg(self): # 定義一個要飯函數,普通類方法 11 print('求各位給個饅頭吃') 12 13 @staticmethod 14 def beg_skill():#定義一個乞討技巧函數,靜態方法 15 print("乞討技巧") 16 # @classmethod 17 # def beg_counter(cls): 18 # print("小乞丐個數:%s" % Begger.count) 19 20 21 if __name__ == '__main__': 22 zm=Begger('古墓派掌門','18',-10) 23 zm.beg() 24 zm.beg_skill() 25 # Begger.beg_counter()
tip:用已經有的類建立一個新的類,這樣就重用了已經有的軟件中的一部分設置大部分,大大生了編程工作量,這就是常說的軟件重用,不僅可以重用自己的類,也可以繼承別人的,比如標准庫,來定制新的數據類型,這樣就是大大縮短了軟件開發周期,對大型軟件開發來說,意義重大.
在python3中,子類新建與父類重名的方法叫重寫,子類執行父類的方法也可以直接用super方法(這些與c++,java類似),如我們重寫一下子類init方法
1 class Person: 2 def __init__(self,name,age,money,single=True): 3 self.name=name #實例屬性 4 self.age=age 5 self.money=money 6 self._single=True #私有屬性 7 class Begger(Person): 8 count=0 #這是一個類屬性 9 10 #重寫父類__init__ 11 def __init__(self,name,age,money,single=True): 12 super(Begger,self).__init__(name,age,money,single) 13 #或者 14 # Person.__init__(self,name,age,money,single) 15 Begger.count+=1 16 17 18 19 def beg(self): # 定義一個要飯函數,普通類方法 20 print('求各位給個饅頭吃') 21 @property 22 def age(self): 23 return self._age 24 25 @age.setter 26 def age(self,value): 27 self._age=value 28 29 @age.deleter 30 def age(self): 31 del self._age 32 @staticmethod 33 def beg_skill():#定義一個乞討技巧函數,靜態方法 34 print("乞討技巧") 35 @classmethod 36 def beg_counter(cls): 37 print("小乞丐個數:%s" % Begger.count) 38 39 40 if __name__ == '__main__': 41 zm=Begger('古墓派掌門','18',-10) 42 zm.beg_counter() 43 zm.beg_skill()
抽象類與接口類
繼承有兩種用途:
一:繼承基類的方法,並且做出自己的改變或者擴展(代碼重用)
二:聲明某個子類兼容於某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,並且實現接口中的功能
class Alipay: ''' 支付寶支付 ''' def pay(self,money): print('支付寶支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) def pay(payment,money): ''' 支付函數,總體負責支付 對應支付的對象和要支付的金額 ''' payment.pay(money) p = Alipay() pay(p,200)
開發中容易出現的問題
class Alipay: ''' 支付寶支付 ''' def pay(self,money): print('支付寶支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) class Wechatpay: def fuqian(self,money): ''' 實現了pay的功能,但是名字不一樣 ''' print('微信支付了%s元'%money) def pay(payment,money): ''' 支付函數,總體負責支付 對應支付的對象和要支付的金額 ''' payment.pay(money) p = Wechatpay() pay(p,200) #執行會報錯
接口初成:手動報異常:NotImplementedError來解決開發中遇到的問題
class Payment: def pay(self): raise NotImplementedError class Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money) p = Wechatpay() #這里不報錯 pay(p,200) #這里報錯了
借用abc模塊來實現接口
from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): @abstractmethod def pay(self,money): pass class Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money) p = Wechatpay() #不調就報錯了
實踐中,繼承的第一種含義意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。
繼承的第二種含義非常重要。它又叫“接口繼承”。
接口繼承實質上是要求“做出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的所有對象”——這在程序設計上,叫做歸一化。
歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合——就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網絡還是屏幕(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然后做出針對性的設計:細致到什么程度,視需求而定)。
在python中根本就沒有一個叫做interface的關鍵字,上面的代碼只是看起來像接口,其實並沒有起到接口的作用,子類完全可以不用去實現接口 ,如果非要去模仿接口的概念,可以借助第三方模塊
抽象類
與java一樣,python也有抽象類的概念但是同樣需要借助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化
#一切皆文件 import abc #利用abc模塊實現抽象類 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定義抽象方法,無需實現功能 def read(self): '子類必須定義讀功能' pass @abc.abstractmethod #定義抽象方法,無需實現功能 def write(self): '子類必須定義寫功能' pass # class Txt(All_file): # pass # # t1=Txt() #報錯,子類沒有定義抽象方法 class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('文本數據的讀取方法') def write(self): print('文本數據的讀取方法') class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('硬盤數據的讀取方法') def write(self): print('硬盤數據的讀取方法') class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('進程數據的讀取方法') def write(self): print('進程數據的讀取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #這樣大家都是被歸一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
多態
多態指的是一類事物有多種形態
如:
class Person: def __init__(self,name,age,money,single=True): self.name=name #實例屬性 self.age=age self.money=money self._single=True #私有屬性 class landlord(Person): def __init__(self): print("這是一個地主") class prince(Person): def __init__(self): print("這是一個王子") class Begger(Person):#乞丐類 count=0 #這是一個類屬性 #重寫父類__init__ def __init__(self,name,age,money,single=True): super(Begger,self).__init__(name,age,money,single) #或者 # Person.__init__(self,name,age,money,single) Begger.count+=1 def beg(self): # 定義一個要飯函數,普通類方法 print('求各位給個饅頭吃') @property def age(self): return self._age @age.setter def age(self,value): self._age=value @age.deleter def age(self): del self._age @staticmethod def beg_skill():#定義一個乞討技巧函數,靜態方法 print("乞討技巧") @classmethod def beg_counter(cls): print("小乞丐個數:%s" % Begger.count) if __name__ == '__main__': zm=Begger('古墓派掌門','18',-10) zm.beg_counter() zm.beg_skill() wz=prince() dz=landlord()
鴨子類型
Python崇尚鴨子類型,即‘如果看起來像、叫聲像而且走起路來像鴨子,那么它就是鴨子’
python程序員通常根據這種行為來編寫程序。例如,如果想編寫現有對象的自定義版本,可以繼承該對象
也可以創建一個外觀和行為像,但與它無任何關系的全新對象,后者通常用於保存程序組件的松耦合度。
結語
“以上就是面向對象的解釋了,小家伙可還滿意”老叫花拍了拍酒葫蘆道
“前輩你懂這么多為什么還是一個要飯的啊”
“其實我以前跟他一樣”說着,老叫花指了指對面的那個年輕人
“前輩,你再給我講點其他的唄”
“好好,回頭老叫花我慢慢給你講點其他的,不過你得給我弄點酒。。。。。。。。。。。。”
