I would like to do different verifications on the same property in Python and I am not sure exactly what is the right way to do this. For instance, in the code below, I would like to verify my obj's First and Last name for both (starts with capital letter and length is more than 2 symbols). For the last name property, I combined all checks in one setter. In this case, my code does not catch both exceptions simultaneously. If I split them just like in first name setter, I can't get it to run all the time. My question is what is the right way to achieve? thanks.
class Person:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
@property
def last_name(self):
return self.__last_name
@last_name.setter
def last_name(self, value):
if len(value) > 2:
self.__last_name = value
else:
raise Exception("Last name: more than 2 symbs needed")
for letter in value:
if letter == letter.upper():
self.__last_name = value
break
else:
raise Exception('Last name: has to start with upper case')
@property
def first_name(self):
return self.__first_name
@first_name.setter
def first_name(self, value):
for letter in value:
if letter == letter.upper():
self.__first_name = value
break
else:
raise Exception('First name: has to start with upper case')
@first_name.setter
def first_name(self,value):
if len(value) > 2:
self.__first_name = value
else:
raise Exception("First name: more than 2 symbs needed")
Jack = Person('Ja', 'sparrow')
print(Jack.first_name)
print(Jack.last_name)
I've cleaned up your code a bit and made some changes for it to work properly. Also, I'd suggest you to raise ValueError()
instead of Exception
as this literally is an error in the value of first_name
and last_name
.
It is possible to catch both errors in one single value by tweaking your code and printing the error message accordingly. However, you can't expect to catch errors in both first_name
and last_name
because as soon as the first exception occurs, the code stops executing, so the next Exception can't be caught and shown. This is simply because of the fact that python is an interpreted language and it stops executing at the first occurance of an error.
Here is my version of your code.
Assigning the value to self.first_name
will actually call the method first_name()
which has been decorated with @property
.
The same is true for self.last_name
.
This makes sure that the values are validated even inside __init__()
The @property.setter
methods set the values to variables named like _property; _first_name
and _last_name
.
Having an underscore(_) before a variable shows the intent that the variable isn't supposed to be used publicly and is meant to be like an internal variable for the code.
_first_name
and _last_name
hold the actual values of the object and the @property
methods simply act as a kind of front end for the user to access(get and set) these values. As these are methods, validation can be easily done here.
class Person:
def __init__(self, first_name='John', last_name='Doe'):
self.first_name = first_name
self.last_name = last_name
def __repr__(self):
return f'{self.__class__.__name__}({self.first_name} {self.last_name})'
@property
def last_name(self):
return self._last_name
@last_name.setter
def last_name(self, value):
msg = ''
if len(value) <= 2:
msg = 'Last name: more than 2 symbols needed'
if not value[0].isupper():
if not msg:
msg = 'Last name: has to start with upper case'
else:
msg += ', has to start with upper case'
if msg:
raise ValueError(msg)
else:
self._last_name = value
@property
def first_name(self):
return self._first_name
@first_name.setter
def first_name(self, value):
msg = ''
if len(value) <= 2:
msg = 'First name: more than 2 symbols needed'
if not value[0].isupper():
if not msg:
msg = 'First name: has to start with upper case'
else:
msg += ', has to start with upper case'
if msg:
raise ValueError(msg)
else:
self._first_name = value
Here is an example of a valid Person
object.
>>> p = Person('Jack', 'Sparrow')
>>> p
Person(Jack Sparrow)
These are the Exceptions caused while trying to create objects with invalid values.
>>> p = Person('ja', 'Sparrow')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "per.py", line 3, in __init__
self.first_name = first_name
File "per.py", line 47, in first_name
raise ValueError(msg)
ValueError: First name: more than 2 symbols needed, has to start with upper case
>>> p = Person('Jack', 'sparrow')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "per.py", line 4, in __init__
self.last_name = last_name
File "per.py", line 26, in last_name
raise ValueError(msg)
ValueError: Last name: has to start with upper case
If both the first_name
and last_name
are invalid, only the Exception
for first_name
is raised because that is the value that gets assigned first in the code. Execution stops as soon as an Exception
is raised, hence the error with the value of last_name
is not caught anymore.
>>> p = Person('jack', 'Sp')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "per.py", line 3, in __init__
self.first_name = first_name
File "per.py", line 47, in first_name
raise ValueError(msg)
ValueError: First name: has to start with upper case