how to make a calculator part 3

test_calculator_raises_type_error

I want to use TypeError with exception handlers to make sure that the calculator program only works with numbers, the way a Calculator would in the real world.

open the project

  • I change directory to the calculator folder

    cd calculator
    

    the terminal shows I am in the calculator folder

    .../pumping_python/calculator
    
  • I activate the virtual environment

    source .venv/bin/activate
    

    Attention

    on Windows without Windows Subsystem for Linux use .venv/bin/activate.ps1 NOT source .venv/bin/activate

    .venv/scripts/activate.ps1
    

    the terminal shows

    (.venv) .../pumping_python/calculator
    
  • I use pytest-watch to run the tests

    pytest-watch
    

    the terminal shows

    rootdir: .../pumping_python/calculator
    collected 4 items
    
    tests/test_calculator.py ....                                        [100%]
    
    ============================ 4 passed in X.YZs =============================
    
  • I hold ctrl on the keyboard and click on tests/test_calculator.py to open it in the editor

RED: make it fail

I add a new failing test to show that the calculator raises TypeError when one of the inputs is None, just like in test_type_error_w_objects_that_do_not_mix

42    def test_division(self):
43        try:
44            self.assertEqual(
45                src.calculator.divide(
46                    self.random_first_number,
47                    self.random_second_number
48                ),
49                self.random_first_number/self.random_second_number
50            )
51        except ZeroDivisionError:
52            self.assertEqual(
53                src.calculator.divide(self.random_first_number, 0),
54                'undefined: I cannot divide by 0'
55            )
56
57    def test_calculator_raises_type_error_w_none(self):
58        src.calculator.add(None, None)
59
60
61# Exceptions seen

the terminal shows TypeError

TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

GREEN: make it pass

I add assertRaises

57    def test_calculator_raises_type_error_w_none(self):
58        with self.assertRaises(TypeError):
59            src.calculator.add(None, None)

the test passes

REFACTOR: make it better

  • I add a failing line for division

    57    def test_calculator_raises_type_error_w_none(self):
    58        with self.assertRaises(TypeError):
    59            src.calculator.add(None None)
    60        src.calculator.divide(None, None)
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for /: 'NoneType' and 'NoneType'
    

    I add assertRaises

    58        with self.assertRaises(TypeError):
    59            src.calculator.add(self.random_first_number, None)
    60        with self.assertRaises(TypeError):
    61            src.calculator.divide(self.random_first_number, None)
    

    the test passes

  • I add another failing line, this time for multiplication

    60        with self.assertRaises(TypeError):
    61            src.calculator.divide(None, None)
    62        src.calculator.multiply(None, None)
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'
    

    I add assertRaises

    60        with self.assertRaises(TypeError):
    61            src.calculator.divide(None, None)
    62        with self.assertRaises(TypeError):
    63            src.calculator.multiply(None, None)
    

    the test passes

  • I add another one for subtraction

    62        with self.assertRaises(TypeError):
    63            src.calculator.multiply(None, None)
    64        src.calculator.subtract(None, None)
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
    

    I add the assertRaises method

    57    def test_calculator_raises_type_error_w_none(self):
    58        with self.assertRaises(TypeError):
    59            src.calculator.add(None, None)
    60        with self.assertRaises(TypeError):
    61            src.calculator.divide(None, None)
    62        with self.assertRaises(TypeError):
    63            src.calculator.multiply(None, None)
    64        with self.assertRaises(TypeError):
    65            src.calculator.subtract(None, None)
    66
    67
    68# Exceptions seen
    

    the test passes

The calculator raises TypeError when given None as input. What does it do when the input is a boolean, string, tuple, list, set or a dictionary?

test_calculator_raises_type_error_w_strings

RED: make it fail

I add a new test with an assertion from test_what_is_an_assertion to test the add function with strings

64        with self.assertRaises(TypeError):
65            src.calculator.subtract(None, None)
66
67    def test_calculator_with_strings(self):
68        self.assertEqual(src.calculator.add('1', '1'), '2')
69
70
71# Exceptions seen

the terminal shows AssertionError

AssertionError: '11' != '2'

GREEN: make it pass

I change the expectation to match reality

68        self.assertEqual(src.calculator.add('1', '1'), '11')

the test passes

