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的验证器,它们在实例化之后也可以工作?
答案 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!