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:\python\block_model_variable_imputer\venv_fixed\lib\site-packages\IPython\core\interactiveshell.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
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
.