REFACTOR: make it better

  • I add an assertion for the divide function

    67    def test_calculator_with_strings(self):
    68        self.assertEqual(src.calculator.add('1', '1'), '11')
    69        self.assertEqual(src.calculator.divide('1', '1'), '11')
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for /: 'str' and 'str'
    

    I change assertEqual to assertRaises

    68    def test_calculator_with_strings(self):
    69        self.assertEqual(src.calculator.add('1', '1'), '11')
    70        with self.assertRaises(TypeError):
    71            src.calculator.divide('1', '1')
    

    the test passes

  • I try it with the multiply function

    69        with self.assertRaises(TypeError):
    70            src.calculator.divide('1', '1')
    71        src.calculator.multiply('1', '1')
    

    the terminal shows TypeError

    TypeError: can't multiply sequence by non-int of type 'str'
    

    I add assertRaises

    69        with self.assertRaises(TypeError):
    70            src.calculator.divide('1', '1')
    71        with self.assertRaises(TypeError):
    72            src.calculator.multiply('1', '1')
    

    the test passes

  • I add an assertion for the subtract function

    71        with self.assertRaises(TypeError):
    72            src.calculator.multiply('1', '1')
    73        src.calculator.subtract('1', '1')
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for -: 'str' and 'str'
    

    I add assertRaises

    57    def test_calculator_w_strings(self):
    58        with self.assertRaises(TypeError):
    59            src.calculator.add(None, None)
    60        with self.assertRaises(TypeError):
    61            src.calculator.divide(None, None)
    62        with self.assertRaises(TypeError):
    63            src.calculator.multiply('1', '1')
    64        with self.assertRaises(TypeError):
    65            src.calculator.subtract('1', '1')
    

    the test passes

how to test if something is an instance of an object in a program

I want the add function to raise TypeError when it gets a string, the same way the other functions raise TypeError when one of the inputs is a string. I can use the isinstance function which is like the assertIsInstance method from when I tested None, it checks if one thing is an instance or child of a class

  • I change the assertEqual to assertRaises in test_calculator_with_strings in test_calculator.py

    67    def test_calculator_with_strings(self):
    68        with self.assertRaises(TypeError):
    69            src.calculator.add('1', '1')
    70        with self.assertRaises(TypeError):
    71            src.calculator.divide('1', '1')
    

    the terminal shows AssertionError

    AssertionError: TypeError not raised
    
  • I open calculator.py from the src folder in the editor

  • then I add an if statement to the add function in calculator.py

    16def add(first_input, second_input):
    17    if isinstance(first_input, str) or isinstance(second_input, str):
    18        raise TypeError
    19    else:
    20        return first_input + second_input
    

    the test passes

    Note

  • I change the name of the test to be clearer

    67    def test_calculator_raises_type_error_w_strings(self):
    68        with self.assertRaises(TypeError):
    69            src.calculator.add('1', '1')
    70        with self.assertRaises(TypeError):
    71            src.calculator.divide('1', '1')
    72        with self.assertRaises(TypeError):
    73            src.calculator.multiply('1', '1')
    74        with self.assertRaises(TypeError):
    75            src.calculator.subtract('1', '1')
    76
    77
    78# Exceptions seen
    

test_calculator_sends_message_when_input_is_not_a_number

I want the calculator functions to send a message when the input is not a number, not raise TypeError which causes the program to stop. I want the user to be able to try again with different input

RED: make it fail

I change the assertRaises to assertEqual for the add function in test_calculator_raises_type_error_w_none

57    def test_calculator_raises_type_error_w_none(self):
58        self.assertEqual(
59            src.calculator.add(None, None),
60            'Excuse me?! Numbers only! try again...'
61        )
62        with self.assertRaises(TypeError):
63            src.calculator.divide(None, None)
64        with self.assertRaises(TypeError):
65            src.calculator.multiply(None, None)
66        with self.assertRaises(TypeError):
67            src.calculator.subtract(None, None)

the terminal shows TypeError

TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

GREEN: make it pass

I add an exception handler to the else clause of the add function in calculator.py

16def add(first_input, second_input):
17    if isinstance(first_input, str) or isinstance(second_input, str):
18        raise TypeError
19    else:
20        try:
21            return first_input + second_input
22        except TypeError:
23            return 'Excuse me?! Numbers only! try again...'

the test passes

