跳至內容

使用者:Xyy23330121/Python/類的私有量與類的繼承

來自維基學院


為篇幅起見,另起一個頁面講這些內容。

子類型與繼承

[編輯 | 編輯原始碼]

類的一個重要方法是繼承。我們可以這樣定義一個類型的子類型:

class C: pass

class D(C): pass

此時, D 會繼承來自 C 的一切屬性或方法。這種單重繼承的方式和定義函數時、變量的作用域很相似的。我們可以簡單理解為:DC 的子類型時,D 的作用域也是 C 作用域的下級作用域。

多重繼承(選學)

[編輯 | 編輯原始碼]
class C: pass

class D: pass

class E(C,D): pass

通過以上方法,可以讓 E 同時作為 CD 的子類型。

子類繼承父類型方法的順序是按照其mro()方法所示的順序的,讀者可以通過輸出 ClassName.mro() 的結果,得知 Python 會按照什麼順序去尋找方法或屬性。

以上述例子為例,E.mro() 的輸出為:

<class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>

即,它會先從類型 E 中查找方法。找不到時,再查找類型 C。若還找不到,則查找類型 D。最後查找類型 object。

繼承順序錯誤

[編輯 | 編輯原始碼]

如果這個順序無法生成,則新的子類無法正常創建,並且會報錯。比如:

class RootA: pass
class RootB: pass
class C(RootA, RootB): pass
class D(RootB, RootA): pass
class E(C,D): pass #报错:TypeError: Cannot create a consistent method resolution order (MRO) for bases RootA, RootB

讀者在完全了解這些順序之前,應當不要隨意使用多重繼承。具體順序,參見此文檔

私有量

[編輯 | 編輯原始碼]

大多數 Python 代碼遵循一個規律:以下劃線開頭的名稱應該被當作是私有部分,不應從外面隨意更改——如果進行更改,可能會破壞一部分功能。這個思想在「類」上的體現是一個「名稱改寫」的功能。

class C:
    __a = 0
    def prt(self):
        print(self.__a)
#print(C.__a) #报错:AttributeError: type object 'C' has no attribute '__a'
print(C._C__a)#输出:0
C.prt(C)      #输出:0

具體來講,在類型 ClassName 中使用以兩個下劃線開頭的函數或屬性時,Python 會自動把屬性名中的 __ 改為 _ClassName__。而在類型外時,則不會進行這樣的改寫。

在 Python 中,任何變量的數值都是可以改寫的。

class property(fget=None, fset=None, fdel=None, doc=None)

property 是一個特殊的、用作類型屬性的類型。對 property 實例進行的讀取、賦值和更改等操作,都會被改為執行 fget、fset 和 fdel 函數。比如以下示例:

class C:
    def __init__(self): self.__x = 0
    def getx(self): return self.__x
    def setx(self, value): self.__x = value
    def delx(self): del self.__x
    x = property(getx, setx, delx, "I'm the 'x' property.")


c = C()
print(c.x)    #输出:0
c.x = 1
print(c._C__x) #输出:1
del c.x
try: print(c._C__x)
except: print("c._C__x deleted")
#输出:c._C__x deleted

property 支持作為 裝飾器 來使用。以下代碼得到的結果和上面是相同的:

class C:
    def __init__(self): self.__x = 0
    
    @property
    def x(self): return self.__x
    
    @x.setter
    def x(self, value): self.__x = value
    
    @x.deleter
    def x(self): del self.__x

property 與只讀屬性

[編輯 | 編輯原始碼]

我們可以利用 property,來達成類似「只讀屬性」的效果,比如:

class AlwaysZero:
    @property
    def x(self): return 0
    @x.setter
    def x(self, value): raise AttributeError("readonly attribute")
    @x.deleter
    def x(self): raise AttributeError("readonly attribute")

a = AlwaysZero
print(a.x) #输出:0
a.x = 1    #报错:AttributeError: readonly attribute

這樣,就實現了只讀屬性的效果。以上代碼還可以簡化為:

class AlwaysZero:
    @property
    def x(self): return 0

a = AlwaysZero()
print(a.x) #输出:0
a.x = 1    #报错:AttributeError: property 'x' of 'AlwaysZero' object has no setter

isinstance / issubclass

[編輯 | 編輯原始碼]

isinstance(object, classinfo)

[編輯 | 編輯原始碼]

classinfo 應輸入類型、多個類型組成的元組或聯合類型

如果 objectclassinfo 中某個類型的實例,或某個類型的、子類型的實例。則返回 True,反之,返回 False

issubclass(class, classinfo)

[編輯 | 編輯原始碼]

isinstanceclassinfo 應輸入類型、多個類型組成的元組或聯合類型

如果 classclassinfo 中某個類型的子類型,則返回 True,反之,返回 False

抽象基類

[編輯 | 編輯原始碼]

抽象基類是用於判斷某個自定義類是否包含對應方法的。比如 collections.abc 模塊中的 collection.abc.Sized。當一個類,其實例具有 __len__ 方法時,該類就會被視為是 collection.abc.Sized 的一個子類。這和創建該類時,是否顯式地說明該類繼承於 collection.abc.Sized 無關。

這個子類關係,是可以通過上述 isinstanceissubclass 函數檢測出來的。這在一些情況下很有用。