Python:元类或`__init_subclass__`:在创建错误的子类期间惯用的异常?

3

有没有任何关于元类或__init_subclass__应该引发哪种异常来强制实现子类中的某些不变量的好惯例?

对于这个问题的回答,我会接受Python内置行为、PEP、标准库或成熟的第三方库的先例,或者为什么应该以某种方式完成的令人信服的推理。


我会自己回答我目前所找到的内容,但我欢迎其他答案,如果它更全面地涵盖了现有先例或提出了更具有说服力的原因来以某种方式完成,我将接受其他答案。 - mtraceur
1个回答

2
我知道 Python 中的惯例是始终引发 TypeError 异常:
  • If you try to give slots to subclasses of types that don't support it, CPython raises a TypeError:
    >>> class MorePowerfulTuple(tuple): pass
    ...     __slots__ = ('power',)
    ...
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: nonempty __slots__ not supported for subtype of 'tuple'
    
  • If you try to subclass bool, it raises a TypeError:
    >>> class BetterBool(bool): pass
    ...
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: type 'bool' is not an acceptable base type
    
  • At least one other Python implementation, MicroPython, has taken this convention and used it for other classes that it does not allow subclassing:
    >>> class StrongerFasterMethodType(types.MethodType):
    ...     pass
    ...
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: type 'bound_method' is not an acceptable base type
    
这里最具有代表性的先例是第一个。可以认为后面两个是特定类型的子类化错误,因为它们是由于尝试使用错误的类型作为子类化操作的输入而导致的错误(所以如果只有这些示例,则与其他异常类型更适合的子类化出错原因不同)。但第一个即使问题不一定与类型有关,仍会引发 TypeError :您可以将 __slots__ =('power',) 替换为 __slots__ =(),它仍然可以工作。因此,至少在语言本身的参考实现中,我们已经有了一个实例,其中一个子类化错误被报告为 TypeError ,即使在许多其他情况下,使用空可迭代对象而不是具有一个或多个元素的可迭代对象更合适应该是一个 ValueError 。
似乎在内置异常层次结构中没有专门为此目的设计的类。
此外,该文档排除了尝试使用多重继承来子类化这些错误(在某些情况下可能很诱人 - 例如,元类或__init_subclass__的实现者可能会想象一个工厂函数根据某些参数动态地在程序执行过程中制作子类,并认为引发更精确的错误,如ValueError,会有所帮助 - 因此,开发人员可能会决定将TypeErrorValueError及其自己的子类错误和ValueError相乘,而不是像上面内置类的预先设置那样仅引发一个普通的TypeError),因为该文档明确建议不要从多个异常类型进行子类化,因为内部实现细节可以自由地以使它们无法同时用作基类的方式实现不同的异常:
有些异常类型具有自定义的内存布局,这使得创建继承自多个异常类型的子类成为不可能。类型的内存布局是一项实现细节,可能会在 Python 版本之间发生变化,从而在未来引起新的冲突。因此,建议完全避免继承多个异常类型。
我不知道是否有任何PEP、标准库或第三方库建立了特定的实践方法。
(然而,肯定有像namedtuple、dataclasses、attrs这样的库,甚至像SQLAlchemy和Pydantic这样根据用户参数自动生成类的库,可能值得研究它们自己的示例。)

2
额外的先例:如果您创建一个带有已装饰@abs.abstractmethod方法的abc.ABC类,并尝试实例化任何未完全重写所有这些abstractmethod的类,以获得该方法的非抽象覆盖,则会出现TypeError: Can't instantiate abstract class Foo with abstract method foo错误(对于class Foo(abc.ABC):@abc.abstractmethoddef foo(self): raise NotImplementedError)。这不是类定义不变式,但它具有相同的目的; 未定义所有抽象方法的类将考虑构建它时出现TypeError - ShadowRanger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接