REFACTOR: make it better

  • I want the same thing to happen when the add function gets a string as input. I change the assertRaises to assertEqual for the add function in test_calculator_raises_type_error_w_strings in test_calculator.py

    70    def test_calculator_raises_type_error_w_strings(self):
    71        self.assertEqual(
    72            src.calculator.add('1', '1'),
    73            'Excuse me?! Numbers only! try again...'
    74        )
    75        with self.assertRaises(TypeError):
    76            src.calculator.divide('1', '1')
    

    the terminal shows TypeError

    TypeError
    
  • I change the raise statement to a return statement in the add function in calculator.py

    16def add(first_input, second_input):
    17    if isinstance(first_input, str) or isinstance(second_input, str):
    18        return 'Excuse me?! Numbers only! try again...'
    19    else:
    20        try:
    21            return first_input + second_input
    22        except TypeError:
    23            return 'Excuse me?! Numbers only! try again...'
    

    the test passes

  • I change the assertRaises to assertEqual for the divide function in test_calculator_raises_type_error_w_strings in test_calculator.py

    69    def test_calculator_raises_type_error_w_strings(self):
    70        self.assertEqual(
    71            src.calculator.add('1', '1'),
    72            'Excuse me?! Numbers only! try again...'
    73        )
    74        self.assertEqual(
    75            src.calculator.divide('1', '1'),
    76            'Excuse me?! Numbers only! try again...'
    77        )
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for /: 'str' and 'str'
    
  • I add another except clause to the exception handler in the divide function in calculator.py

     9def divide(first_input, second_input):
    10    try:
    11        return first_input / second_input
    12    except ZeroDivisionError:
    13        return 'undefined: I cannot divide by 0'
    14    except TypeError:
    15        return 'Excuse me?! Numbers only! try again...'
    

    the terminal shows AssertionError

    AssertionError: TypeError not raised
    

    test_calculator_raises_type_error_w_none fails because it expects TypeError when the inputs are not numbers

  • I change the assertRaises to assertEqual for the divide function in test_calculator_raises_type_error_w_none in test_calculator.py

    57def test_calculator_raises_type_error_w_none(self):
    58    self.assertEqual(
    59        src.calculator.add(None, None),
    60        'Excuse me?! Numbers only! try again...'
    61    )
    62    self.assertEqual(
    63        src.calculator.divide(None, None),
    64        'Excuse me?! Numbers only! try again...'
    65    )
    

    the test passes

  • I change the assertRaises to assertEqual for the multiply function in test_calculator_raises_type_error_w_none

    62        self.assertEqual(
    63            src.calculator.divide(None, None),
    64            'Excuse me?! Numbers only! try again...'
    65        )
    66        self.assertEqual(
    67            src.calculator.multiply(None, None),
    68            'Excuse me?! Numbers only! try again...'
    69        )
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'
    
  • I add an exception handler to the multiply function in calculator.py

    5def multiply(first_input, second_input):
    6    try:
    7        return first_input * second_input
    8    except TypeError:
    9        return 'Excuse me?! Numbers only! try again...'
    

    the terminal shows AssertionError

    AssertionError: TypeError not raised
    
  • I change the assertRaises to assertEqual for the multiply function in test_calculator_raises_type_error_w_strings in test_calculator.py

    78        self.assertEqual(
    79            src.calculator.divide('1', '1'),
    80            'Excuse me?! Numbers only! try again...'
    81        )
    82        self.assertEqual(
    83            src.calculator.multiply('1', '1'),
    84            'Excuse me?! Numbers only! try again...'
    85        )
    

    the test passes

  • I change the assertRaises to assertEqual for the subtract function in test_calculator_raises_type_error_w_strings

    84        self.assertEqual(
    85            src.calculator.multiply('1', '1'),
    86            'Excuse me?! Numbers only! try again...'
    87        )
    88        self.assertEqual(
    89            src.calculator.subtract('1', '1'),
    90            'Excuse me?! Numbers only! try again...'
    91        )
    92
    93
    94# Exceptions seen
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for -: 'str' and 'str'
    
  • I add an exception handler to the subtract function in calculator.py

    1def subtract(first_input, second_input):
    2    try:
    3        return first_input - second_input
    4    except TypeError:
    5        return 'Excuse me?! Numbers only! try again...'
    

    the terminal shows AssertionError

    AssertionError: TypeError not raised
    
  • I change the assertRaises to assertEqual for the subtract function in test_calculator_raises_type_error_w_none in test_calculator.py

    66        self.assertEqual(
    67            src.calculator.multiply(None, None),
    68            'Excuse me?! Numbers only! try again...'
    69        )
    70        self.assertEqual(
    71            src.calculator.subtract(None, None),
    72            'Excuse me?! Numbers only! try again...'
    73        )
    74
    75
    76    def test_calculator_raises_type_error_w_strings(self):
    

    the test passes

    That was a lot of doing the same thing over and over again.

    • test_calculator_raises_type_error_w_none and test_calculator_raises_type_error_w_strings both look the same

    • the calculator no longer raises TypeError when any of the inputs are NOT a number

  • I remove the name of test_calculator_raises_type_error_w_strings to make its assertions part of test_calculator_raises_type_error_w_none

    70        self.assertEqual(
    71            src.calculator.subtract(None, None),
    72            'Excuse me?! Numbers only! try again...'
    73        )
    74        self.assertEqual(
    75            src.calculator.add('1', '1'),
    76            'Excuse me?! Numbers only! try again...'
    77        )
    78        self.assertEqual(
    79            src.calculator.divide('1', '1'),
    80            'Excuse me?! Numbers only! try again...'
    81        )
    82        self.assertEqual(
    83            src.calculator.multiply('1', '1'),
    84            'Excuse me?! Numbers only! try again...'
    85        )
    86        self.assertEqual(
    87            src.calculator.subtract('1', '1'),
    88            'Excuse me?! Numbers only! try again...'
    89        )
    90
    91
    92# Exceptions seen
    
  • I change the name from test_calculator_raises_type_error_w_none to test_calculator_sends_message_when_input_is_not_a_number to be clearer

    57    def test_calculator_sends_message_when_input_is_not_a_number(self):
    58        self.assertEqual(
    

    the tests are still green

  • I have the same error message 8 times in this test. I use a variable to make it better

    57    def test_calculator_sends_message_when_input_is_not_a_number(self):
    58        error_message = 'Excuse me?! Numbers only! try again...'
    59
    60        self.assertEqual(
    61            src.calculator.add(None, None),
    62            error_message
    63        )
    64        self.assertEqual(
    65            src.calculator.divide(None, None),
    66            error_message
    67        )
    68        self.assertEqual(
    69            src.calculator.multiply(None, None),
    70            error_message
    71        )
    72        self.assertEqual(
    73            src.calculator.subtract(None, None),
    74            error_message
    75        )
    76        self.assertEqual(
    77            src.calculator.add('1', '1'),
    78            error_message
    79        )
    80        self.assertEqual(
    81            src.calculator.divide('1', '1'),
    82            error_message
    83        )
    84        self.assertEqual(
    85            src.calculator.multiply('1', '1'),
    86            error_message
    87        )
    88        self.assertEqual(
    89            src.calculator.subtract('1', '1'),
    90            error_message
    91        )
    

    still green. All these assertions look the same, they check that the calculator functions return an error message when they get input that is NOT a number

    self.assertEqual(
        src.calculator.function(NOT_A_NUMBER, ALSO_NOT_A_NUMBER),
        error_message
    )
    

    there has to be a better way to test the calculator with inputs that are NOT numbers


how to make a decorator function

All the functions in the calculator program have the same exception handler

try:
    something
except TypeError:
    return 'Excuse me?! Numbers only! try again...'

the divide function is different because it has another except clause

except ZeroDivisionError:
    return 'undefined: I cannot divide by 0'

the other part that is different for all the functions are the calculations

return first_input - second_input
return first_input * second_input
return first_input / second_input
return first_input + second_input

what is a decorator function?

A decorator or wrapper function takes another function as input and returns a function. I can use it to remove the exception handler that is the same in all of the calculator functions

  • I add a new function add the top of calculator.py

     1def only_takes_numbers(function):
     2    def wrapper(first_input, second_input):
     3        try:
     4            return function(first_input, second_input)
     5        except TypeError:
     6            return 'Excuse me?! Numbers only! try again...'
     7    return wrapper
     8
     9
    10def subtract(first_input, second_input):
    

    The only_takes_numbers function takes a function as input

    • it tries to return the result of the function working on the two inputs

    • if TypeError is raised it returns the error message

  • I use it to wrap the subtract function

    10@only_takes_numbers
    11def subtract(first_input, second_input):
    12    try:
    

    the test is still green

  • I remove the parts that are also in the only_takes_numbers function

    15@only_takes_numbers
    16def subtract(first_input, second_input):
    17    return first_input - second_input
    18
    19
    20def multiply(first_input, second_input):
    

    still green

  • I do the same thing with the multiply function

    15@only_takes_numbers
    16def multiply(first_input, second_input):
    17    try:
    

    the terminal shows green

  • I remove the parts that are also in the only_takes_numbers function

    15@only_takes_numbers
    16def multiply(first_input, second_input):
    17    return first_input * second_input
    18
    19
    20def divide(first_input, second_input):
    

    the tests are still passing

  • on to the divide function

    20@only_takes_numbers
    21def divide(first_input, second_input):
    22    try:
    

    still green

  • I remove the except clause for TypeError

    20@only_takes_numbers
    21def divide(first_input, second_input):
    22    try:
    23        return first_input / second_input
    24    except ZeroDivisionError:
    25        return 'undefined: I cannot divide by 0'
    26
    27
    28def add(first_number, second_input):
    

    all the tests are still green

  • one more to go, I wrap the add function with the only_takes_numbers function

    28@only_takes_numbers
    29def add(first_input, second_input):
    30    if isinstance(first_input, str) or isinstance(second_input, str):
    

    the test is still passing

  • I remove the exception handler from the else clause

    28@only_takes_numbers
    29def add(first_input, second_input):
    30    if isinstance(first_input, str) or isinstance(second_input, str):
    31        return 'Excuse me?! Numbers only! try again...'
    32    else:
    33        return first_input + second_input
    

    green! Lovely!

  • I can make a function for the condition in the if statement in the add function in calculator.py

    28def is_string(something):
    29    return isinstance(something, str)
    30
    31
    32@only_takes_numbers
    33def add(first_input, second_input):
    34    if is_string(first_input) or is_string(second_input):
    35        return 'Excuse me?! Numbers only! try again...'
    36    else:
    37        return first_input + second_input
    

    the test is still green

    • This removes the duplication of str in the call to the isinstance function

      isinstance(first_input, str)
      isinstance(second_input, str):
      
    • it also adds 2 lines of code to remove 6 characters. WOW!

  • I can make a function for the whole if statement in the add function

    28def one_input_is_a_string(first_input, second_input):
    29    return isinstance(first_input, str) or isinstance(second_input, str)
    30
    31
    32@only_takes_numbers
    33def add(first_input, second_input):
    34    if one_input_is_a_string(first_input, second_input):
    35        return 'Excuse me?! Numbers only! try again...'
    36    else:
    37        return first_input + second_input
    

    the test is still green.

  • I can also make a decorator function for the if statement to practice making a decorator function

     1def reject_strings(function):
     2    def wrapper(first_input, second_input):
     3        if isinstance(first_input, str) or isinstance(second_input, str):
     4            return 'Excuse me?! Numbers only! try again...'
     5        else:
     6            return function(first_input, second_input)
     7    return wrapper
     8
     9
    10def only_takes_numbers(function):
    
  • then use it to wrap the add function

    37@reject_strings
    38@only_takes_numbers
    39def add(first_input, second_input):
    

    the test is still green

  • I remove the if statement from the add function

    37@reject_strings
    38@only_takes_numbers
    39def add(first_input, second_input):
    40    return first_input + second_input
    

    the test is still green

  • the reject_strings and only_takes_numbers functions have parts that are the same

    def wrapper(first_input, second_input):
        ...
        return 'Excuse me?! Numbers only! try again...'
        ...
        return function(first_input, second_input)
    return wrapper
    

    I make a new function that has the if statement from reject_strings and the exception handler from only_takes_numbers

     1def only_takes_numbers_and_rejects_strings(function):
     2    def wrapper(first_input, second_input):
     3        if isinstance(first_input, str) or isinstance(second_input, str):
     4            return 'Excuse me?! Numbers only! try again...'
     5        else:
     6            try:
     7                return function(first_input, second_input)
     8            except TypeError:
     9                return 'Excuse me?! Numbers only! try again...'
    10    return wrapper
    11
    12
    13def reject_strings(function):
    
  • I use the new function to wrap the add function

    49@only_takes_numbers_and_rejects_strings
    50@reject_strings
    51@only_takes_numbers
    52def add(first_input, second_input):
    53    return first_input + second_input
    

    the test is still green

  • I remove the other wrappers from the add function

    49@only_takes_numbers_and_rejects_strings
    50def add(first_input, second_input):
    51    return first_input + second_input
    

    still green

  • I wrap the other functions with only_takes_numbers_and_reject_strings

    31@only_takes_numbers_and_rejects_strings
    32@only_takes_numbers
    33def subtract(first_input, second_input):
    34    return first_input - second_input
    35
    36
    37@only_takes_numbers_and_rejects_strings
    38@only_takes_numbers
    39def multiply(first_input, second_input):
    40    return first_input * second_input
    41
    42
    43@only_takes_numbers_and_rejects_strings
    44@only_takes_numbers
    45def divide(first_input, second_input):
    46    try:
    47        return first_input / second_input
    48    except ZeroDivisionError:
    49        return 'undefined: I cannot divide by 0'
    50
    51
    52@only_takes_numbers_and_rejects_strings
    53def add(first_input, second_input):
    

    the terminal shows green

  • I remove only_takes_numbers from each function

    31@only_takes_numbers_and_rejects_strings
    32def subtract(first_input, second_input):
    33    return first_input - second_input
    34
    35
    36@only_takes_numbers_and_rejects_strings
    37def multiply(first_input, second_input):
    38    return first_input * second_input
    39
    40
    41@only_takes_numbers_and_rejects_strings
    42def divide(first_input, second_input):
    43    try:
    44        return first_input / second_input
    45    except ZeroDivisionError:
    46        return 'undefined: I cannot divide by 0'
    47
    48
    49@only_takes_numbers_and_rejects_strings
    50def add(first_input, second_input):
    51    return first_input + second_input
    

    the test is still green

  • I remove the reject_strings and only_takes_numbers functions

     1def only_takes_numbers_and_rejects_strings(function):
     2    def wrapper(first_input, second_input):
     3        if isinstance(first_input, str) or isinstance(second_input, str):
     4            return 'Excuse me?! Numbers only! try again...'
     5        else:
     6            try:
     7                return function(first_input, second_input)
     8            except TypeError:
     9                return 'Excuse me?! Numbers only! try again...'
    10    return wrapper
    11
    12
    13@only_takes_numbers_and_rejects_strings
    14def subtract(first_input, second_input):
    15    return first_input - second_input
    16
    17
    18@only_takes_numbers_and_rejects_strings
    19def multiply(first_input, second_input):
    20    return first_input * second_input
    21
    22
    23@only_takes_numbers_and_rejects_strings
    24def divide(first_input, second_input):
    25    try:
    26        return first_input / second_input
    27    except ZeroDivisionError:
    28        return 'undefined: I cannot divide by 0'
    29
    30
    31@only_takes_numbers_and_rejects_strings
    32def add(first_input, second_input):
    33    return first_input + second_input
    
  • I change the name of the new decorator function to make it easier

     1def only_takes_numbers(function):
     2    def wrapper(first_input, second_input):
     3        if isinstance(first_input, str) or isinstance(second_input, str):
     4            return 'Excuse me?! Numbers only! try again...'
     5        else:
     6            try:
     7                return function(first_input, second_input)
     8            except TypeError:
     9                return 'Excuse me?! Numbers only! try again...'
    10    return wrapper
    11
    12
    13@only_takes_numbers
    14def subtract(first_input, second_input):
    15    return first_input - second_input
    16
    17
    18@only_takes_numbers
    19def multiply(first_input, second_input):
    20    return first_input * second_input
    21
    22
    23@only_takes_numbers
    24def divide(first_input, second_input):
    25    try:
    26        return first_input / second_input
    27    except ZeroDivisionError:
    28        return 'undefined: I cannot divide by 0'
    29
    30
    31@only_takes_numbers
    32def add(first_input, second_input):
    33    return first_input + second_input
    

    still green

  • There is also duplication of the error message. I add a variable to remove it

     1def only_takes_numbers(function):
     2    def wrapper(first_input, second_input):
     3        error_message = 'Excuse me?! Numbers only! try again...'
     4
     5        if isinstance(first_input, str) or isinstance(second_input, str):
     6            return error_message
     7        else:
     8            try:
     9                return function(first_input, second_input)
    10            except TypeError:
    11                return error_message
    12    return wrapper
    

    and all the tests are still passing. The world is my oyster!


close the project

  • I close test_calculator.py and calculator.py in the editor

  • I click in the terminal and exit the tests with ctrl+c on the keyboard, the terminal shows

    (.venv) .../pumping_python/calculator
    
  • I deactivate the virtual environment

    deactivate
    

    the terminal goes back to the command line, (.venv) is no longer on the left side

    .../pumping_python/calculator
    
  • I change directory to the parent of calculator

    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory


code from the chapter

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


what is next?

you know

Would you like to test Lists?


rate pumping python

If this has been a 7 star experience for you, please leave a 5 star review. It helps other people get into the book too