Python attrs包:实例化后的验证器

时间:2018-06-12 12:32:25

标签: python validation properties attributes attr

python的attrs包提供了一种在实例化时验证传递变量的简单方法(example taken from attrs page):

>>> @attr.s
... class C(object):
...     x = attr.ib(validator=attr.validators.instance_of(int))
>>> C(42)
C(x=42)
>>> C("42")
Traceback (most recent call last):
   ...
TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')

这很有效,因为抛出的异常证明了这一点。但是,当我在实例化后更改x的值时,不会抛出任何异常:

c = C(30)
c.x = '30'

对于静态对象,这种行为可以正常,但假设一个对象是静态的,这对我来说似乎很危险。是否有一个解决方法来获得带有attrs的验证器,它们在实例化之后也可以工作?

3 个答案:

答案 0 :(得分:1)

根据discussion in thread for attrs

  

所以验证码的原始实现实际上也是如此   在分配时运行验证器。

     

我在合并之前删除了它,因为在动态语言中   Python有太多方法来规避它和我个人   我不喜欢不改变我的对象。因此得到了丰富的支持   冻结类并使用更改的属性创建新实例   (缔合)。

     

当然,你可以通过实现一个自己添加这样一个功能    setattr 方法,只要您尝试设置属性,就会调用验证程序。

答案 1 :(得分:1)

保持可变性的一种方法是这样的:

@attr.s
class C(object):

    _x = attr.ib(validator=attr.validators.instance_of(int))

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        assert isinstance(value, int), repr(value)  # or whatever error you want
        self._x = value

但即使这对c._x = '30'也不安全。

问题不在attrs,而在于python。当a.b = c只是一个变量时,a.b始终有效。这是由于蟒蛇概念“我们都在这里同意成年人” - 即一切都是公开的,一切都是可以修改的。如果你编辑的东西不应该,那就是你的错。

话虽如此,attrs确实提供了一个黑客来阻止属性赋值给出不可变性的错觉:

@attr.s(frozen=True)
class C(object):

    x = attr.ib(validator=attr.validators.instance_of(int))


 c = C(1)
 c.x = 30  # raises FrozenInstanceError

答案 2 :(得分:0)

version 20.1.0 中,他们添加了 on_setattr

<块引用>

每当用户尝试设置属性时运行的可调用对象(通过像 i.x = 42 这样的赋值或像 setattr(i, "x", 42) 这样的 setattr)。它接收与验证器相同的参数:实例、正在修改的属性和新值。

所以,补充:

import attr

@attr.s
class C(object):
    x = attr.ib(
        validator=attr.validators.instance_of(int),
        on_setattr = attr.setters.validate,  # new in 20.1.0
    )

收益

C(42)
# C(x=42)
C("42")
#  TypeError: ("'x' must be <class 'int'> 

此外,特别是对于像您的示例中的字符串输入,您可能会发现 attrs 转换器很方便。例如,要自动转换:

@attr.s
class CWithConvert(object):
    x = attr.ib(
        converter=int,
        validator=attr.validators.instance_of(int),
        on_setattr = attr.setters.validate,
    )

CWithConvert(42)
# CWithConvert(x=42)
CWithConvert("42")
# CWithConvert(x=42)  # converted!
CWithConvert([42])
# TypeError: int() argument must be a string, a bytes-like object or a number, not 'list'

小心:

CWithConvert(0.8)
# CWithConvert(x=0)  # float to int!