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
.