how to make a person with Exceptions


I had a problem when I made a person with conditions

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 120

  • if the year_of_birth is not an integer


preview

I have these tests by the end of the chapter

person/tests/test_person.py
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
person/tests/test_person.py
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
person/tests/test_person.py
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
person/tests/test_person.py
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
person/tests/test_person.py
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):

open the project

  • I open a terminal

  • I change directory to the project

    cd person
    
  • I use pytest-watcher to run the tests automatically

    uv run pytest-watcher . --now
    

    the 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.py from the tests folder

  • I add test_when_person_is_too_old_to_be_alive for if the age of the person is greater than 120, in tests/test_person.py

    202    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       AssertionError
    

    I use a calculation (datetime.date.today().year-121) as the year of birth so that it will always be 121 years ago.


GREEN: make it pass


  • I open src/person/__init__.py

  • I change the assert statement in the calculate_age function for if the age is less than or equal to 120 to raise an Exception if the age is greater than 120

    44def 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           Exception
    
  • I add a try statement to test_when_person_is_too_old_to_be_alive in tests/test_person.py

    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:
    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_birth makes the person older than 120 an Exception is is raised.

  • 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 when year_of_birth is not an integer in the calculate_age function in src/person/__init__.py

    44def 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           Exception
    
  • I add a try statement for when year_of_birth is None to test_when_year_of_birth_is_not_an_integer in tests/test_person.py

    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:
    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_birth is not an integer.


REFACTOR: make it better


  • I make a person with a float as the value for year_of_birth

    235        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):
    

    the terminal is my friend, and shows Exception

  • I add a try statement for when the year_of_birth is a float

    235        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',  # fails
    

    the test passes, showing that Exception is raised when year_of_birth is not an integer.

  • I make a person with a string as the value for year_of_birth

    245        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):
    

    the terminal is my friend, and shows Exception

  • I add a try statement for when the year_of_birth is a string

    245        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,), # fails
    

    the test passes, showing that Exception is raised when year_of_birth is not an integer.

  • I make a person with a tuple as the value for year_of_birth

    255        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):
    

    the terminal is my friend, and shows Exception

  • I add a try statement for when the year_of_birth is a tuple, and remove the other comments since I no longer need them

    255        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_birth is not an integer.

  • I remove the commented lines from the calculate_age function in src/person/__init__.py

    44def 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



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 120 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    )
    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.py and src/person/__init__.py

  • I use q on the keyboard to leave the tests. The terminal goes back to the command line.

  • I change directory to the parent of person

    cd ..
    

review

My tests still have problems:


code from the chapter

Do you want to see all the CODE I typed in this chapter?


what is next?

Would you like to test handling Exceptions in tests?


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.