Search code examples
pythonvalidationpropertiesattributesattr

Python attrs package: validators after instantiation


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?


Solution

  • 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