how to make a calculator 8

I can use the __getattribute__ method that comes with every Python object in the calculator tests


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 6 items
    
    tests/test_calculator.py ......                                      [100%]
    
    ============================ 6 passed in X.YZs =============================
    
  • I hold ctrl on the keyboard and click on tests/test_calculator.py to open it in the editor


test_calculator_w_getattribute


RED: make it fail


  • I add a test with a dictionary

    40            'multiplication': {
    41                'function': src.calculator.multiply,
    42                'expectation': (
    43                    self.random_first_number*self.random_second_number
    44                ),
    45            },
    46        }
    47
    48    def test_calculator_w_getattribute(self):
    49        calculator_tests = {
    50            'add': (
    51                self.random_first_number+self.random_second_number
    52            )
    53        }
    54
    55    def test_calculator_functions(self):
    
  • I add an assertion

    48    def test_calculator_w_getattribute(self):
    49        calculator_tests = {
    50            'add': (
    51                self.random_first_number+self.random_second_number
    52            )
    53        }
    54        self.assertEqual(
    55            src.calculator.__getattribute__('add'),
    56            calculator_tests['add']
    57        )
    58
    59    def test_calculator_functions(self):
    

    the terminal shows AssertionError

    AssertionError: <function only_takes_numbers.<locals>.wrapper at 0xffff1a2bc3d4> != ABCD.EFGHIJKLMN
    

    I have to call the function after __getattribute__ returns it


GREEN: make it pass


  • I add parentheses after to make it a call

    54        self.assertEqual(
    55            src.calculator.__getattribute__('add')(),
    56            calculator_tests['add']
    57        )
    

    the terminal shows TypeError

    TypeError: only_takes_numbers.<locals>.wrapper() missing 2 required positional arguments: 'first_input' and 'second_input'
    

    I have to give the inputs to the function

  • I add inputs in the parentheses

    48    def test_calculator_w_getattribute(self):
    49        calculator_tests = {
    50            'add': (
    51                self.random_first_number+self.random_second_number
    52            )
    53        }
    54        self.assertEqual(
    55            src.calculator.__getattribute__('add')(
    56                self.random_first_number,
    57                self.random_second_number
    58            ),
    59            calculator_tests['add']
    60        )
    61
    62    def test_calculator_functions(self):
    

    the test passes

    • src.calculator.__getattribute__('add') returns src.calculator.add

    • calculator_tests['add'] returns self.random_first_number + self.random_second_number

    • object.__getattribute__(something) checks if something is in object and returns it


