第三週:Python抽象類
Python抽象類
在Python中抽象類只能被繼承,不能被例項化。
並且,抽象類中只有抽象方法和普通方法。
定義抽象類和抽象方法
Python的抽象類的定義需要abc
模組。(= =...)
# 匯入抽象類需要用到的庫 from abc import ABCMeta, abstractmethod class Person(metaclass=ABCMeta): """使用元類(模板類)""" pname = "這是Person抽象類" # 可以定義屬性 # 定義抽象方法,抽象方法不需要方法體 # 需要在方法前加@abstractmethod裝飾器 @abstractmethod def run(self): pass # 不需要寫方法體 @classmethod # 可以定義類方法 def eat(cls): print("在吃東西啊") @staticmethod def drink(): # 可以定義靜態方法 print("在喝東西啊") def sleep(self): # 可以定義普通方法 print("在睡覺啊")
抽象方法不需要寫方法體,但並不是不能寫。可以寫,但是沒有用。
抽象類中可以定義屬性、類方法、靜態方法
使用抽象類和抽象方法
class Student(Person): sno = 123 # 學號 # 不會提示要要重寫方法 def run(self): print("跑") # 不重寫抽象方法就無法例項化物件,並且拋異常 s = Student() s.run() # 跑 s.eat() # 在吃東西啊 s.sleep() # 在睡覺啊 print(s.pname) # 這是Person抽象類 s.drink() # 在喝東西啊 # p = Person() 執行時拋異常
繼承了抽象類,就必須重寫抽象類中的抽象方法,否則無法例項化物件,並拋異常。
應該儘量避免多繼承
如果出現多繼承,那麼對於同名抽象方法,哪個繼承類靠前就是實現的哪個類的抽象方法。
這對於抽象方法來說沒什麼意義。
但是!
由於抽象類不止能寫抽象方法,還可以寫屬性、類方法、靜態方法、普通方法,這些方法如果同名,就是按繼承順序來繼承和重寫的了。
class A(metaclass=ABCMeta): num = 10 def __init__(self): print("這是A的構造方法") @abstractmethod def show(self): pass @classmethod # 可以定義類方法 def eat(cls): print("吃 1") @staticmethod def drink(): # 可以定義靜態方法 print("喝 1") def sleep(self): # 可以定義普通方法 print("睡 1") class B(metaclass=ABCMeta): num = 20 def __init__(self): print("這是B的構造方法") @abstractmethod def show2(self): pass @classmethod # 可以定義類方法 def eat(cls): print("吃 2") @staticmethod def drink(): # 可以定義靜態方法 print("喝 2") def sleep(self): # 可以定義普通方法 print("睡 2") class C(B, A): def show(self): print("showA") def show2(self): print("showB") c = C() # 在列印20前,先列印了 這是B的構造方法 但是沒有列印 這是A的構造方法 print(c.num) # 20 c.show() # showA c.eat() # 吃 2 c.drink() # 喝 2 c.sleep() # 睡 2 c.show2() # showB
抽象類中可以寫構造方法,但是子類繼承時只會呼叫第一個繼承類的構造方法。
抽象類方法和抽象靜態方法
class A和class B中增添這兩個方法
@classmethod
@abstractmethod
def swim(cls): # 抽象 類方法
pass
@staticmethod
@abstractmethod
def run(): # 抽象 靜態方法
pass
與抽象方法一樣,方法體可以不寫,能寫,但沒必要。
子類必須實現這兩個方法才能例項化。
不過!
子類實現後,就是普通方法了。與普通抽象方法的實現沒啥區別。
class C(B, A):
def show(self):
print("show")
def swim(self):
print("swim")
def run(self):
print("run")
# 不能這樣永類名呼叫
C.swim()
C.run()
所以還要在實現的方法上加對應的裝飾器。
@classmethod
def swim(cls):
print("swim")
@staticmethod
def run():
print("run")
然而這時寫反裝飾器(不按抽象類中的來),也不會有啥事。
所以說抽象類只保證了抽象類本身不能例項化,和子類必須實現與抽象的方法(抽象方法、抽象類方法、抽象靜態方法)同名的方法。
但是不保證子類的實現方法和原抽象的方法是同種型別。
簡單來說,只要子類有同名的方法就行,管它是不是為了實現抽象的方法的,還是自己本身獨有的。
抽象“屬性”
Python中有個個特殊的裝飾器@property
。用了它,方法就可以當屬性一樣用(呼叫時不需要加(),直接.屬性名
)
@property
@abstractmethod
def age(self): # 抽象屬性
pass
但是在實現時也必須要加上@property
,否則也就當成普通方法了。
@property
def age(self):
print("age")
return 22
print(c.age) # 先列印 22 再列印 age
print(type(c.age)) # <class 'int'>
c.age = 30 # 這樣會拋異常,只讀不能修改
print(c.age)
這個屬性是只讀的,不能修改。
當然可以配合@X.setter
和@X.deleter
來使得該“抽象”屬性可讀可寫可刪除。(其中X
是@property
修飾的方法的方法名)
這裡不演示了,有興趣的可以去檢視@property
/@X.setter
/@X.deleter
三個裝飾器的使用情況和方法。
也一定要注意子類中實現時,對應也要加上裝飾器修飾,否則會被當成普通方法。
使用抽象類的好處
對於一些有相似屬性和方法類,可以統一把這些屬性和方法抽出來放在一個類中。
這樣可以很好的理清類直接的繼承關係、方法屬性的意義、做到了解耦合。
比如說,貓和狗都是動物,都有年齡、性別等屬性、都有吃喝拉撒睡等動作(假設這些共有動作兩者是不同的)。那麼就可以把這些屬性和動作抽出來放在一個動物類中(class Animal)。讓貓類(class Cat)和狗類(class Dog)都繼承這個動物類,然後根據自身特點實現這些屬性和動作即可。
其實如果動作相同的話就沒必要寫成抽象方法,而是寫成普通方法即可,這樣不需要重寫方法,直接呼叫就行。
抽象就是一種規範,讓繼承了 抽象類 後的子類必須實現抽象方法才能夠例項化。
介面?
Python中沒有介面的概念,但是鑑於Java的介面理念,Python可以用abc模組
像實現抽象類那樣,來實現介面類。
只要在類中只寫抽象的方法、static和final屬性(用@property
等和@staticmethod
配合著使用),那麼就可以。
當然肯定沒有Java中那麼嚴格的檢驗,不過也勉強能做到。
但最終還是用看開發的需求來決定是否要這樣去寫,不能為了炫技而去寫一些只能自己才能看懂的程式碼啊,那就失去了程式設計的初衷了。