初學Python——面向對象(二)
一、抽象類、接口類和抽象接口
轉自博客園魏恒https://www.cnblogs.com/weihengblog/p/8528967.html
(一)接口類
什麽是接口類?在繼承中,我們可以聲明某個子類繼承自某基類,這個基類是個接口類,在接口類中定義了接口名(函數名)且並未實現接口的功能,子類繼承接口類,並實現接口中的功能。這又叫做“接口繼承”。
接口繼承實質上是規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的所有對象”——這在程序設計上,叫做歸一化。特點:1.做出良好的抽象類,2.規定兼容接口 3.調用者可以無需關心具體實現細節,可以一視同仁處理實現特定接口的所有對象。
#做出一個良好的抽象
class Payment(object):
#規定了一個兼容接口
def pay(self):
pass
#微信支付
class WeChatPay(Payment):
def pay(self,money):
print(‘微信支付了%s‘%money)
#支付寶支付
class AliPay(Payment):
def pay(self,money):
print(‘支付寶支付了%s‘%money)
#蘋果支付
class ApplePay(Payment):
def pay(self,money):
print(‘蘋果支付了%s‘%money)
def pay(obj,money):
obj.pay(money)
weixin = WeChatPay()
alipay = AliPay()
applepay = ApplePay()
#調用者無需關心具體實現細節,可以一視同仁的處理實現了特定接口的所有對象
pay(weixin,100)
pay(alipay,200)
pay(applepay,300)
(二)抽象類
什麽是抽象類?
與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)
(三)抽象類和接口類
抽象類的本質還是類,指的是一組類的相似性,包括數據屬性和函數屬性,而接口強調函數屬性的相似性。
抽象類是一個介於類和接口直接的一個概念,同時具備類和接口的部分特征,可以用來實現歸一化設計。
二、靜態方法、屬性方法、類方法
1.靜態方法
@staticmethod
靜態方法就是一個普通方法,不能訪問實例變量與類變量,與類唯一的聯系就是需要通過類名來調用這個方法,所以
只是名義上歸類管。私有方法不能作為靜態方法,但受保護類可以作為靜態方法。
class Dog(object): def __init__(self,name): self.name = name @staticmethod # 將eat變成了靜態方法,作用是將此方法斷絕與類的聯系,變普通函數 def eat(self): print(" is eating") d = Dog("ChenRonghua") d.eat() # 這樣運行會出錯,要想不出錯 1.調用時,d.eat(d)。已經斷絕與類的聯系,"self"需要手動傳入 2.eat()方法去掉參數self
2.類方法
@classmethod
變成類方法後,方法內只能訪問類變量,不能訪問實例變量。
class Cat(object): name = "華仔" def __init__(self,name): self.name = name self.__person = None @classmethod # 類方法 def eat(self,food): print(‘{0}吃{1}‘ .format(self.name,food)) c1 = Cat("小貓") c1.eat("魚") # 輸出結果顯示,類方法沒有使用實例變量,使用的是類變量
3.屬性方法
@property
可以把一個方法變成靜態屬性。也就是將原本的方法變成了靜態屬性,不能傳參數,不能修改屬性值,也不能刪除
先來看一下
class Cat(object): name = "華仔" def __init__(self,name): self.name = name self.person = None @property def talk(self): print("{0} is talking with {1}".format(self.name,self.person)) c1 = Cat("小貓") c1.talk #輸出:小貓 is talking with None
這時,想要對self.person進行修改是不可以的,會報錯。
若非要修改,也是可以通過特殊方法來修改的,
#在上述代碼的基礎上加上如下代碼(在原eat方法的下面,類的裏面) @talk.setter # 修改屬性方法的值需要這樣做 def talk(self,person): print("{0} is talking with {1}".format(self.name,person)) self.person = person c1.talk = "Jack" c1.talk # 再次訪問,發現真正修改了它 # 輸出: 小貓 is talk with Jack
同樣,若非要刪除,也可以
@talk.deleter # 刪除屬性方法的值需要這樣做 def talk(self): del self.person print("已刪除talk") del c1.talk c1.talk # 刪除後再訪問運行出錯!
這時相信你會有個疑問,為什麽還需要屬性方法的存在呢?直接定義一個靜態變量不就行了嗎?屬性方法和屬性有什麽不同呢?
屬性方法的本質還是方法(函數),只是變得特殊了,我們知道屬性方法具有以下兩點特征:
1.屬性方法內可以對內部屬性進行訪問
2.屬性方法不能被隨意修改刪除,防止誤改誤刪,保證了安全性。
在實際應用中,有這樣的需求:已經寫好了具有特定功能的方法,作用是返回一個數值。而要經過一系列的動作才能得到這個值,這顯然不是變量可以完成的。用戶調用它時只需要像對待靜態變量一樣就可以得到一個值,中間的過程用戶不需要關心。
三、反射
在實際應用中,我們需要根據用戶輸入來做出相應的工作,比如輸入1調用a方法,輸入2調用b方法,輸入2調用c方法.....,這些我們可以用if else分支語句來判斷並執行,但顯而易見,如果用戶輸入的種類非常多,if else語句的代碼量就非常可觀了,不美觀,也不便於維護。怎麽辦呢?
1.需要用到反射函數hasattr()來判斷對象是否有這個方法
2.如果有這個方法,則可以用getatter()函數來找到對象方法的內存地址
3.加上括號即可調用
class Dog(object): def __init__(self,name): self.name = name def eat(self,name): print("{0} is eating {1}".format(self.name,name)) def bulk(self): print("{0} is yelling".format(self.name)) d = Dog("Alex") chioce = input("請輸入操作:").strip() If hasattr(d,chioce) == True: fun = getattr(d,chioce) a = input("what does the dog want eating ?") fun(a) else: setattr(d,chioce,bulk) # 將類外部的函數加入了類的內部 v = getattr(d,chioce) v(d)
so,什麽叫反射?通過字符串來運行或修改程序運行時的狀態、屬性、方法
一共有四個方法:
1.hasattr(obj,str) obj是對象,str是字符串,判斷對象是否有此方法(屬性),返回True或False
2.getattr(obj,str) obj是對象,str是字符串,返回對象中方法(屬性)的內存地址
3.setattr(x,y,z) (x,y=z)x是對象,y是字符串,z是方法的內容,設置一個新的方法(屬性),或修改方法(屬性)
4.delattr(obj,str) obj是對象,str是字符串,刪除屬性(不能刪除原有的方法?)
chioce = input("請輸入操作:").strip() setattr(d,chioce,18) # 創建屬性並賦值 print(d.talk) setattr(d,"name","Chen") # 修改已存在的name屬性 print(d.name) delattr(d,"name") # 刪除name屬性 #print(d.name) # 報錯 # 輸入:talk 輸出: 18 Chen Chen is yelling
四、動態導入模塊
如何使用字符串導入模塊呢?
之前講過一個方法,使用__import__()內置函數;第二個方法是動態導入模塊
- __import__()內置函數:
‘‘‘導入lib包,調用包下aa模塊的text()函數‘‘‘ b = __import__("lib.aa") b.aa.text() "這是解釋器內部用的,一般不建議用這個,建議用下面的" #output: from lib.aa.text()
- 動態導入
vimport importlib c = importlib.import_module("lib.aa") "導入lib.aa。b 代表 lib.aa " c.text() #output: from lib.aa.text()
五、異常處理
name = ["alex","jack"] data = {} try: a=5 f = open("123","r") #會觸發異常Exception,這個包括了所有個異常 name[3] # 會觸發異常indexError data["name"] # 會觸發異常KeyError except KeyError as e: print("沒有此信息",e) except IndexError as e: print("列表索引超出範圍",e) except Exception as e: print("未知錯誤",e) else: # 一切正常的情況下會執行後面的語句 print("OK") finally: print("不管有沒有錯誤,都會執行的語句")
#輸出:
未知錯誤 [Errno 2] No such file or directory: ‘123‘
不管有沒有錯誤,都會執行的語句
上面的輸出結果是因為,按照執行的順序,首先執行打開文件出現異常,後面的語句沒有被執行的機會。
想要一條語句抓住兩種錯誤,可以這樣寫:
try: name[3] data["name"] except (KeyError,IndexError) as e: print("沒有此信息",e)
能抓住所有錯誤的寫法:
try: name[3] data["name"] except Exception as e: # 一勞永逸,無論出現什麽錯誤都會異常處理 print("出錯了",e)
有一種錯誤沒辦法抓住:縮進錯誤,例如
try: name1 = 10 b = 20 except Exception as e: print("錯誤!") # output: File "F:/Python Files/Learning Log/day7/異常處理.py", line 44 b = 20 ^ IndentationError: unexpected indent
斷言:
class A(object): def __init__(self,name): self.name = name a = A("Alex") try: assert type(a.name) is str # 意為:斷定A.name的類型是字符串 except AssertionError: print("is not str")
斷言的作用是:可以做一些檢查,增強安全性
初學Python——面向對象(二)