簡體   English   中英

在Python中設置父類子類的自動inheritance

[英]Setup automatic inheritance of parent class' subclass in Python

運行此代碼時,打印顯示消息"no number found in class A" ,盡管實際上它沒有在 class B 的 object 中找到。目的是僅更改 Base class,以便在繼承時從中,后代創建他們自己的從 Base 繼承的NotFoundError異常。

class Base:
    class NotFoundError(Exception):
        pass

    def __init__(self, numbers: list[int]):
        self.numbers = numbers

    def pop_by_val(self, number: int):
        try:
            self.numbers.remove(number)
        except ValueError:
            raise self.NotFoundError()


class A(Base):
    pass


class B(Base):
    pass


a = A([1, 2])
b = B([1, 2])

try:
    a.pop_by_val(1)
    b.pop_by_val(3)
except A.NotFoundError:
    print("no number found in class A")
except B.NotFoundError:
    print("no number found in class B")

我想它可以通過某種init / new dunders 的定制來修復,但我的嘗試沒有成功

沒有自動定義新的、繼承的 class 屬性,但您可以定義__init_subclass__來為您定義它們。

class Base:
    class NotFoundError(Exception):
        pass

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        class NotFoundError(Base.NotFoundError):
            pass
        cls.NotFoundError = NotFoundError

    def __init__(self, numbers: list[int]):
        self.numbers = numbers

    def pop_by_val(self, number: int):
        try:
            self.numbers.remove(number)
        except ValueError:
            raise self.NotFoundError()

(這基本上與@kindall's answer做同樣的事情,但避免使用元類。盡可能使用__init_subclass__使您的 class 更容易與更多類交互,因為元類不能很好地組合。)

您似乎希望A.NotFoundErrorB.NotFoundError是單獨的類,因為AB是單獨的類。 然而,事實並非如此。

The code inside class Base: is not some kind of template that is re-run for each derived class. There is an actual object that represents that class, and the code creates that object, and that object has another class, Base.NotFoundError ,作為一個屬性。

派生類本身不定義名為NotFoundError的屬性; 因此,在其中查找NotFoundError會發現附加到Base的那個 -就像A.__init__隱式調用Base.__init__

在這個例子中使用子類化並沒有什么意義——相反,只需使用單獨的實例,每個實例都有自己的.numbers 如有必要,顯式引發的異常可以包括對self的引用,以明確哪個實例未能包含該數字。

然而,有時會有一個合法的用例來制作像這樣的類的“並行”結構,其中每個派生的 class 都有自己的關聯 class。當然,它可以在每個派生的 class 中定義NotFoundError ,但是有一些方法可以最小化樣板文件。

特別是,我們可以使用元類(實際上可以作為函數實現),或者class 裝飾器


函數即元類方法

元類是用來創建 class 個實例的東西。 它只是 class,這些類是其中的實例。 默認情況下,每個 class 都是內置type的一個實例。 我們可以定義我們自己的元類,它做一些類似於實例化時type所做的事情,但也添加了相應的異常 class。因此:

def TypeWithNotFound(name, bases, attributes):
    attributes['NotFoundError'] = type('NotFoundError', (Exception,), {})
    return type(name, bases, attributes)

class Base(metaclass=TypeWithNotFound):
    def __init__(self, numbers):
        self.numbers = numbers
    def pop_by_val(self, number):
        try:
            self.numbers.remove(number)
        except ValueError:
            raise self.NotFoundError()

class A(Base, metaclass=TypeWithNotFound): pass

class B(Base, metaclass=TypeWithNotFound): pass

對於元類,我們可以使用 function 而不是 class,因為我們只需要替換它被調用時發生的事情(我們的類實際上仍然具有type作為元類,只是修改了設置過程)。 我們將新的名稱 class 作為字符串、基類元組和屬性字典傳遞給我們。 (Python 通常通過從class主體中計算這些 arguments,然后用它們調用type來創建類。)因此,我們動態創建一個異常 class,將其存儲在屬性字典中,然后動態創建實際的 class。

從那里開始,我們不需要每個 class 中的任何代碼來創建相應的異常 - 但我們確實需要為所有這些異常使用元類。

Class 裝飾器進場

這可以說更簡單。 我們簡單地定義一個 function 接受、修改並返回一個 class,然后我們可以使用這個 function 作為類的裝飾器。 具體來說,我們將通過創建並添加相關的異常類型來修改 class。 因此:

def addNotFound(cls):
    cls.NotFoundError = type('NotFoundError', (Exception,), {})
    return cls

@addNotFound
class Base:
    # as before

@addNotFound
class A(Base): pass

@addNotFound
class B(Base): pass

實際的元類方法

這在概念上有點難,但它進一步減少了樣板文件:派生類必須具有與其基類相同的元類。

class TypeWithNotFound(type):
    def __new__(cls, name, bases, attributes):
        attributes['NotFoundError'] = type('NotFoundError', (Exception,), {})
        return super().__new__(cls, name, bases, attributes)

我們定義了type元類的子類,並定義了它的__new__ 現在,當TypeWithNotFound的實例(即:具有此元類的類)時,重寫的__new__將在委托回基本type.__new__之前修改屬性字典。 (因為我們重寫__new__而不是__init__ ,所以第一個參數將是 class TypeWithNotFound本身,它需要轉發給基本的__new__調用。(這只是標准的最佳實踐,以防我們以后需要在 inheritance 之間實現合作多個 inheritance元類。)

無論如何,現在我們可以簡單地定義Base來使用這個元類,派生類也會自動使用它:

class Base(metaclass=TypeWithNotFound):
    # as before

class A(Base): pass

class B(Base): pass

對於這些方法中的每一種,讀者應該能夠驗證結果:

>>> a = A([1, 2])
>>> b = B([1, 2])
>>> 
>>> try:
...     a.pop_by_val(1)
...     b.pop_by_val(3)
... except A.NotFoundError:
...     print("no number found in class A")
... except B.NotFoundError:
...     print("no number found in class B")
... 
no number found in class B

參考資料/另見

Python 中的元類是什么?

如何裝飾一個class?

A.NotFoundErrorB.NotFoundError指向相同的 class object,可以通過運行驗證:

print(id(A.NotFoundError) == id(B.NotFoundError))

當你跑步時

try:
    a.pop_by_val(1)
    b.pop_by_val(3)
except A.NotFoundError:
    print("no number found in class A")
except B.NotFoundError:
    print("no number found in class B")

第一個pop_by_val(1)成功,所以程序繼續。 在第二個b.pop_by_val(3)上,引發了B.NotFoundError ,但由於B.NotFoundErrorA.NotFoundError相同,因此第一個except子句捕獲了異常,因此打印no number found in class A

一種解決方案是編寫一個元類,為每個子類創建一個新的異常類型。

class NotFoundError(Exception):
     pass

class NotFoundErrorMeta(type):

    def __init__(cls, name, bases, attrs):
        super().__init__(name, bases, attrs)
        cls.NotFoundError = type("NotFoundError", (NotFoundError,), {
            "__qualname__": cls.__qualname__ + ".NotFoundError" })

class Base(metaclass=NotFoundErrorMeta):

    def __init__(self, numbers: list[int]):
        self.numbers = numbers

    def pop_by_val(self, number: int):
        try:
            self.numbers.remove(number)
        except ValueError:
            raise self.NotFoundError()

現在每個 class 都會引發自己的NotFoundError異常。 這些是在元類外部定義的NotFoundError class 的子類,因此您可以通過該基類 class 捕獲任何NotFoundError ,或捕獲特定類的異常。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM