how to make a person with Exceptions
I had a problem when I made a person with conditions
I skipped test_when_year_of_birth_is_not_an_integer because it is always in a RED state since it causes an Exception.
I commented out the bad
year_of_birthvalues in test_john for when a person is older than120because it causes an Exception.
Python has a way that allows programs to make a choice when they encounter an Exception and continue running without stopping. It is the try statement.
I want to use the try statement to handle making sure the program raises an Exception
if the age is older than
120if the
year_of_birthis not an integer
preview
I have these tests by the end of the chapter
216 def test_when_person_is_too_old_to_be_alive(self):
217 try:
218 src.person.Person(
219 first_name='first_name',
220 last_name='last_name',
221 sex='F',
222 year_of_birth=datetime.date.today().year-121,
223 )
224 except ValueError:
225 pass
227 def test_when_year_of_birth_is_not_an_integer(self):
228 try:
229 src.person.Person(
230 first_name='first_name',
231 last_name='last_name',
232 sex='M',
233 year_of_birth=None,
234 )
235 except TypeError:
236 pass
238 try:
239 src.person.Person(
240 first_name='first_name',
241 last_name='last_name',
242 sex='M',
243 year_of_birth=2026.0,
244 )
245 except TypeError:
246 pass
248 try:
249 src.person.Person(
250 first_name='first_name',
251 last_name='last_name',
252 sex='M',
253 year_of_birth='2026',
254 )
255 except TypeError:
256 pass
open the project
I open a terminal
I change directory to the project
cd personI use pytest-watcher to run the tests automatically
uv run pytest-watcher . --nowthe terminal shows
tests/test_person.py ....... [100%] ============ 7 passed, 1 skipped in T.UVs ============
test_when_person_is_too_old_to_be_alive
RED: make it fail
I open
test_person.pyfrom thetestsfolderI add test_when_person_is_too_old_to_be_alive for if the age of the person is greater than
120, intests/test_person.py202 def test_underage_citizen(self): 203 person = src.person.Person( 204 first_name='first_name', 205 last_name='last_name', 206 sex='M', 207 year_of_birth=datetime.date.today().year-17, 208 is_citizen=True, 209 passed_test=True, 210 ) 211 self.assertEqual(person.can_vote(), False) 212 self.assertEqual( 213 person.can_get_license(), False 214 ) 215 216 def test_when_person_is_too_old_to_be_alive(self): 217 src.person.Person( 218 first_name='first_name', 219 last_name='last_name', 220 sex='F', 221 year_of_birth=datetime.date.today().year-121, 222 ) 223 224 @unittest.skip('will always fail') 225 def test_when_year_of_birth_is_not_an_integer(self):the terminal is my friend, and shows AssertionError
E AssertionErrorI use a calculation (
datetime.date.today().year-121) as the year of birth so that it will always be121years ago.
GREEN: make it pass
I open
src/person/__init__.pyI change the assert statement in the calculate_age function for if the age is less than or equal to
120to raise an Exception if the age is greater than12044def calculate_age(year_of_birth): 45 assert isinstance(year_of_birth, int) 46 age = ( 47 datetime.date.today().year 48 - year_of_birth 49 ) 50 # assert age <= 120 51 if age > 120: 52 raise Exception 53 return age 54 55 56def say_hello( 57 first_name, last_name, year_of_birth, 58):the terminal is my friend, and shows Exception
E ExceptionI add a try statement to test_when_person_is_too_old_to_be_alive in
tests/test_person.py216 def test_when_person_is_too_old_to_be_alive(self): 217 try: 218 src.person.Person( 219 first_name='first_name', 220 last_name='last_name', 221 sex='F', 222 year_of_birth=datetime.date.today().year-121, 223 ) 224 except: 225 pass 226 227 @unittest.skip('will always fail') 228 def test_when_year_of_birth_is_not_an_integer(self):the test passes, confirming that when the value for
year_of_birthmakes the person older than120an Exception is is raised.The try statement is like an if statement for Exceptions. It tells the program what to do if an Exception is is raised. A simple way to think of it is
trysomethingexcept- if something raises an Exception do something else
pass is a special keyword that allows the try statement to follow Python language rules (the except block must have a body).
I add a git commit message
git commit -am \ 'add test_when_person_is_too_old_to_be_alive'
add exception handler to test_when_year_of_birth_is_not_an_integer
RED: make it fail
I remove the unittest.skip decorator from test_when_year_of_birth_is_not_an_integer and remove the comment from year_of_birth=None to test when year_of_birth is None, in tests/test_person.py
224 except:
225 pass
226
227 def test_when_year_of_birth_is_not_an_integer(self):
228 src.person.Person(
229 first_name='first_name',
230 last_name='last_name',
231 sex='M',
232 year_of_birth=None,
233 )
234 # year_of_birth=2026.0, # fails
235 # year_of_birth='2026', # fails
236 # year_of_birth=(2026,), # fails
237
238 def test_dir_person_class(self):
the terminal is my friend, and shows AssertionError
E AssertionError
GREEN: make it pass
I change
assert isinstance(year_of_birth, int)to an if statement that raises an Exception whenyear_of_birthis not an integer in the calculate_age function insrc/person/__init__.py44def calculate_age(year_of_birth): 45 # assert isinstance(year_of_birth, int) 46 if not isinstance(year_of_birth, int): 47 raise Exception 48 49 age = ( 50 datetime.date.today().year 51 - year_of_birth 52 ) 53 # assert age <= 120 54 if age > 120: 55 raise Exception 56 return age 57 58 59def say_hello( 60 first_name, last_name, year_of_birth, 61):the terminal is my friend, and shows Exception
E ExceptionI add a try statement for when
year_of_birthis None to test_when_year_of_birth_is_not_an_integer intests/test_person.py227 def test_when_year_of_birth_is_not_an_integer(self): 228 try: 229 src.person.Person( 230 first_name='first_name', 231 last_name='last_name', 232 sex='M', 233 year_of_birth=None, 234 ) 235 except: 236 pass 237 # year_of_birth=2026.0, # fails 238 # year_of_birth='2026', # fails 239 # year_of_birth=(2026,), # fails 240 241 def test_dir_person_class(self):the test passes, showing that Exception is raised when
year_of_birthis not an integer.
REFACTOR: make it better
I make a person with a float as the value for
year_of_birth235 except: 236 pass 237 238 src.person.Person( 239 first_name='first_name', 240 last_name='last_name', 241 sex='M', 242 year_of_birth=2026.0, 243 ) 244 245 # year_of_birth='2026', # fails 246 # year_of_birth=(2026,), # fails 247 248 # fails if year_of_birth is not an integer 249 250 def test_dir_person_class(self):I add a try statement for when the
year_of_birthis a float235 except: 236 pass 237 238 try: 239 src.person.Person( 240 first_name='first_name', 241 last_name='last_name', 242 sex='M', 243 year_of_birth=2026.0, 244 ) 245 except: 246 pass 247 248 # year_of_birth='2026', # failsthe test passes, showing that Exception is raised when
year_of_birthis not an integer.I make a person with a string as the value for
year_of_birth245 except: 246 pass 247 248 src.person.Person( 249 first_name='first_name', 250 last_name='last_name', 251 sex='M', 252 year_of_birth='2026', 253 ) 254 255 # year_of_birth=(2026,), # fails 256 257 # fails if year_of_birth is not an integer 258 259 def test_dir_person_class(self):I add a try statement for when the
year_of_birthis a string245 except: 246 pass 247 248 try: 249 src.person.Person( 250 first_name='first_name', 251 last_name='last_name', 252 sex='M', 253 year_of_birth='2026', 254 ) 255 except: 256 pass 257 258 # year_of_birth=(2026,), # failsthe test passes, showing that Exception is raised when
year_of_birthis not an integer.I make a person with a tuple as the value for
year_of_birth255 except: 256 pass 257 258 src.person.Person( 259 first_name='first_name', 260 last_name='last_name', 261 sex='M', 262 year_of_birth=(2026,), 263 ) 264 265 def test_dir_person_class(self):I add a try statement for when the
year_of_birthis a tuple, and remove the other comments since I no longer need them255 except: 256 pass 257 258 try: 259 src.person.Person( 260 first_name='first_name', 261 last_name='last_name', 262 sex='M', 263 year_of_birth=(2026,), 264 ) 265 except: 266 pass 267 268 def test_dir_person_class(self):the test passes, showing that Exception is raised when
year_of_birthis not an integer.I remove the commented lines from the calculate_age function in
src/person/__init__.py44def calculate_age(year_of_birth): 45 if not isinstance(year_of_birth, int): 46 raise Exception 47 48 age = ( 49 datetime.date.today().year 50 - year_of_birth 51 ) 52 53 if age > 120: 54 raise Exception 55 return age 56 57 58def say_hello( 59 first_name, last_name, year_of_birth, 60):I add a git commit message
git commit -am \ 'add exception handler to test_when_year_of_birth_is_not_an_integer'
raise TypeError when year_of_birth is not an integer
The problem with using except: is that it catches all Exceptions which means it does not tell anyone that reads the code what the actual Exception is.
try:
something
except:
something else
is the same as
try:
something
except Exception:
something else
because Exception is the mother of all the Exceptions covered so far, they inherit from it.
From the Zen of Python: Explicit is better than implicit. I want to make things clearer.
RED: make it fail
I change the except clause in test_when_year_of_birth_is_not_an_integer for when the year_of_birth is a tuple to be more specific
258 try:
259 src.person.Person(
260 first_name='first_name',
261 last_name='last_name',
262 sex='M',
263 year_of_birth=(2026,),
264 )
265 except TypeError:
266 pass
267
268 def test_dir_person_class(self):
the terminal is my friend, and shows Exception
E Exception
because Exception is not TypeError even though TypeError is an Exception. I cannot use a child Exception to catch its parent Exception.
GREEN: make it pass
I change the raise statement in the calculate_age function for when the year_of_birth is not an integer to be more specific, in src/person/__init__.py
44def calculate_age(year_of_birth):
45 if not isinstance(year_of_birth, int):
46 # raise Exception
47 raise TypeError
48
49 age = (
50 datetime.date.today().year
51 - year_of_birth
52 )
the test passes because the try statement now only catches/handles TypeError.
try:
something
except TypeError:
something else
REFACTOR: make it better
I change the except clause in test_when_year_of_birth_is_not_an_integer for when the
year_of_birthis a string to catch AssertionError intests/test_person.py248 try: 249 src.person.Person( 250 first_name='first_name', 251 last_name='last_name', 252 sex='M', 253 year_of_birth='2026', 254 ) 255 except AssertionError: 256 pass 257 258 try:the terminal is my friend, and shows TypeError
E TypeErrorbecause TypeError is not AssertionError or a child of AssertionError.
I change the except clause in test_when_year_of_birth_is_not_an_integer for when the
year_of_birthis a string to catch TypeError248 try: 249 src.person.Person( 250 first_name='first_name', 251 last_name='last_name', 252 sex='M', 253 year_of_birth='2026', 254 ) 255 except TypeError: 256 pass 257 258 try:the test passes.
I change the except clause in test_when_year_of_birth_is_not_an_integer for when the
year_of_birthis a float to catch NameError238 try: 239 src.person.Person( 240 first_name='first_name', 241 last_name='last_name', 242 sex='M', 243 year_of_birth=2026.0, 244 ) 245 except NameError: 246 pass 247 248 try:the terminal is my friend, and shows TypeError
E TypeErrorI change the except clause in test_when_year_of_birth_is_not_an_integer for when the
year_of_birthis a float to catch TypeError238 try: 239 src.person.Person( 240 first_name='first_name', 241 last_name='last_name', 242 sex='M', 243 year_of_birth=2026.0, 244 ) 245 except TypeError: 246 pass 247 248 try:the test passes.
I change the except clause in test_when_year_of_birth_is_not_an_integer for when the
year_of_birthis None to catch ValueError227 def test_when_year_of_birth_is_not_an_integer(self): 228 try: 229 src.person.Person( 230 first_name='first_name', 231 last_name='last_name', 232 sex='M', 233 year_of_birth=None, 234 ) 235 except ValueError: 236 pass 237 238 try:the terminal is my friend, and shows TypeError
E TypeErrorbecause TypeError is not ValueError or a child of ValueError.
I change the except clause in test_when_year_of_birth_is_not_an_integer for when the
year_of_birthis None to catch TypeError227 def test_when_year_of_birth_is_not_an_integer(self): 228 try: 229 src.person.Person( 230 first_name='first_name', 231 last_name='last_name', 232 sex='M', 233 year_of_birth=None, 234 ) 235 except TypeError: 236 pass 237 238 try:the test passes.
I add a git commit message
git commit -am \ 'raise TypeError when year_of_birth is not an integer'
raise ValueError when age is greater than 120
RED: make it fail
I change the except clause in test_when_person_is_too_old_to_be_alive to catch ValueError
216 def test_when_person_is_too_old_to_be_alive(self):
217 try:
218 src.person.Person(
219 first_name='first_name',
220 last_name='last_name',
221 sex='F',
222 year_of_birth=datetime.date.today().year-121,
223 )
224 except ValueError:
225 pass
226
227 def test_when_year_of_birth_is_not_an_integer(self):
the terminal is my friend, and shows Exception
E Exception
because Exception is not ValueError even though ValueError is an Exception. I cannot use a child Exception to catch its parent Exception.
GREEN: make it pass
I change the raise statement in the calculate_age function for when the age is greater than
120to be more specific, insrc/person/__init__.py44def calculate_age(year_of_birth): 45 if not isinstance(year_of_birth, int): 46 # raise Exception 47 raise TypeError 48 49 age = ( 50 datetime.date.today().year 51 - year_of_birth 52 ) 53 54 if age > 120: 55 # raise Exception 56 raise ValueError 57 return age 58 59 60def say_hello( 61 first_name, last_name, year_of_birth, 62):the test passes.
I remove the commented lines from the calculate_age function
44def calculate_age(year_of_birth): 45 if not isinstance(year_of_birth, int): 46 raise TypeError 47 48 age = ( 49 datetime.date.today().year 50 - year_of_birth 51 ) 52 53 if age > 120: 54 raise ValueError 55 return age 56 57 58def say_hello( 59 first_name, last_name, year_of_birth, 60):I add a git commit message
git commit -am \ 'raise ValueError when age > 120'
close the project
I close
test_person.pyandsrc/person/__init__.pyI use q on the keyboard to leave the tests. The terminal goes back to the command line.
I change directory to the parent of
personcd ..
review
I can use the try statement to make sure a program can make a decision when it runs into an Exception.
I can use the try statement in a test to confirm that a program raises an Exception when certain conditions are met.
I can use the raise statement to make an Exception happen to stop a program from running past a certain point.
My tests still have problems:
The attribute tests - test_dir_person_class and test_dir_person_instance catch changes to the attributes and methods of the Person class and they are a problem to maintain. There has to be a better way.
test_joe, test_jane, test_john and test_mary all still have the same three tests. There has to be a better way.
test_when_year_of_birth_is_not_an_integer has four tests that are basically the same, the only thing that changes are the values for the
year_of_birthparameter. There has to be a better way.
code from the chapter
what is next?
rate pumping python
If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too.