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
calculatorfoldercd calculatorthe terminal shows I am in the
calculatorfolder.../pumping_python/calculatorI activate the virtual environment
source .venv/bin/activateAttention
on Windows without Windows Subsystem for Linux use
.venv/bin/activate.ps1NOTsource .venv/bin/activate.venv/scripts/activate.ps1the terminal shows
(.venv) .../pumping_python/calculatorI use
pytest-watchto run the testspytest-watchthe 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.pyto 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.EFGHIJKLMNI 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 )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')returnssrc.calculator.addcalculator_tests['add']returnsself.random_first_number + self.random_second_numberobject.__getattribute__(something)checks ifsomethingis inobjectand returns it
GREEN: make it pass
I add another operation to the
calculator_testsdictionaryI 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.IJKLMNOPQRSTUVI 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 != 1I 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 namethe 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_getattribute60 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_ain the for loop75 # 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_testsvariable60 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_functionssince it is covered bytest_calculator_w_getattribute, they have the same tests. I removetest_calculator_functions60 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_getattributetotest_calculator_functions55 '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_messagevariable. I remove it because I do not need it anymore71 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 withself.calculator_tests_ain a new for loop in test_calculator_w_list_items115 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 != 1I 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 != 1I 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 withself.calculator_tests_ain a new for loop in test_calculator_raises_type_error_when_given_more_than_two_inputs168 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 seenthe terminal shows TypeError for each operation
TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were givengood, 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 seenthe 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 seenthe test is still green
I remove
self.calculator_testsfrom the setUp method because it is no longer used12 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 Symbolfeature to changeself.calculator_tests_atoself.calculator_tests12 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.pyin the editorI delete all the text in
calculator.py, the terminal shows 3 passing tests and AttributeError for 23 failing tests, I start with the last oneAttributeError: module 'src.calculator' has no attribute 'add'I add the name to
calculator.pyNameError: name 'add' is not defined
GREEN: make it pass
I point
addto None to define it1add = NoneTypeError: 'NoneType' object is not callableI change it to a function
1def add(): 2 return NoneTypeError: add() takes 0 positional arguments but 2 were givenI add a name to the parentheses
1def add(x): 2 return NoneTypeError: add() takes 1 positional argument but 2 were givenI add a second name to the parentheses
1def add(x, y): 2 return Nonethe terminal shows AssertionError
AssertionError: None != TUV.WXYZABCDEFGHIJI change the return statement
1def add(x, y): 2 return x + ythe 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 / ythe 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 * ythe 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 - ythe terminal shows AssertionError
TypeError: multiply() got an unexpected keyword argument 'first_input'I add a name to the
multiplyfunction9def multiply(x, y, first_input): 10 return x * yTypeError: 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 * yTypeError: multiply() got an unexpected keyword argument 'second_input'I add
second_inputwith a default value to the function9def multiply(x, y, first_input=None, second_input=None): 10 return x * yTypeError: multiply() missing 2 required positional arguments: 'x' and 'y'my solution makes no sense, I remove
xandyfrom the parentheses9def multiply(first_input=None, second_input=None): 10 return x * yNameError: name 'x' is not definedI change the names in the return statement
9def multiply(first_input=None, second_input=None): 10 return first_input * second_inputTypeError: divide() got an unexpected keyword argument 'first_input'I use
Rename Symbolto change the name5def divide(first_input, y): 6 return first_input / yTypeError: divide() got an unexpected keyword argument 'second_input'I do the same thing to
y5def divide(first_input, second_input): 6 return first_input / second_inputTypeError: 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_inputTypeError: add() got an unexpected keyword argument 'first_input'I change the names in the
addfunction1def add(first_input, second_input): 2 return first_input + second_inputTypeError: 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_inputthe 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_inputTypeError: unsupported operand type(s) for -: 'dict' and 'float'I add an if statement to the
subtractfunction15def subtract(first_input, second_input): 16 if isinstance(first_input, dict): 17 return None 18 return first_input - second_inputthe 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_inputTypeError: unsupported operand type(s) for /: 'dict' and 'float'same problem, same solution
I add an if statement with the same message in the
dividefunction7def divide(first_input, second_input): 8 if isinstance(first_input, dict): 9 return 'brmph?! Numbers only. Try again...' 10 return first_input / second_inputTypeError: unsupported operand type(s) for *: 'dict' and 'float'I add the same if statement to the
multiplyfunction13def 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_inputTypeError: 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_inputTypeError: unsupported operand type(s) for -: 'set' and 'float'I change the if statement in the
subtractfunction19def subtract(first_input, second_input): 20 if isinstance(first_input, (dict, set)): 21 return 'brmph?! Numbers only. Try again...' 22 return first_input - second_inputTypeError: unsupported operand type(s) for *: 'set' and 'float'I change the if statement in the
multiplyfunction13def 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_inputTypeError: can only concatenate list (not "float") to listI add another data type to the if statement of the
addfunction1def 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_inputTypeError: 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_inputTypeError: can only concatenate tuple (not "float") to tupleanother data type, there has to be a better way
I add tuple to the `isinstance method`_ in the if statement of the
addfunction1def 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_inputTypeError: 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_inputTypeError: can only concatenate str (not "float") to strI 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_inputthe 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_inputTypeError: 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_inputTypeError: isinstance() arg 2 must be a type, a tuple of types, or a unionthat 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_inputthe 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
addfunction15@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_inputthe 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
dividefunction20@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_inputgreen
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_inputstill 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.pyin the editorI click in the terminal and exit the tests with ctrl+c on the keyboard, the terminal shows
(.venv) .../pumping_python/calculatorI deactivate the virtual environment
deactivatethe terminal goes back to the command line,
(.venv)is no longer on the left side.../pumping_python/calculatorI change directory to the parent of
calculatorcd ..the terminal shows
.../pumping_pythonI am back in the
pumping_pythondirectory
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
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