1. 程式人生 > 實用技巧 >第三週:Python抽象類

第三週: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中那麼嚴格的檢驗,不過也勉強能做到。

但最終還是用看開發的需求來決定是否要這樣去寫,不能為了炫技而去寫一些只能自己才能看懂的程式碼啊,那就失去了程式設計的初衷了。