Validation in a getter method

Normally when I use properties in my classes and have both getter and setter methods, I do all the validation checks inside the setter.

On the other hand, what do I do if I want to restrict the end user from changing the value of the property but still need to perform validations? I just put the checks into the getter although I’m not sure if it really belongs there.

class Foo:
    def __init__(self, value):
        self.__value = value

    @property
    def value(self):
        if not isinstance(value, int):
            raise ValueError(f'Expecting an integer, got {type(value)}.')
        else:
            return self.__value

This works fine, but the issue is the a) no validation is done until the property is accessed and b) validation is performed every time a property is accessed, which can get expensive.

So I made the below version instead. It solves the above issues but it looks and feels wrong. Is there a better way to do this:

class Foo:
    def __init__(self, val):
        self.__val = val

    @property
    def __val(self):
        return self.__tmp
    
    @__val.setter
    def __val(self, value):        
        if not isinstance(value, int):
            raise ValueError()
        else:
            self.__tmp = value

    @property
    def value(self):
        return self.__val

I guess, I could do the validation in __init__ as well, but this is also ugly.

EDIT:

>>> a = Foo('bar')

Traceback (most recent call last):
  File "C:pythonblock_model_variable_imputervenv_fixedlibsite-packagesIPythoncoreinteractiveshell.py", line 3437, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-8-637c10c15d73>", line 1, in <module>
    a = Foo('bar')
  File "<ipython-input-7-f7d16143eb85>", line 3, in __init__
    self.__val = val
  File "<ipython-input-7-f7d16143eb85>", line 12, in __val
    raise ValueError()
ValueError

Answer

Personally, I’d go for

class Foo:
    def __init__(self, value):
        if not isinstance(value, int):
            raise TypeError(f'Expecting an integer, got {type(value)}.')
        else:
            self.__value = value

    @property
    def value(self):
        return self.__value

Note that I used TypeError rather than ValueError, as that’s the more appropriate exception type here 😉

This solves both of

a) no validation is done until the property is accessed and b) validation is performed every time a property is accessed, which can get expensive.

and also doesn’t introduce an additional helper variable like in your second snippet. Having setters and getters (for __val) exclusively for internal usage seems bad practice to me …

If the goal of your second snippet is to move the checking logic out of __init__ for readability, you could do something like

class Foo:
    def __init__(self, value):
        self.__value = self._check_value_input(value)

    def _check_value_input(value):
        if not isinstance(value, int):
            raise TypeError(f'Expecting an integer, got {type(value)}.')
        else:
            return value

    @property
    def value(self):
        return self.__value

This also has the benefit that you can reuse _check_value_input if at some point you do need to check the input at some other pace, e.g. if you do decide to re-add the setter for value.