我一直在尝试验证用户可以在框架样式设置中创建的类。 我可以通过以下方式确保子类中存在类属性:
from abc import ABC, abstractmethod
class A(ABC):
@property
@classmethod
@abstractmethod
def s(self):
raise NotImplementedError
class ClassFromA(A):
pass
ClassFromA()
这导致以下
Exception
:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class ClassFromA with abstract methods s
我还可以在类创建时使用装饰器检查类属性
s
的类型,如下所示:
from abc import ABC, abstractmethod
def validate_class_s(cls):
if not isinstance(cls.s, int):
raise ValueError("S NOT INT!!!")
return cls
class A(ABC):
@property
@classmethod
@abstractmethod
def s(self):
raise NotImplementedError
@validate_class_s
class ClassFromA(A):
s = 'a string'
结果:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 3, in validate_class_s
ValueError: S NOT INT!!!
这对于最终检查类属性很有用。 但这会导致冗长的类定义,其中每个子类都必须进行修饰。
有没有办法验证基类中的类属性(示例中的
s
)?最好不要太冗长?
您可以使用Python 3.6中的新功能
__init_subclass__
。因此,例如,如果您想通过在基类上进行注释来指示子类上所需的方法和属性,您可以这样做:
_sentinel = type("_", (), {})
class Base:
def __init_subclass__(cls, **kwargs):
errors = []
for attr_name, type_ in cls.__annotations__.items():
if not isinstance(getattr(cls, attr_name, _sentinel), type_):
errors.append((attr_name, type))
if errors:
raise TypeError(f"Class {cls.__name__} failed to initialize the following attributes: {errors}")
super().__init_subclass__(**kwargs)
s: int
class B(Base):
pass
您可以将
collections.abc.Callable
放在需要方法的注释上,或者将 (type(None), int)
之类的元组放在可选整数上,但不幸的是 isinstance
不适用于“打字”模块提供的通用语义。如果您想要的话,我建议您查看 pydantic 项目并使用它。
另一种方法,使用可配置的验证器作为装饰器,您可以在多个不同的子类和基类上使用,从而节省一些冗长。 基类使用类型注释声明属性
def validate_with(baseclass):
def validator(cls):
for n, t in baseclass.__annotations__.items():
if not isinstance(getattr(cls, n), t):
raise ValueError(f"{n} is not of type {t}!!!")
return cls
return validator
class BaseClass:
s: str
i: int
@validate_with(BaseClass)
class SubClass(BaseClass):
i = 3
s = 'xyz'
如果类型不匹配,它会引发
ValueError
;如果属性不存在,则会引发 AttributeError
。
当然,您可以收集错误(如前面的答案所示)并一次性呈现所有错误,而不是在第一个错误处停止
使用 hasattr()
class Meta(type):
def __new__(cls, name, bases, attrs):
# Create the class
new_class = super().__new__(cls, name, bases, attrs)
# Ensure that the 'version' attribute is set
if not hasattr(new_class, 'version'):
raise TypeError(f"Class {name} must have a 'version' attribute.")
return new_class
class X(metaclass=Meta):
version = "1.0" # Define the 'version' attribute
class Y(metaclass=Meta):
version = "2.0" # Define the 'version' attribute
# Example usage
print(f"X version: {X.version}")
print(f"Y version: {Y.version}")
# This would raise an error because 'Z' does not have a 'version' attribute
class Z(metaclass=Meta):
pass