The attrs package for python provides a simple way to validate passed variables upon instantiation (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')
This works well, as the thrown exception proves. However, when I change the value of x after instantiation, no exception is thrown:
c = C(30)
c.x = '30'
For static objects this behavior can be OK, but it seems heavily dangerous to me to assume that an object is static. Is there a workaround to get validators with attrs that also work after instantiation?
One way which keeps mutability is like so:
@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
But even this is not safe against c._x = '30'
.
The problem isn't with attrs
, it's with python. a.b = c
is always going to work when a.b
is just a variable. This is due to pythons concept of "we're all consenting adults here" -- i.e. everything is public, and everything is modifiable. If you edit something you shouldn't, it's your fault.
That being said, attrs
does provide a hack to prevent attribute assignment to give an illusion of immutability:
@attr.s(frozen=True)
class C(object):
x = attr.ib(validator=attr.validators.instance_of(int))
c = C(1)
c.x = 30 # raises FrozenInstanceError