【问题标题】:How to validate a class attribute in python?如何在python中验证类属性?
【发布时间】:2020-02-01 08:08:21
【问题描述】:

我一直在尝试验证用户可以在框架样式设置中创建的类。 我可以通过以下方式确保子类中存在类属性:

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 python-3.x metaclass python-object


【解决方案1】:

另一种方法,使用可配置的验证器作为装饰器,您可以在多个不同的子类和基类上使用,从而节省一些冗长。 基类使用类型注解声明属性

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

当然,您可以收集错误(如上一个答案)并一次性将它们全部呈现,而不是在第一个错误处停止

【讨论】:

    【解决方案2】:

    您可以使用 Python 3.6 中的新 __init_subclass__ 功能。
    这是在您的基类上定义的类方法,将在创建时为每个创建的子类调用一次。对于大多数断言用例,它可能比 Python 的 ABC 更有用,后者只会在类实例化时间引发错误(相反,如果您想在进入具体类之前对其他抽象类进行子类化,则必须在您的代码)。

    因此,例如,如果您想通过在基类上进行注释来指示子类上所需的方法和属性,您可以这样做:

    _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 项目并利用它。

    【讨论】:

      猜你喜欢
      • 2019-01-15
      • 2014-01-28
      • 1970-01-01
      • 1970-01-01
      • 2020-03-09
      • 1970-01-01
      • 1970-01-01
      • 2014-03-01
      • 2018-12-19
      相关资源
      最近更新 更多