GREEN: make it pass


  • I add another operation to the calculator_tests dictionary

  • I add an assertion

    57        self.assertEqual(
    58            src.calculator.__getattribute__('add')(
    59                self.random_first_number,
    60                self.random_second_number
    61            ),
    62            calculator_tests['add']
    63        )
    64        self.assertEqual(
    65            src.calculator.__getattribute__('subtract')(
    66                self.random_first_number,
    67                self.random_second_number
    68            ),
    69            calculator_tests['add']
    70        )
    71
    72    def test_calculator_functions(self):
    

    the terminal shows AssertionError

    AssertionError: OPQ.RSTUVWXYZABCDE != FGH.IJKLMNOPQRSTUV
    
  • I change the expectation

    64            self.assertEqual(
    65                src.calculator.__getattribute__('subtract')(
    66                    self.random_first_number,
    67                    self.random_second_number
    68                ),
    69                calculator_tests['subtract']
    70            )
    

    the test passes

  • I add a for loop

    64        self.assertEqual(
    65            src.calculator.__getattribute__('subtract')(
    66                self.random_first_number,
    67                self.random_second_number
    68            ),
    69            calculator_tests['subtract']
    70        )
    71
    72        for operation in calculator_tests:
    73            with self.subTest(operation=operation):
    74                self.assertEqual(
    75                    src.calculator.__getattribute__(operation)(
    76                        self.random_first_number,
    77                        self.random_second_number
    78                    ),
    79                    1
    80                )
    81
    82    def test_calculator_functions(self):
    

    the terminal shows AssertionError

    SUBFAILED(operation='add') tests/test_calculator.py::TestCalculator::test_calculator_w_getattribute - AssertionError: WXY.ZABCDEFGHIJKL != 1
    SUBFAILED(operation='subtract') tests/test_calculator.py::TestCalculator::test_calculator_w_getattribute - AssertionError: MNO.PQRSTUVWXYZAB != 1
    
  • I change the expectation

    72        for operation in calculator_tests:
    73            with self.subTest(operation=operation):
    74                self.assertEqual(
    75                    src.calculator.__getattribute__(operation)(
    76                        self.random_first_number,
    77                        self.random_second_number
    78                    ),
    79                    calculator_tests[operation]
    80                )
    

    the test passes

  • I remove the other assertion

    48    def test_calculator_w_getattribute(self):
    49        calculator_tests = {
    50            'add': (
    51                self.random_first_number+self.random_second_number
    52            ),
    53            'subtract': (
    54                self.random_first_number-self.random_second_number
    55            )
    56        }
    57
    58        for operation in calculator_tests:
    59            with self.subTest(operation=operation):
    60                self.assertEqual(
    61                    src.calculator.__getattribute__(operation)(
    62                        self.random_first_number,
    63                        self.random_second_number
    64                    ),
    65                    calculator_tests[operation]
    66                )
    67
    68    def test_calculator_functions(self):
    

    the test is still green

  • I add a key for the next operation

    48    def test_calculator_w_getattribute(self):
    49        calculator_tests = {
    50            'add': (
    51                self.random_first_number+self.random_second_number
    52            ),
    53            'subtract': (
    54                self.random_first_number-self.random_second_number
    55            ),
    56            'division': self.division_result,
    57        }
    58
    59        for operation in calculator_tests:
    

    the terminal shows AttributeError

    AttributeError: module 'src.calculator' has no attribute 'division'
    
  • I used a name that is not in calculator.py. I change the name

    the test passes

  • I add another operation

    49        calculator_tests = {
    50            'add': (
    51                self.random_first_number+self.random_second_number
    52            ),
    53            'subtract': (
    54                self.random_first_number-self.random_second_number
    55            ),
    56            'divide': self.division_result,
    57            'multiplication': (
    58                self.random_first_number*self.random_second_number
    59            ),
    60        }
    

    the terminal shows AttributeError

    AttributeError: module 'src.calculator' has no attribute 'multiplication'
    
  • I change the name

    49        calculator_tests = {
    50            'add': (
    51                self.random_first_number+self.random_second_number
    52            ),
    53            'subtract': (
    54                self.random_first_number-self.random_second_number
    55            ),
    56            'divide': self.division_result,
    57            'multiply': (
    58                self.random_first_number*self.random_second_number
    59            ),
    60        }
    

    the test passes


  • I add the new dictionary to the setUp method

    40            'multiplication': {
    41                'function': src.calculator.multiply,
    42                'expectation': (
    43                    self.random_first_number*self.random_second_number
    44                ),
    45            },
    46        }
    47        self.calculator_tests_a = {
    48            'add': (
    49                self.random_first_number+self.random_second_number
    50            ),
    51            'subtract': (
    52                self.random_first_number-self.random_second_number
    53            ),
    54            'divide': self.division_result,
    55            'multiply': (
    56                self.random_first_number*self.random_second_number
    57            ),
    58        }
    59
    60    def test_calculator_w_getattribute(self):
    
  • I use the new class attribute in test_calculator_w_getattribute

    60    def test_calculator_w_getattribute(self):
    61        # calculator_tests = {
    62        #     'add': (
    63        #         self.random_first_number+self.random_second_number
    64        #     ),
    65        #     'subtract': (
    66        #         self.random_first_number-self.random_second_number
    67        #     ),
    68        #     'divide': self.division_result,
    69        #     'multiply': (
    70        #         self.random_first_number*self.random_second_number
    71        #     ),
    72        # }
    73        calculator_tests = self.calculator_tests_a
    74
    75        for operation in calculator_tests:
    

    the test is still green

  • I use self.calculator_tests_a in the for loop

    75        # for operation in calculator_tests:
    76        for operation in self.calculator_tests_a:
    77            with self.subTest(operation=operation):
    78                self.assertEqual(
    

    still green

  • I use it in the expectation of the assertion

    75        # for operation in calculator_tests:
    76        for operation in self.calculator_tests_a:
    77            with self.subTest(operation=operation):
    78                self.assertEqual(
    79                    src.calculator.__getattribute__(operation)(
    80                        self.random_first_number,
    81                        self.random_second_number
    82                    ),
    83                    # calculator_tests[operation]
    84                    self.calculator_tests_a[operation]
    85                )
    

    green

  • I remove the commented lines and the calculator_tests variable

    60    def test_calculator_w_getattribute(self):
    61        for operation in self.calculator_tests_a:
    62            with self.subTest(operation=operation):
    63                self.assertEqual(
    64                    src.calculator.__getattribute__(operation)(
    65                        self.random_first_number,
    66                        self.random_second_number
    67                    ),
    68                    self.calculator_tests_a[operation]
    69                )
    70
    71    def test_calculator_functions(self):
    

    still green

  • I no longer need test_calculator_functions since it is covered by test_calculator_w_getattribute, they have the same tests. I remove test_calculator_functions

    60    def test_calculator_w_getattribute(self):
    61        for operation in self.calculator_tests_a:
    62            with self.subTest(operation=operation):
    63                self.assertEqual(
    64                    src.calculator.__getattribute__(operation)(
    65                        self.random_first_number,
    66                        self.random_second_number
    67                    ),
    68                    self.calculator_tests_a[operation]
    69                )
    70
    71    def test_calculator_sends_message_when_input_is_not_a_number(self):
    

    the tests are still green

  • I change the name of test_calculator_w_getattribute to test_calculator_functions

    55            'multiply': (
    56                self.random_first_number*self.random_second_number
    57            ),
    58        }
    59
    60    def test_calculator_functions(self):
    61        for operation in self.calculator_tests_a:
    62            with self.subTest(operation=operation):
    63                self.assertEqual(
    64                    src.calculator.__getattribute__(operation)(
    65                        self.random_first_number,
    66                        self.random_second_number
    67                    ),
    68                    self.calculator_tests_a[operation]
    69                )
    70
    71    def test_calculator_sends_message_when_input_is_not_a_number(self):
    

    still green


  • I use the new class attribute in test_calculator_sends_message_when_input_is_not_a_number in a new for loop

    84                for operation in self.calculator_tests:
    85                    self.assertEqual(
    86                        self.calculator_tests[operation]['function'](
    87                            data, a_random_number()
    88                        ),
    89                        error_message
    90                    )
    91                for operation in self.calculator_tests_a:
    92                    self.assertEqual(
    93                        src.calculator.__getattribute__(operation)(
    94                            data, a_random_number()
    95                        ),
    96                        'BOOM!!!'
    97                    )
    98
    99    def test_calculator_w_list_items(self):
    

    the terminal shows AssertionError for 13 tests

    AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'
    
  • I change the expectation to the error message

    91                for operation in self.calculator_tests_a:
    92                    self.assertEqual(
    93                        src.calculator.__getattribute__(operation)(
    94                            data, a_random_number()
    95                        ),
    96                        error_message
    97                    )
    

    the test passes

  • I remove the other for loop

    74        for data in (
    75            None,
    76            True, False,
    77            str(), 'text',
    78            tuple(), (0, 1, 2, 'n'),
    79            list(), [0, 1, 2, 'n'],
    80            set(), {0, 1, 2, 'n'},
    81            dict(), {'key': 'value'},
    82        ):
    83            with self.subTest(i=data):
    84                for operation in self.calculator_tests_a:
    85                    self.assertEqual(
    86                        src.calculator.__getattribute__(operation)(
    87                            data, a_random_number()
    88                        ),
    89                        error_message
    90                    )
    91
    92    def test_calculator_w_list_items(self):
    

    the test is still green

  • I forgot to remove the error_message variable. I remove it because I do not need it anymore

    71    def test_calculator_sends_message_when_input_is_not_a_number(self):
    72        for data in (
    73            None,
    74            True, False,
    75            str(), 'text',
    76            tuple(), (0, 1, 2, 'n'),
    77            list(), [0, 1, 2, 'n'],
    78            set(), {0, 1, 2, 'n'},
    79            dict(), {'key': 'value'},
    80        ):
    81            with self.subTest(i=data):
    82                for operation in self.calculator_tests_a:
    83                    self.assertEqual(
    84                        src.calculator.__getattribute__(operation)(
    85                            data, a_random_number()
    86                        ),
    87                        'brmph?! Numbers only. Try again...'
    88                    )
    89
    90    def test_calculator_w_list_items(self):
    

    still green


  • I use the __getattribute__ method with self.calculator_tests_a in a new for loop in test_calculator_w_list_items

    115        for operation in self.calculator_tests:
    116            with self.subTest(operation=operation):
    117                self.assertEqual(
    118                    self.calculator_tests[operation]['function'](
    119                        *two_numbers
    120                    ),
    121                    self.calculator_tests[operation]['expectation']
    122                )
    123        for operation in self.calculator_tests_a:
    124            with self.subTest(operation=operation):
    125                self.assertEqual(
    126                    src.calculator.__getattribute__(operation)(
    127                        *two_numbers
    128                    ),
    129                    1
    130                )
    131
    132    def test_calculator_w_dictionary_items(self):
    

    the terminal shows AssertionError for the 4 operations

    AssertionError: CDEF.GHIJKLMNOPQRS != 1
    
  • I change the expectation

    123        for operation in self.calculator_tests_a:
    124            with self.subTest(operation=operation):
    125                self.assertEqual(
    126                    src.calculator.__getattribute__(operation)(
    127                        *two_numbers
    128                    ),
    129                    self.calculator_tests_a[operation]
    130                )
    131
    132    def test_calculator_w_dictionary_items(self):
    

    the test passes

  • I remove the other for loop

    110        self.assertEqual(
    111            src.calculator.subtract(two_numbers[-2], two_numbers[0]),
    112            self.random_first_number-self.random_first_number
    113        )
    114
    115        for operation in self.calculator_tests_a:
    116            with self.subTest(operation=operation):
    117                self.assertEqual(
    118                    src.calculator.__getattribute__(operation)(
    119                        *two_numbers
    120                    ),
    121                    self.calculator_tests_a[operation]
    122                )
    123
    124    def test_calculator_w_dictionary_items(self):
    

    the test is still green


  • I do the same thing in test_calculator_w_dictionary_items

    159        for operation in self.calculator_tests:
    160            with self.subTest(operation=operation):
    161                self.assertEqual(
    162                    self.calculator_tests[operation]['function'](
    163                        **two_numbers
    164                    ),
    165                    self.calculator_tests[operation]['expectation']
    166                )
    167        for operation in self.calculator_tests_a:
    168            with self.subTest(operation=operation):
    169                self.assertEqual(
    170                    src.calculator.__getattribute__(operation)(
    171                        **two_numbers
    172                    ),
    173                    1
    174                )
    175
    176    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    

    the terminal shows AssertionError for the 4 operations

    AssertionError: TUVW.XYZABCDEFGHI != 1
    
  • I change the expectation

    167        for operation in self.calculator_tests_a:
    168            with self.subTest(operation=operation):
    169                self.assertEqual(
    170                    src.calculator.__getattribute__(operation)(
    171                        **two_numbers
    172                    ),
    173                    self.calculator_tests_a[operation]
    174                )
    175
    176    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    

    the test passes

  • I remove the for loop

    151        self.assertEqual(
    152            src.calculator.subtract(
    153                two_numbers['first_input'],
    154                two_numbers['first_input']
    155            ),
    156            self.random_first_number-self.random_first_number
    157        )
    158
    159        for operation in self.calculator_tests_a:
    160            with self.subTest(operation=operation):
    161                self.assertEqual(
    162                    src.calculator.__getattribute__(operation)(
    163                        **two_numbers
    164                    ),
    165                    self.calculator_tests_a[operation]
    166                )
    167
    168    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    

    the test is still green


  • I use the __getattribute__ method with self.calculator_tests_a in a new for loop in test_calculator_raises_type_error_when_given_more_than_two_inputs

    168    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    169        for operation in self.calculator_tests:
    170            with (
    171                self.subTest(operation=operation),
    172                self.assertRaises(TypeError),
    173            ):
    174                self.calculator_tests[operation]['function'](
    175                    *[0, 1, 2]
    176                )
    177        for operation in self.calculator_tests_a:
    178            with (
    179                self.subTest(operation=operation),
    180                self.assertRaises(ZeroDivisionError),
    181            ):
    182                src.calculator.__getattribute__(operation)(
    183                    *[0, 1, 2]
    184                )
    185
    186
    187# Exceptions seen
    

    the terminal shows TypeError for each operation

    TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were given
    

    good, the tests work

  • I change the Exception to match

    177        for operation in self.calculator_tests_a:
    178            with (
    179                self.subTest(operation=operation),
    180                self.assertRaises(TypeError),
    181            ):
    182                src.calculator.__getattribute__(operation)(
    183                    *[0, 1, 2]
    184                )
    185
    186
    187# Exceptions seen
    

    the test passes

  • I remove the other for loop

    168    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    169        for operation in self.calculator_tests_a:
    170            with (
    171                self.subTest(operation=operation),
    172                self.assertRaises(TypeError),
    173            ):
    174                src.calculator.__getattribute__(operation)(
    175                    *[0, 1, 2]
    176                )
    177
    178
    179# Exceptions seen
    

    the test is still green


  • I remove self.calculator_tests from the setUp method because it is no longer used

    12    def setUp(self):
    13        self.history = {}
    14        self.random_first_number = a_random_number()
    15        self.random_second_number = a_random_number()
    16        try:
    17            self.division_result = (
    18                self.random_first_number / self.random_second_number
    19            )
    20        except ZeroDivisionError:
    21            self.division_result = 'brmph?! cannot divide by 0. Try again...'
    22
    23        self.calculator_tests_a = {
    24            'add': (
    25                self.random_first_number+self.random_second_number
    26            ),
    27            'subtract': (
    28                self.random_first_number-self.random_second_number
    29            ),
    30            'divide': self.division_result,
    31            'multiply': (
    32                self.random_first_number*self.random_second_number
    33            ),
    34        }
    35
    36    def test_calculator_functions(self):
    

    the tests are still green

  • I use the Rename Symbol feature to change self.calculator_tests_a to self.calculator_tests

    12    def setUp(self):
    13        self.history = {}
    14        self.random_first_number = a_random_number()
    15        self.random_second_number = a_random_number()
    16        try:
    17            self.division_result = (
    18                self.random_first_number / self.random_second_number
    19            )
    20        except ZeroDivisionError:
    21            self.division_result = 'brmph?! cannot divide by 0. Try again...'
    22
    23        self.calculator_tests = {
    24            'add': (
    25                self.random_first_number+self.random_second_number
    26            ),
    27            'subtract': (
    28                self.random_first_number-self.random_second_number
    29            ),
    30            'divide': self.division_result,
    31            'multiply': (
    32                self.random_first_number*self.random_second_number
    33            ),
    34        }
    35
    36    def test_calculator_functions(self):
    
    36    def test_calculator_functions(self):
    37        for operation in self.calculator_tests:
    38            with self.subTest(operation=operation):
    39                self.assertEqual(
    40                    src.calculator.__getattribute__(operation)(
    41                        self.random_first_number,
    42                        self.random_second_number
    43                    ),
    44                    self.calculator_tests[operation]
    45                )
    
    47    def test_calculator_sends_message_when_input_is_not_a_number(self):
    48        for data in (
    49            None,
    50            True, False,
    51            str(), 'text',
    52            tuple(), (0, 1, 2, 'n'),
    53            list(), [0, 1, 2, 'n'],
    54            set(), {0, 1, 2, 'n'},
    55            dict(), {'key': 'value'},
    56        ):
    57            with self.subTest(i=data):
    58                for operation in self.calculator_tests:
    59                    self.assertEqual(
    60                        src.calculator.__getattribute__(operation)(
    61                            data, a_random_number()
    62                        ),
    63                        'brmph?! Numbers only. Try again...'
    64                    )
    65
    66    def test_calculator_w_list_items(self):
    
     91        for operation in self.calculator_tests:
     92            with self.subTest(operation=operation):
     93                self.assertEqual(
     94                    src.calculator.__getattribute__(operation)(
     95                        *two_numbers
     96                    ),
     97                    self.calculator_tests[operation]
     98                )
     99
    100    def test_calculator_w_dictionary_items(self):
    
    135        for operation in self.calculator_tests:
    136            with self.subTest(operation=operation):
    137                self.assertEqual(
    138                    src.calculator.__getattribute__(operation)(
    139                        **two_numbers
    140                    ),
    141                    self.calculator_tests[operation]
    142                )
    143
    144    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    
    144    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    145        for operation in self.calculator_tests:
    146            with (
    147                self.subTest(operation=operation),
    148                self.assertRaises(TypeError),
    149            ):
    150                src.calculator.__getattribute__(operation)(
    151                    *[0, 1, 2]
    152                )
    153
    154
    155# Exceptions seen
    

all the tests are passing and I feel like magic


test calculator tests again

I want to write the solution that will make all the tests in test_calculator.py pass without looking at them


RED: make it fail


  • I close test_calculator.py in the editor

  • I delete all the text in calculator.py, the terminal shows 3 passing tests and AttributeError for 23 failing tests, I start with the last one

    AttributeError: module 'src.calculator' has no attribute 'add'
    
  • I add the name to calculator.py

    the terminal shows NameError

    NameError: name 'add' is not defined
    

GREEN: make it pass


  • I point add to None to define it

    1add = None
    

    the terminal shows TypeError

    TypeError: 'NoneType' object is not callable
    
  • I change it to a function

    1def add():
    2    return None
    

    the terminal shows TypeError

    TypeError: add() takes 0 positional arguments but 2 were given
    
  • I add a name to the parentheses

    1def add(x):
    2    return None
    

    the terminal shows TypeError

    TypeError: add() takes 1 positional argument but 2 were given
    
  • I add a second name to the parentheses

    1def add(x, y):
    2    return None
    

    the terminal shows AssertionError

    AssertionError: None != TUV.WXYZABCDEFGHIJ
    
  • I change the return statement

    1def add(x, y):
    2    return x + y
    

    the terminal shows AttributeError

    AttributeError: module 'src.calculator' has no attribute 'divide'
    
  • I add a function

    1def add(x, y):
    2    return x + y
    3
    4
    5def divide(x, y):
    6    return x / y
    

    the terminal shows AttributeError

    AttributeError: module 'src.calculator' has no attribute 'multiply'
    
  • I add a function

     5def divide(x, y):
     6    return x / y
     7
     8
     9def multiply(x, y):
    10    return x * y
    

    the terminal shows AttributeError

    AttributeError: module 'src.calculator' has no attribute 'subtract'
    
  • I add a function for it

     9def multiply(x, y):
    10    return x * y
    11
    12
    13def subtract(x, y):
    14    return x - y
    

    the terminal shows AssertionError

    TypeError: multiply() got an unexpected keyword argument 'first_input'
    
  • I add a name to the multiply function

     9def multiply(x, y, first_input):
    10    return x * y
    

    the terminal shows TypeError

    TypeError: multiply() missing 1 required positional argument: 'first_input'
    
  • I add a default argument to make it a choice

     9def multiply(x, y, first_input=None):
    10    return x * y
    

    the terminal shows TypeError

    TypeError: multiply() got an unexpected keyword argument 'second_input'
    
  • I add second_input with a default value to the function

     9def multiply(x, y, first_input=None, second_input=None):
    10    return x * y
    

    the terminal shows TypeError

    TypeError: multiply() missing 2 required positional arguments: 'x' and 'y'
    
  • my solution makes no sense, I remove x and y from the parentheses

     9def multiply(first_input=None, second_input=None):
    10    return x * y
    

    the terminal shows NameError

    NameError: name 'x' is not defined
    
  • I change the names in the return statement

     9def multiply(first_input=None, second_input=None):
    10    return first_input * second_input
    

    the terminal shows TypeError

    TypeError: divide() got an unexpected keyword argument 'first_input'
    
  • I use Rename Symbol to change the name

    5def divide(first_input, y):
    6    return first_input / y
    

    the terminal shows TypeError

    TypeError: divide() got an unexpected keyword argument 'second_input'
    
  • I do the same thing to y

    5def divide(first_input, second_input):
    6    return first_input / second_input
    

    the terminal shows TypeError

    TypeError: subtract() got an unexpected keyword argument 'first_input'
    

    same problem, same solution

  • I change the names

    13def subtract(first_input, second_input):
    14    return first_input - second_input
    

    the terminal shows TypeError

    TypeError: add() got an unexpected keyword argument 'first_input'
    
  • I change the names in the add function

    1def add(first_input, second_input):
    2    return first_input + second_input
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for +: 'dict' and 'float'
    
  • I add an if statement

    1def add(first_input, second_input):
    2    if isinstance(first_input, dict):
    3        return None
    4    return first_input + second_input
    

    the terminal shows AssertionError

    AssertionError: None != 'brmph?! Numbers only. Try again...'
    
  • I change the return statement

    1def add(first_input, second_input):
    2    if isinstance(first_input, dict):
    3        return 'brmph?! Numbers only. Try again...'
    4    return first_input + second_input
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for -: 'dict' and 'float'
    
  • I add an if statement to the subtract function

    15def subtract(first_input, second_input):
    16    if isinstance(first_input, dict):
    17        return None
    18    return first_input - second_input
    

    the terminal shows AssertionError

    AssertionError: None != 'brmph?! Numbers only. Try again...'
    
  • I change the return statement

    15def subtract(first_input, second_input):
    16    if isinstance(first_input, dict):
    17        return 'brmph?! Numbers only. Try again...'
    18    return first_input - second_input
    

    the terminal shows TypeError

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

    same problem, same solution

  • I add an if statement with the same message in the divide function

     7def divide(first_input, second_input):
     8    if isinstance(first_input, dict):
     9        return 'brmph?! Numbers only. Try again...'
    10    return first_input / second_input
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for *: 'dict' and 'float'
    
  • I add the same if statement to the multiply function

    13def multiply(first_input=None, second_input=None):
    14    if isinstance(first_input, dict):
    15        return 'brmph?! Numbers only. Try again...'
    16    return first_input * second_input
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for +: 'set' and 'float'
    
  • I add to the if statement

    1def add(first_input, second_input):
    2    if isinstance(first_input, (dict, set)):
    3        return 'brmph?! Numbers only. Try again...'
    4    return first_input + second_input
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for -: 'set' and 'float'
    
  • I change the if statement in the subtract function

    19def subtract(first_input, second_input):
    20    if isinstance(first_input, (dict, set)):
    21        return 'brmph?! Numbers only. Try again...'
    22    return first_input - second_input
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for *: 'set' and 'float'
    
  • I change the if statement in the multiply function

    13def multiply(first_input=None, second_input=None):
    14    if isinstance(first_input, (dict, set)):
    15        return 'brmph?! Numbers only. Try again...'
    16    return first_input * second_input
    

    the terminal shows TypeError

    TypeError: can only concatenate list (not "float") to list
    
  • I add another data type to the if statement of the add function

    1def add(first_input, second_input):
    2    if isinstance(first_input, (dict, set, list)):
    3        return 'brmph?! Numbers only. Try again...'
    4    return first_input + second_input
    

    the terminal shows TypeError

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

    it looks I will have to do this for all the other operations

  • I add the list to the if statement in the other functions

     7def divide(first_input, second_input):
     8    if isinstance(first_input, (dict, set, list)):
     9        return 'brmph?! Numbers only. Try again...'
    10    return first_input / second_input
    11
    12
    13def multiply(first_input=None, second_input=None):
    14    if isinstance(first_input, (dict, set, list)):
    15        return 'brmph?! Numbers only. Try again...'
    16    return first_input * second_input
    17
    18
    19def subtract(first_input, second_input):
    20    if isinstance(first_input, (dict, set, list)):
    21        return 'brmph?! Numbers only. Try again...'
    22    return first_input - second_input
    

    the terminal shows TypeError

    TypeError: can only concatenate tuple (not "float") to tuple
    

    another data type, there has to be a better way

  • I add tuple to the `isinstance method`_ in the if statement of the add function

    1def add(first_input, second_input):
    2    if isinstance(first_input, (dict, set, list, tuple)):
    3        return 'brmph?! Numbers only. Try again...'
    4    return first_input + second_input
    

    the terminal shows TypeError

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

    same problem, same solution

  • I add tuple to the if statement of the other functions

     7def divide(first_input, second_input):
     8    if isinstance(first_input, (dict, set, list, tuple)):
     9        return 'brmph?! Numbers only. Try again...'
    10    return first_input / second_input
    11
    12
    13def multiply(first_input=None, second_input=None):
    14    if isinstance(first_input, (dict, set, list, tuple)):
    15        return 'brmph?! Numbers only. Try again...'
    16    return first_input * second_input
    17
    18
    19def subtract(first_input, second_input):
    20    if isinstance(first_input, (dict, set, list, tuple)):
    21        return 'brmph?! Numbers only. Try again...'
    22    return first_input - second_input
    

    the terminal shows TypeError

    TypeError: can only concatenate str (not "float") to str
    
  • I add str to the if statements of the 4 Functions

     1def add(first_input, second_input):
     2    if isinstance(first_input, (dict, set, list, tuple, str)):
     3        return 'brmph?! Numbers only. Try again...'
     4    return first_input + second_input
     5
     6
     7def divide(first_input, second_input):
     8    if isinstance(first_input, (dict, set, list, tuple, str)):
     9        return 'brmph?! Numbers only. Try again...'
    10    return first_input / second_input
    11
    12
    13def multiply(first_input=None, second_input=None):
    14    if isinstance(first_input, (dict, set, list, tuple, str)):
    15        return 'brmph?! Numbers only. Try again...'
    16    return first_input * second_input
    17
    18
    19def subtract(first_input, second_input):
    20    if isinstance(first_input, (dict, set, list, tuple, str)):
    21        return 'brmph?! Numbers only. Try again...'
    22    return first_input - second_input
    

    the terminal shows AssertionError

    SUBFAILED(i=True) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 404.40333818765737 != 'brmph?! Numbers on...
    SUBFAILED(i=False) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: JKL.MNOPQRSTUVWXYZ != 'brmph?! Numbers on...
    

    2 of the failures are for booleans

  • I add bool to the if statements of the functions

     1def add(first_input, second_input):
     2    if isinstance(first_input, (dict, set, list, tuple, str, bool)):
     3        return 'brmph?! Numbers only. Try again...'
     4    return first_input + second_input
     5
     6
     7def divide(first_input, second_input):
     8    if isinstance(first_input, (dict, set, list, tuple, str, bool)):
     9        return 'brmph?! Numbers only. Try again...'
    10    return first_input / second_input
    11
    12
    13def multiply(first_input=None, second_input=None):
    14    if isinstance(first_input, (dict, set, list, tuple, str, bool)):
    15        return 'brmph?! Numbers only. Try again...'
    16    return first_input * second_input
    17
    18
    19def subtract(first_input, second_input):
    20    if isinstance(first_input, (dict, set, list, tuple, str, bool)):
    21        return 'brmph?! Numbers only. Try again...'
    22    return first_input - second_input
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for +: 'NoneType' and 'float'
    
  • I add None to the call to the `isinstance method`_ in the if statements of the functions

     1def add(first_input, second_input):
     2    if isinstance(first_input, (dict, set, list, tuple, str, bool, None)):
     3        return 'brmph?! Numbers only. Try again...'
     4    return first_input + second_input
     5
     6
     7def divide(first_input, second_input):
     8    if isinstance(first_input, (dict, set, list, tuple, str, bool, None)):
     9        return 'brmph?! Numbers only. Try again...'
    10    return first_input / second_input
    11
    12
    13def multiply(first_input=None, second_input=None):
    14    if isinstance(first_input, (dict, set, list, tuple, str, bool, None)):
    15        return 'brmph?! Numbers only. Try again...'
    16    return first_input * second_input
    17
    18
    19def subtract(first_input, second_input):
    20    if isinstance(first_input, (dict, set, list, tuple, str, bool, None)):
    21        return 'brmph?! Numbers only. Try again...'
    22    return first_input - second_input
    

    the terminal shows TypeError

    TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
    

    that change made things worse. I cannot use None in the `isinstance method`_

  • I undo the change then add a new condition

     1def add(first_input, second_input):
     2    if (
     3        isinstance(first_input, (dict, set, list, tuple, str, bool))
     4        or first_input is None
     5    ):
     6        return 'brmph?! Numbers only. Try again...'
     7    return first_input + second_input
     8
     9
    10def divide(first_input, second_input):
    11    if (
    12        isinstance(first_input, (dict, set, list, tuple, str, bool))
    13        or first_input is None
    14    ):
    15        return 'brmph?! Numbers only. Try again...'
    16    return first_input / second_input
    17
    18
    19def multiply(first_input=None, second_input=None):
    20    if (
    21        isinstance(first_input, (dict, set, list, tuple, str, bool))
    22        or first_input is None
    23    ):
    24        return 'brmph?! Numbers only. Try again...'
    25    return first_input * second_input
    26
    27
    28def subtract(first_input, second_input):
    29    if (
    30        isinstance(first_input, (dict, set, list, tuple, str, bool))
    31        or first_input is None
    32    ):
    33        return 'brmph?! Numbers only. Try again...'
    34    return first_input - second_input
    

    the test passes


REFACTOR: make it better


  • I add a decorator function to remove the repetition of the if statements because they are all the same

     1def check_input(function):
     2    def wrapper(first_input, second_input):
     3        if (
     4            isinstance(
     5                first_input,
     6                (dict, set, list, tuple, str, bool)
     7            ) or
     8            first_input is None
     9        ):
    10            return 'brmph?! Numbers only. Try again...'
    11        return function(first_input, second_input)
    12    return wrapper
    13
    14
    15def add(first_input, second_input):
    
  • I use the new function to wrap the add function

    15@check_input
    16def add(first_input, second_input):
    17    if (
    18        isinstance(first_input, (dict, set, list, tuple, str, bool))
    19        or first_input is None
    20    ):
    21        return 'brmph?! Numbers only. Try again...'
    22    return first_input + second_input
    

    the test is still green

  • I remove the if statement

    15@check_input
    16def add(first_input, second_input):
    17    return first_input + second_input
    18
    19
    20def divide(first_input, second_input):
    

    still green

  • I do the same thing with the divide function

    20@check_input
    21def divide(first_input, second_input):
    22    if (
    23        isinstance(first_input, (dict, set, list, tuple, str, bool))
    24        or first_input is None
    25    ):
    26        return 'brmph?! Numbers only. Try again...'
    27    return first_input / second_input
    

    green

  • I remove the if statement

    20@check_input
    21def divide(first_input, second_input):
    22    return first_input / second_input
    23
    24
    25def multiply(first_input=None, second_input=None):
    

    the test is still green

  • I make the same change to the other 2 functions

     1def check_input(function):
     2    def wrapper(first_input, second_input):
     3        if (
     4            isinstance(
     5                first_input,
     6                (dict, set, list, tuple, str, bool)
     7            ) or
     8            first_input is None
     9        ):
    10            return 'brmph?! Numbers only. Try again...'
    11        return function(first_input, second_input)
    12    return wrapper
    13
    14
    15@check_input
    16def add(first_input, second_input):
    17    return first_input + second_input
    18
    19
    20@check_input
    21def divide(first_input, second_input):
    22    return first_input / second_input
    23
    24
    25@check_input
    26def multiply(first_input=None, second_input=None):
    27    return first_input * second_input
    28
    29
    30@check_input
    31def subtract(first_input, second_input):
    32    return first_input - second_input
    

    still green


All the tests are passing, but there is a problem. What happens when the functions receive a bad input as the second input or something that is not a dictionary, set, list, string or boolean? I need a better test


close the project

  • I close test_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


review

I used the __getattribute__ built-in function to make the calculator tests simpler.

I rewrote the solution after rewriting the tests and found that I did not add a test for bad second inputs


code from the chapter

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


what is next?

you have gone through a lot of things and know

Would you like to know what causes ModuleNotFoundError?


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