[英]Python Class Inheritance: How to initialize a subclass with values not in the parent class
[英]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.NotFoundError
和B.NotFoundError
是單獨的類,因為A
和B
是單獨的類。 然而,事實並非如此。
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 中的任何代碼來創建相應的異常 - 但我們確實需要為所有這些異常使用元類。
這可以說更簡單。 我們簡單地定義一個 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
A.NotFoundError
和B.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.NotFoundError
與A.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.