简体   繁体   English

在Python中设置父类子类的自动inheritance

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

When running this code, the print displays the message "no number found in class A" , although in fact it was not found in an object of class B. The aim is to change only the Base class in such a way that, when inheriting from it, descendants create their own NotFoundError exception inherited from Base.运行此代码时,打印显示消息"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")

I guess it can be fixed by some kind of init / new dunders' customization, but I haven't succeeded in my tries我想它可以通过某种init / new dunders 的定制来修复,但我的尝试没有成功

There's no automatic definition of new, inherited class attributes, but you can define __init_subclass__ to define them for you.没有自动定义新的、继承的 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()

(This is basically doing the same thing as @kindall's answer , but avoids using a metaclass. Prefer __init_subclass__ where possible to make it easier for your class to interact with more classes, as metaclasses don't compose well.) (这基本上与@kindall's answer做同样的事情,但避免使用元类。尽可能使用__init_subclass__使您的 class 更容易与更多类交互,因为元类不能很好地组合。)

It seems that you expect A.NotFoundError and B.NotFoundError to be separate classes, since A and B are separate classes.您似乎希望A.NotFoundErrorB.NotFoundError是单独的类,因为AB是单独的类。 However, that is not the case.然而,事实并非如此。

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 , as an attribute. 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 ,作为一个属性。

The derived classes don't define attributes named NotFoundError themselves;派生类本身不定义名为NotFoundError的属性; thus, looking up NotFoundError within them finds the one attached to the Base - just like how eg A.__init__ implicitly calls Base.__init__ .因此,在其中查找NotFoundError会发现附加到Base的那个 -就像A.__init__隐式调用Base.__init__

It doesn't really make sense to use subclassing for this example - instead, just use separate instances that each have their own .numbers ;在这个例子中使用子类化并没有什么意义——相反,只需使用单独的实例,每个实例都有自己的.numbers the explicitly raised exception could include a reference to self if necessary, making it clear which instance failed to contain the number.如有必要,显式引发的异常可以包括对self的引用,以明确哪个实例未能包含该数字。

However, sometimes there will be a legitimate use case for making a "parallel" structure of classes like this, wherein each derived class has its own associated class. Of course it works to just define NotFoundError within each derived class, but there are ways to minimize the boilerplate.然而,有时会有一个合法的用例来制作像这样的类的“并行”结构,其中每个派生的 class 都有自己的关联 class。当然,它可以在每个派生的 class 中定义NotFoundError ,但是有一些方法可以最小化样板文件。

In particular, we can use a metaclass (which actually can be implemented as a function), or else a class decorator .特别是,我们可以使用元类(实际上可以作为函数实现),或者class 装饰器


Function-as-metaclass approach函数即元类方法

A metaclass is a thing used to create class instances.元类是用来创建 class 个实例的东西。 It's simply the class, of which those classes are instances.它只是 class,这些类是其中的实例。 By default, every class is an instance of the built-in type .默认情况下,每个 class 都是内置type的一个实例。 We can define our own metaclass that does something similar to what type does at instantiation time, but also adds the corresponding exception class. Thus:我们可以定义我们自己的元类,它做一些类似于实例化时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

For the metaclass, we can use a function rather than a class, since we only need to replace what happens when it's called (our classes will actually still have type as a metaclass, just the setup process is modified).对于元类,我们可以使用 function 而不是 class,因为我们只需要替换它被调用时发生的事情(我们的类实际上仍然具有type作为元类,只是修改了设置过程)。 We are passed the name of the new class as a string, a tuple of base classes, and a dict of attributes.我们将新的名称 class 作为字符串、基类元组和属性字典传递给我们。 (Python normally creates classes by computing these arguments from the class body, and then calling type with them.) So, we dynamically create an exception class, store it in the attributes dict, and then dynamically create the actual class. (Python 通常通过从class主体中计算这些 arguments,然后用它们调用type来创建类。)因此,我们动态创建一个异常 class,将其存储在属性字典中,然后动态创建实际的 class。

From there, we don't need any code within each class to create corresponding exceptions - but we do need to use the metaclass for all of them.从那里开始,我们不需要每个 class 中的任何代码来创建相应的异常 - 但我们确实需要为所有这些异常使用元类。

Class decorator approach Class 装饰器进场

This is arguably simpler.这可以说更简单。 We simply define a function that accepts, modifies and returns a class, and then we can use that function as a decorator for classes.我们简单地定义一个 function 接受、修改并返回一个 class,然后我们可以使用这个 function 作为类的装饰器。 Specifically, we will modify the class by creating and adding a related exception type.具体来说,我们将通过创建并添加相关的异常类型来修改 class。 Thus:因此:

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

Actual metaclass approach实际的元类方法

This is a little harder conceptually, but it minimizes the boilerplate even more: derived classes will necessarily have the same metaclass as their base.这在概念上有点难,但它进一步减少了样板文件:派生类必须具有与其基类相同的元类。

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

We defined a subclass of the type metaclass, and defined its __new__ .我们定义了type元类的子类,并定义了它的__new__ Now, when instances of TypeWithNotFound (ie: classes that have this metaclass) are created, the overridden __new__ will modify the attributes dict before delegating back to the base type.__new__ .现在,当TypeWithNotFound的实例(即:具有此元类的类)时,重写的__new__将在委托回基本type.__new__之前修改属性字典。 (Since we are overriding __new__ rather than __init__ , the first parameter will be the class TypeWithNotFound itself, which needs to be forwarded to the base __new__ call. (This is just standard best practice, in case we later need to implement cooperative multiple inheritance between metaclasses.) (因为我们重写__new__而不是__init__ ,所以第一个参数将是 class TypeWithNotFound本身,它需要转发给基本的__new__调用。(这只是标准的最佳实践,以防我们以后需要在 inheritance 之间实现合作多个 inheritance元类。)

Anyway, now we can simply define Base to use this metaclass, and the derived classes will also use it automatically:无论如何,现在我们可以简单地定义Base来使用这个元类,派生类也会自动使用它:

class Base(metaclass=TypeWithNotFound):
    # as before

class A(Base): pass

class B(Base): pass

For each of these approaches, the reader should be able to verify the result:对于这些方法中的每一种,读者应该能够验证结果:

>>> 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

References / See Also参考资料/另见

What are metaclasses in Python? Python 中的元类是什么?

How to decorate a class?如何装饰一个class?

A.NotFoundError and B.NotFoundError point to the same class object, as can be verified by running: A.NotFoundErrorB.NotFoundError指向相同的 class object,可以通过运行验证:

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

When you run当你跑步时

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")

The first pop_by_val(1) succeeds, so the program goes on.第一个pop_by_val(1)成功,所以程序继续。 On the second b.pop_by_val(3) , B.NotFoundError is raised, but since B.NotFoundError is identical to A.NotFoundError , the exception is caught by the first except clause and therefore prints no number found in class A .在第二个b.pop_by_val(3)上,引发了B.NotFoundError ,但由于B.NotFoundErrorA.NotFoundError相同,因此第一个except子句捕获了异常,因此打印no number found in class A

One solution to this is to write a metaclass that creates a new exception type for each subclass.一种解决方案是编写一个元类,为每个子类创建一个新的异常类型。

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()

Now each class raises its own NotFoundError exception.现在每个 class 都会引发自己的NotFoundError异常。 These are subclasses of the NotFoundError class defined outside the metaclass, so you can catch any NotFoundError via that base class, or catch a specific class's exception.这些是在元类外部定义的NotFoundError class 的子类,因此您可以通过该基类 class 捕获任何NotFoundError ,或捕获特定类的异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM