第五章 面向對象編程設計與開發——續
5.1 類、實例、屬性、方法詳解
類的語法
上面的代碼其實有問題,屬性名字和年齡都寫死了,想傳名字傳不進去。
class Person(object): def __init__(self, name, age): self.name = name self.age = age p = Person("Alex", 22) print(p.name, p.age)
為什麽有__init__? 為什麽有self? 此時的你一臉蒙逼,相信不畫個圖,你的智商是理解不了的!
畫圖之前, 你先註釋掉這兩句
# p = Person("Alex", 22)# print(p.name, p.age) #加上句 print(Person) #執行結果 <class ‘__main__.Person‘>
其實self,就是實例本身!你實例化時python解釋器會自動把這個實例本身通過self參數傳進去。
了解self的好處,得給Person類加個功能
class Person(object): def __init__(self, name, age): self.name = name self.age = age def talk(self): print("Hello, my name is %s, I‘m %s years old!" % (self.name, self.age)) p = Person("Alex", 22) # print(p.name, p.age) p.talk() #註意這裏調用並未傳遞參數
執行輸出
Hello, my name is Alex, I‘m 22 years old!
為何p.talk()未傳參數不報錯,且為何talk方法定義要跟一個self參數?
我們上面講到self其實是實例本身, 那p.talk() 其實相當於p.talk(p),你不需要自己把p實例傳進去,解釋器幫你幹了,那為何要在talk定義時加self參數呢?
是因為,你的talk方法裏有調用到實例的屬性呀,這些屬性又都是綁定在實例上的,你想調用實例屬性,就必須要把實例傳進去。
構造方法
__init__(...)
被稱為 構造方法或初始化方法,在例實例化過程中自動執行,目的是初始化實例的一些屬性。每個實例通過__init__初始化的屬性都是獨有的
剛才定義的這個類體現了面向對象的第一個基本特性,封裝,其實就是使用構造方法將內容封裝到某個具體對象中,然後通過對象直接或者self間接獲取被封裝的內容
了解了基本定義,下面詳解下類的方法和屬性
類的方法
構造方法
剛才上面已經說了,主要作用是實例化時給實例一些初始化參數,或執行一些其它的初始化工作,總之,因為這個__init__只要一實例化,就會自動執行,so不管你在這個方法裏寫什麽,它都會統統在實例化時執行一遍
普通方法
定義類的一些正常功能,比如人這個類, 可以說話、走路、吃飯等,每個方法其實想當於一個功能或動作
析構方法(解構方法)
實例在內存中被刪除時,會自動執行這個方法,如你在內存裏生成了一個人的實例,現在他被打死了,那這個人除了自己的實例要被刪除外,可能它在實例外產生的一些痕跡也要清除掉,清除的動作就可以寫在這個方法裏
class Person(object): def __init__(self, name, age): self.name = name self.age = age def talk(self): print("Hello, my name is %s, I‘m %s years old!" % (self.name, self.age)) def __del__(self): print("running del method, this person must be died.") p = Person("Alex", 22) p.talk() del p print(‘--end program--‘)
面向過程的程序設計
概念:
核心是“過程”二字,“過程”指的是解決問題的步驟,即先幹什麽再幹什麽......,基於面向過程設計程序就好比在設計一條流水線,是一種機械式的思維方式。若程序一開始是要著手解決一個大的問題,面向過程的基本設計思路就是把這個大的問題分解成很多個小問題或子過程,這些子過程在執行的過程中繼續分解,直到小問題足夠簡單到可以在一個小步驟範圍內解決。
優點是:
復雜的問題流程化,進而簡單化(一個復雜的問題,分成一個個小的步驟去實現,實現小的步驟將會非常簡單)
舉個典型的面向過程的例子, 寫一個數據遠程備份程序, 分三步,本地數據打包,上傳至雲服務器,測試備份文件可用性。
import os def data_backup(folder): print("找到備份目錄: %s" %folder) print(‘正在備份......‘) zip_file=‘/tmp/backup20181103.zip‘ print(‘備份成功,備份文件為: %s‘ %zip_file) return zip_file def cloud_upload(file): print("\nconnecting cloud storage center...") print("cloud storage connected.") print("upload file...%s...to cloud..." %file) link=‘http://www.xxx.com/bak/%s‘ %os.path.basename(file) print(‘close connection.....‘) return link def data_backup_test(link): print("\n下載文件: %s , 驗證文件是否無損" %link) def main(): #步驟一:本地數據打包 zip_file = data_backup("c:\\users\\alex\歐美100G高清無碼") #步驟二:上傳至雲服務器 link=cloud_upload(zip_file) #步驟三:測試備份文件的可用性 data_backup_test(link) if __name__ == ‘__main__‘: main()
缺點是:
一套流水線或者流程就是用來解決一個問題,比如生產汽水的流水線無法生產汽車,即便是能,也得是大改,改一個組件,與其相關的組件都需要修改,牽一發而動全身,擴展性極差。
比如我們修改了步驟二的函數cloud_upload的邏輯,那麽依賴於步驟二結果才能正常執行的步驟三的函數data_backup_test相關的邏輯也需要修改,這就造成了連鎖反應,而這一弊端會隨著程序的增大而變得越發的糟糕,我們程序的維護難度將會越來越大。
import os def data_backup(folder): print("找到備份目錄: %s" %folder) print(‘正在備份......‘) zip_file=‘/tmp/backup20181103.zip‘ print(‘備份成功,備份文件為: %s‘ %zip_file) return zip_file def cloud_upload(file): #加上異常處理,在出現異常的情況下,沒有link返回 try: print("\nconnecting cloud storage center...") print("cloud storage connected.") print("upload file...%s...to cloud..." % file) link = ‘http://www.xxx.com/bak/%s‘ % os.path.basename(file) return link except Exception: print(‘upload error‘) finally: print(‘close connection.....‘) def data_backup_test(link): #加上對參數link的判斷 if link: print("\n下載文件: %s , 驗證文件是否無損" %link) else: print(‘\n鏈接不存在‘) def main(): #步驟一:本地數據打包 zip_file = data_backup("c:\\users\\alex\歐美100G高清無碼") #步驟二:上傳至雲服務器 link=cloud_upload(zip_file) #步驟三:測試備份文件的可用性 data_backup_test(link) if __name__ == ‘__main__‘: main()
應用場景:
面向過程的程序設計思想一般用於那些功能一旦實現之後就很少需要改變的場景, 如果你只是寫一些簡單的腳本,去做一些一次性任務,用面向過程的方式是極好的,著名的例子有Linux內核,git,以及Apache HTTP Server等。但如果你要處理的任務是復雜的,且需要不斷叠代和維護 的, 那還是用面向對象最方便了。
面向對象的程序設計
概念:
核心是“對象”二字,要理解對象為何物,必須把自己當成上帝,在上帝眼裏,世間存在的萬物皆為對象,不存在的也可以創造出來。程序員基於面向對象設計程序就好比如來設計西遊記,如來要解決的問題是把經書傳給東土大唐,如來並沒有考慮問題的解決流程,而是設計出了負責取經的師傅四人:唐僧,沙和尚,豬八戒,孫悟空,負責騷擾的一群妖魔鬼怪,以及負責保駕護航的一眾神仙,這些全都是對象,然後取經開始,就是師徒四人與妖魔鬼怪神仙交互著直到完成取經任務。所以說基於面向對象設計程序就好比在創造一個世界,世界是由一個個對象組成,而你就是這個世界的上帝。
我們從西遊記中的任何一個人物對象都不難總結出:對象是特征與技能的結合體。比如孫悟空的特征是:毛臉雷公嘴,技能是:七十二變、火眼金睛等。
與面向過程機械式的思維方式形成鮮明對比,面向對象更加註重對現實世界而非流程的模擬,是一種“上帝式”的思維方式。
優點是:
解決了面向過程可擴展性低的問題,這一點我們將在5.2小節中為大家驗證,需要強調的是,對於一個軟件質量來說,面向對象的程序設計並不代表全部,面向對象的程序設計只是用來解決擴展性問題。
缺點是:
編程的復雜度遠高於面向過程,不了解面向對象而立即上手並基於它設計程序,極容易出現過度設計的問題,而且在一些擴展性要求低的場景使用面向對象會徒增編程難度,比如管理linux系統的shell腳本程序就不適合用面向對象去設計,面向過程反而更加適合。
應用場景:
當然是應用於需求經常變化的軟件中,一般需求的變化都集中在用戶層,互聯網應用,企業內部軟件,遊戲等都是面向對象的程序設計大顯身手的好地方。
5.2 類與對象
類與對象的概念
類即類別、種類,是面向對象設計最重要的概念,從一小節我們得知對象是特征與技能的結合體,而類則是一系列對象相似的特征與技能的結合體。
那麽問題來了,先有的一個個具體存在的對象(比如一個具體存在的人),還是先有的人類這個概念,這個問題需要分兩種情況去看
- 在現實世界中:肯定是先有對象,再有類
-
世界上肯定是先出現各種各樣的實際存在的物體,然後隨著人類文明的發展,人類站在不同的角度總結出了不同的種類,比如 人類、動物類、植物類等概念。也就說,對象是具體的存在,而類僅僅只是一個概念,並不真實存在,比如你無法告訴我人類 具體指的是哪一個人。
在程序中:務必保證先定義類,後產生對象
-
這與函數的使用是類似的:先定義函數,後調用函數,類也是一樣的:在程序中需要先定義類,後調用類。不一樣的是:調用 函數會執行函數體代碼返回的是函數體執行的結果,而調用類會產生對象,返回的是對象
定義類
按照上述步驟,我們來定義一個類(我們站在老男孩學校的角度去看,在座的各位都是學生)
- 在現實世界中,先有對象,再有類
-
對象1:李坦克 特征: 學校=oldboy 姓名=李坦克 性別=男 年齡=18 技能: 學習 吃飯 睡覺 對象2:王大炮 特征: 學校=oldboy 姓名=王大炮 性別=女 年齡=38 技能: 學習 吃飯 睡覺 對象3:牛榴彈 特征: 學校=oldboy 姓名=牛榴彈 性別=男 年齡=78 技能: 學習 吃飯 睡覺 現實中的老男孩學生類 相似的特征: 學校=oldboy 相似的技能: 學習 吃飯 睡覺
在程序中,務必保證:先定義(類),後使用類(用來產生對象)
-
#在Python中程序中的類用class關鍵字定義,而在程序中特征用變量標識,技能用函數標識,因而類中最常見的無非是:變量和函數的定義 class OldboyStudent: school=‘oldboy‘ def learn(self): print(‘is learning‘) def eat(self): print(‘is eating‘) def sleep(self): print(‘is sleeping‘)
註意:
- 類中可以有任意python代碼,這些代碼在類定義階段便會執行,因而會產生新的名稱空間,用來存放類的變量名與函數名,可以通過OldboyStudent.__dict__查看
- 類中定義的名字,都是類的屬性,點是訪問屬性的語法。
- 對於經典類來說我們可以通過該字典操作類名稱空間的名字,但新式類有限制(新式類與經典類的區別我們將在後續章節介紹)
-
類的使用
- 引用類的屬性
-
OldboyStudent.school #查 OldboyStudent.school=‘Oldboy‘ #改 OldboyStudent.x=1 #增 del OldboyStudent.x #刪
調用類,或稱為實例化,得到程序中的對象
-
s1=OldboyStudent() s2=OldboyStudent() s3=OldboyStudent() #如此,s1、s2、s3都一樣了,而這三者除了相似的屬性之外還各種不同的屬性,這就用到了__init__
__init__方法
-
#註意:該方法是在對象產生之後才會執行,只用來為對象進行初始化操作,可以有任意代碼,但一定不能有返回值 class OldboyStudent: ...... def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex ...... s1=OldboyStudent(‘李坦克‘,‘男‘,18) #先調用類產生空對象s1,然後調用OldboyStudent.__init__(s1,‘李坦克‘,‘男‘,18) s2=OldboyStudent(‘王大炮‘,‘女‘,38) s3=OldboyStudent(‘牛榴彈‘,‘男‘,78)
對象的使用
#執行__init__,s1.name=‘牛榴彈‘,很明顯也會產生對象的名稱空間可以用s2.__dict__查看,查看結果為 {‘name‘: ‘王大炮‘, ‘age‘: ‘女‘, ‘sex‘: 38} s2.name #查,等同於s2.__dict__[‘name‘] s2.name=‘王三炮‘ #改,等同於s2.__dict__[‘name‘]=‘王三炮‘ s2.course=‘python‘ #增,等同於s2.__dict__[‘course‘]=‘python‘ del s2.course #刪,等同於s2.__dict__.pop(‘course‘)
補充說明
- 站的角度不同,定義出的類是截然不同的;
- 現實中的類並不完全等於程序中的類,比如現實中的公司類,在程序中有時需要拆分成部門類,業務類等;
- 有時為了編程需求,程序中也可能會定義現實中不存在的類,比如策略類,現實中並不存在,但是在程序中卻是一個很常見的類。
5.3 屬性查找與綁定方法
屬性查找
類有兩種屬性:數據屬性和函數屬性
1、類的數據屬性是所有對象共享的
#類的數據屬性是所有對象共享的,id都一樣 print(id(OldboyStudent.school)) print(id(s1.school)) #4377347328 print(id(s2.school)) #4377347328 print(id(s3.school)) #4377347328
2、類的函數數據是綁定給對象用的,稱為綁定到對象的方法
#類的函數屬性是綁定給對象使用的,obj.method稱為綁定方法,內存地址都不一樣 print(OldboyStudent.learn) #<function OldboyStudent.learn at 0x1021329d8> print(s1.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x1021466d8>> print(s2.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146710>> print(s3.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146748>> #ps:id是python的實現機制,並不能真實反映內存地址,如果有內存地址,還是以內存地址為準
在obj.name會先從obj自己的名稱空間裏找name,找不到則去類中找,類也找不到就找父類...最後都找不到就拋出異常
綁定方法
定義類並實例化出三個對象
class OldboyStudent: school=‘oldboy‘ def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def learn(self): print(‘%s is learning‘ %self.name) #新增self.name def eat(self): print(‘%s is eating‘ %self.name) def sleep(self): print(‘%s is sleeping‘ %self.name) s1=OldboyStudent(‘李坦克‘,‘男‘,18) s2=OldboyStudent(‘王大炮‘,‘女‘,38) s3=OldboyStudent(‘牛榴彈‘,‘男‘,78) 類中定義的函數(沒有被任何裝飾器裝飾的)是類的函數屬性,類可以使用,但必須遵循函數的參數規則,有幾個參數需要傳幾個參數 OldboyStudent.learn(s1) #李坦克 is learning OldboyStudent.learn(s2) #王大炮 is learning OldboyStudent.learn(s3) #牛榴彈 is learning
類中定義的函數(沒有被任何裝飾器裝飾的)是類的函數屬性,類可以使用,但必須遵循函數的參數規則,有幾個參數需要傳幾個參數
OldboyStudent.learn(s1) #李坦克 is learning OldboyStudent.learn(s2) #王大炮 is learning OldboyStudent.learn(s3) #牛榴彈 is learning
類中定義的函數(沒有被任何裝飾器裝飾的),其實主要是給對象使用的,而且是綁定到對象的,雖然所有對象指向的都是相同的功能,但是綁定到不同的對象就是不同的綁定方法
強調:綁定到對象的方法的特殊之處在於,綁定給誰就由誰來調用,誰來調用,就會將‘誰’本身當做第一個參數傳給方法,即自動傳值(方法__init__也是一樣的道理)
s1.learn() #等同於OldboyStudent.learn(s1) s2.learn() #等同於OldboyStudent.learn(s2) s3.learn() #等同於OldboyStudent.learn(s3)
註意:綁定到對象的方法的這種自動傳值的特征,決定了在類中定義的函數都要默認寫一個參數self,self可以是任意名字,但是約定俗成地寫出self。
類即類型
python中一切皆為對象,且python3中類與類型是一個概念,類型就是類
#類型dict就是類dict >>> list <class ‘list‘> #實例化的到3個對象l1,l2,l3 >>> l1=list() >>> l2=list() >>> l3=list() #三個對象都有綁定方法append,是相同的功能,但內存地址不同 >>> l1.append <built-in method append of list object at 0x10b482b48> >>> l2.append <built-in method append of list object at 0x10b482b88> >>> l3.append <built-in method append of list object at 0x10b482bc8> #操作綁定方法l1.append(3),就是在往l1添加3,絕對不會將3添加到l2或l3 >>> l1.append(3) >>> l1 [3] >>> l2 [] >>> l3 [] #調用類list.append(l3,111)等同於l3.append(111) >>> list.append(l3,111) #l3.append(111) >>> l3 [111]
第五章 面向對象編程設計與開發——續