python面向物件(一)
python全棧開發,初識面向物件
面向過程 VS 面向物件
面向過程的程式設計的核心是過程(流水線式思維),過程即解決問題的步驟,面向過程的設計就好比精心設計好一條流水線,考慮周全什麼時候處理什麼東西。
優點是:極大的降低了寫程式的複雜度,只需要順著要執行的步驟,堆疊程式碼即可。
應用場景:一旦完成基本很少改變的場景,著名的例子有Linux核心,git,以及Apache HTTP Server等。
面向物件的程式設計的核心是物件(上帝式思維),要理解物件為何物,必須把自己當成上帝,上帝眼裡世間存在的萬物皆為物件,不存在的也可以創造出來。面向物件的程式設計好比如來設計西遊記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題需要四個人:唐僧,沙和尚,豬八戒,孫悟空,每個人都有各自的特徵和技能(這就是物件的概念,特徵和技能分別對應物件的屬性和方法),然而這並不好玩,於是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經路上被搞死,又安排了一群神仙保駕護航,這些都是物件。然後取經開始,師徒四人與妖魔鬼怪神仙互相纏鬥著直到最後取得真經。如來根本不會管師徒四人按照什麼流程去取。
面向物件的程式設計的
優點是:解決了程式的擴充套件性。對某一個物件單獨修改,會立刻反映到整個體系中,如對遊戲中一個人物引數的特徵和技能修改都很容易。
。於是我們經常看到一個遊戲人某一引數的修改極有可能導致陰霸的技能出現,一刀砍死3個人,這個遊戲就失去平衡。應用場景:需求經常變化的軟體,一般需求的變化都集中在使用者層,網際網路應用,企業內部軟體,遊戲等都是面向物件的程式設計大顯身手的好地方。
在python 中面向物件的程式設計並不是全部。
面向物件程式設計可以使程式的維護和擴充套件變得更簡單,並且可以大大提高程式開發效率 ,另外,基於面向物件的程式可以使它人更加容易理解你的程式碼邏輯,從而使團隊開發變得更從容。
瞭解一些名詞:類、物件、例項、例項化
類:具有相同特徵的一類事物(人、狗、老虎)
物件/例項:具體的某一個事物(隔壁阿花、樓下旺財)
例項化:類——>物件的過程(這在生活中表現的不明顯,我們在後面再慢慢解釋)
初識類和物件
python中一切皆為物件,型別的本質就是類,用變量表示特徵,用函式表示技能,因而具有相同特徵和技能的一類事物就是‘類’,物件是則是這一類事物中具體的一個。
宣告:
class Person:#定義一個人類 role = 'person'#人的角色屬性都是人 def __init__(self,name): self.name = name# 每一個角色都有自己的暱稱; def walk(self):#人都可以走路,也就是有一個走路方法 print("person is walking...") print(Person.role)#檢視人的role屬性 print(Person.walk)#引用人的走路方法,注意,這裡不是在呼叫
例項化:類名加括號就是例項化,會自動觸發__init__函式的執行,可以用它來為每個例項定製自己的特徵
例項化的過程就是類——>物件的過程
原本我們只有一個Person類,在這個過程中,產生了一個egg物件,有自己具體的名字、攻擊力和生命值。
語法:物件名 = 類名(引數)
egg = Person('egon')#類名()就等於在執行Person.__init__() #執行完__init__()就會返回一個物件。這個物件類似一個字典,存著屬於這個人本身的一些屬性和方法。
檢視屬性&呼叫方法
print(egg.name)#檢視屬性直接 物件名.屬性名 print(egg.walk())#呼叫方法,物件名.方法名()
關於self:self:在例項化時自動將物件/例項本身傳給__init__的第一個引數,你也可以給他起個別的名字,但是不建議。
class Preson: def __init__( self, name, age, role, job ) : self.__name = name self.age = age self.role = role self.job = job def getname(self): return self.__name def setjob(self,newjob): self.job = newjob cosmo=Preson('Cosmo_Ma',18,'student','Learn Python') print(cosmo.getname(),cosmo.age,cosmo.job) print(dir(Preson)) print(Preson.__dict__) print(Preson.__name__) print(Preson.__doc__) print(Preson.__base__) print(Preson.__bases__) print(Preson.__module__) print(Preson.__class__) Output: Cosmo_Ma 18 Learn Python ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'getname', 'setjob'] {'__module__': '__main__', '__init__': <function Preson.__init__ at 0x000001A4F091DA60>, 'getname': <function Preson.getname at 0x000001A4F091D950>, 'setjob': <function Preson.setjob at 0x000001A4F091D9D8>, '__dict__': <attribute '__dict__' of 'Preson' objects>, '__weakref__': <attribute '__weakref__' of 'Preson' objects>, '__doc__': None} Preson None <class 'object'> (<class 'object'>,) __main__ <class 'type'>
方法詳見,非常實用
一:我們定義的類的屬性到底存到哪裡了?有兩種方式檢視 dir(類名):查出的是一個名字列表 類名.__dict__:查出的是一個字典,key為屬性名,value為屬性值 二:特殊的類屬性 類名.__name__# 類的名字(字串) 類名.__doc__# 類的文件字串 類名.__base__# 類的第一個父類(在講繼承時會講) 類名.__bases__# 類所有父類構成的元組(在講繼承時會講) 類名.__dict__# 類的字典屬性 類名.__module__# 類定義所在的模組 類名.__class__# 例項對應的類(僅新式類中)
類名稱空間與物件、例項的名稱空間
建立一個類就會建立一個類的名稱空間,用來儲存類中定義的所有名字,這些名字稱為類的屬性
而類有兩種屬性:靜態屬性和動態屬性
- 靜態屬性就是直接在類中定義的變數
- 動態屬性就是定義在類中的方法
其中類的資料屬性是共享給所有物件的
在上面的code中加入靜態屬性:place='China'
print(id(cosmo.place)) print(id(Preson.place)) 1579379311592 1579379311592
而類的動態屬性是繫結到所有物件的
print(cosmo.setjob) print(Preson.setjob) <bound method Preson.setjob of <__main__.Preson object at 0x0000010A178707F0>> <function Preson.setjob at 0x0000010A1786D9D8>
建立一個物件/例項就會建立一個物件/例項的名稱空間,存放物件/例項的名字,稱為物件/例項的屬性
在obj.name會先從obj自己的名稱空間裡找name,找不到則去類中找,類也找不到就找父類...最後都找不到就丟擲異常
類中的靜態變數 可以被物件和類呼叫
- 對於不可變資料型別來說,類變數最好用類名操作
- 對於可變資料型別來說,物件名的修改是共享的,重新賦值是獨立的
面向物件的組合用法
軟體重用的重要方式除了繼承之外還有另外一種方式,即:組合
組合指的是,在一個類中以另外一個類的物件作為資料屬性,稱為類的組合
class Weapon: def prick(self, obj):# 這是該裝備的主動技能,扎死對方 obj.life_value -= 500# 假設攻擊力是500 class Person:# 定義一個人類 role = 'person'# 人的角色屬性都是人 def __init__(self, name): self.name = name# 每一個角色都有自己的暱稱; self.weapon = Weapon()# 給角色繫結一個武器; egg = Person('egon') egg.weapon.prick() #egg組合了一個武器的物件,可以直接egg.weapon來使用組合類中的所有方法
圓環是由兩個圓組成的,圓環的面積是外面圓的面積減去內部圓的面積。圓環的周長是內部圓的周長加上外部圓的周長。
這個時候,我們就首先實現一個圓形類,計算一個圓的周長和麵積。然後在"環形類"中組合圓形的例項作為自己的屬性來用
from math import pi class Circle: ''' 定義了一個圓形類; 提供計算面積(area)和周長(perimeter)的方法 ''' def __init__(self,radius): self.radius = radius def area(self): return pi * self.radius * self.radius def perimeter(self): return 2 * pi *self.radius circle =Circle(10) #例項化一個圓 area1 = circle.area() #計算圓面積 per1 = circle.perimeter() #計算圓周長 print(area1,per1) #列印圓面積和周長 class Ring: ''' 定義了一個圓環類 提供圓環的面積和周長的方法 ''' def __init__(self,radius_outside,radius_inside): self.outsid_circle = Circle(radius_outside) self.inside_circle = Circle(radius_inside) def area(self): return self.outsid_circle.area() - self.inside_circle.area() def perimeter(self): returnself.outsid_circle.perimeter() + self.inside_circle.perimeter() ring = Ring(10,5) #例項化一個環形 print(ring.perimeter()) #計算環形的周長 print(ring.area()) #計算環形的面積
用組合的方式建立了類與組合的類之間的關係,它是一種‘有’的關係,比如教授有生日,教授教python課程,當類之間有顯著不同,並且較小的類是較大的類所需要的元件時,用組合比較好
面向物件的三大特性
1 繼承
繼承是一種建立新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為基類或超類,新建的類稱為派生類或子類,python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類 pass class ParentClass2: #定義父類 pass class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支援多繼承,用逗號分隔開多個繼承的類 pass
檢視繼承
>>> SubClass1.__bases__ #__base__只檢視從左到右繼承的第一個子類,__bases__則是檢視所有繼承的父類 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果沒有指定基類,python的類會預設繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)
繼承與抽象(先抽象再繼承)
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
1.將奧巴馬和梅西這倆物件比較像的部分抽取成類;
2.將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關注點,降低複雜度)
繼承:是基於抽象的結果,通過程式語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
在開發程式的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B的大部分內容與類A的相同時我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(資料屬性和函式屬性),實現程式碼重用。
提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟體中的一部分設定大部分,大大生了程式設計工作量,這就是常說的軟體重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定製新的資料型別,這樣就是大大縮短了軟體開發週期,對大型軟體開發來說,意義重大.
派生
當然子類也可以新增自己新的屬性或者在自己這裡重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那麼呼叫新增的屬性時,就以自己為準了。
在子類中,新建的重名的函式屬性,在編輯函式內功能的時候,有可能需要重用父類中重名的那個函式功能,應該是用呼叫普通函式的方式,即:類名.func(),此時就與呼叫普通函式無異了,因此即便是self引數也要為其傳值.
在python3中,子類執行父類的方法也可以直接用super方法.supper().函式名()
抽象類與介面類
1 介面類
繼承有兩種用途:
一:繼承基類的方法,並且做出自己的改變或者擴充套件(程式碼重用)
二:宣告某個子類兼容於某基類,定義一個介面類Interface,介面類中定義了一些介面名(就是函式名)且並未實現介面的功能,子類繼承介面類,並且實現介面中的功能
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 ''' 實現了pay的功能,但是名字不一樣 ''' print('微信支付了%s元'%money) def pay(payment,money): ''' 支付函式,總體負責支付 對應支付的物件和要支付的金額 ''' payment.pay(money) p = Wechatpay() pay(p,200)#執行會報錯
介面初成:手動報異常:NotImplementedError來解決開發中遇到的問題
使用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的關鍵字,上面的程式碼只是看起來像介面,其實並沒有起到介面的作用,子類完全可以不用去實現介面 ,如果非要去模仿介面的概念,可以藉助第三方模組:
ofollow,noindex">http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py裡使用zope.interface
文件https://zopeinterface.readthedocs.io/en/latest/
設計模式: https://github.com/faif/python-patterns
介面提取了一群類共同的函式,可以把介面當做一個函式的集合。
然後讓子類去實現介面中的函式。
這麼做的意義在於歸一化,什麼叫歸一化,就是隻要是基於同一個介面實現的類,那麼所有的這些類產生的物件在使用時,從用法上來說都一樣。
歸一化,讓使用者無需關心物件的類是什麼,只需要的知道這些物件都具備某些功能就可以了,這極大地降低了使用者的使用難度。
比如:我們定義一個動物介面,接口裡定義了有跑、吃、呼吸等介面函式,這樣老鼠的類去實現了該介面,松鼠的類也去實現了該介面,由二者分別產生一隻老鼠和一隻松鼠送到你面前,即便是你分別不到底哪隻是什麼鼠你肯定知道他倆都會跑,都會吃,都能呼吸。
再比如:我們有一個汽車介面,裡面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車介面,這樣就好辦了,大家只需要學會了怎麼開汽車,那麼無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函式呼叫)都一樣\
1.2 抽象類
什麼是抽象類
與java一樣,python也有抽象類的概念但是同樣需要藉助模組實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被例項化
為什麼要有抽象類
如果說類是從一堆物件中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括資料屬性和函式屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。
從設計角度去看,如果類是從現實物件抽象而來的,那麼抽象類就是基於類抽象而來的。
從實現角度來看,抽象類與普通類的不同之處在於: 抽象類中有抽象方法,該類不能被例項化,只能被繼承,且子類必須實現抽象方法。這一點與介面有點類似,但其實是不同的 ,即將揭曉答案
在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) Output: 文字資料的讀取方法 硬碟資料的讀取方法 程序資料的讀取方法 file file file
抽象類與介面類
抽象類的本質還是類,指的是一組類的相似性,包括資料屬性(如all_type)和函式屬性(如read、write),而介面只強調函式屬性的相似性。
抽象類是一個介於類和介面直接的一個概念,同時具備類和介面的部分特性,可以用來實現歸一化設計