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 use
pytest-watcherto run the testsuv run pytest-watcher . --nowthe terminal shows
rootdir: .../pumping_python/calculator configfile: pyproject.toml collected 5 items tests/test_calculator.py ..... [100%] ======================== 5 passed in X.YZs =========================I hold ctrl on the keyboard, then click on
tests/test_calculator.pyto open it in the editor
test_calculator_w_getattribute
RED: make it fail
I add a new test
168 def test_calculator_functions(self):
169 for operation in self.arithmetic_tests:
170 with self.subTest(operation=operation):
171 self.assertEqual(
172 self.arithmetic_tests[operation]['function'](
173 self.random_first_number,
174 self.random_second_number
175 ),
176 self.arithmetic_tests[operation]['expectation']
177 )
178
179 def test_calculator_w_getattribute(self):
180 self.assertEqual(
181 src.calculator.__getattribute__('BOOM!!!'),
182 src.calculator.add
183 )
184
185
186# Exceptions seen
the terminal shows AttributeError
AttributeError: module 'src.calculator' has no attribute 'BOOM!!!'
__getattribute__is a method that takes a name and checks if the name is an attribute of the objectif the name is NOT an attribute it raises AttributeError
if the name is an attribute it returns the object it points to
src.calculator.__getattribute__('BOOM!!!')checks if thecalculator.pymodule in thesrcfolder has anything namedBOOM!!!
GREEN: make it pass
I change 'BOOM!!!' to 'add'
179 def test_calculator_w_getattribute(self):
180 self.assertEqual(
181 src.calculator.__getattribute__('add'),
182 src.calculator.add
183 )
the test passes because the add function is an attribute of src.calculator
REFACTOR: make it better
I add a call to the add function
179 def test_calculator_w_getattribute(self): 180 self.assertEqual( 181 src.calculator.__getattribute__('add'), 182 src.calculator.add( 183 self.random_first_number, 184 self.random_second_number 185 ) 186 )the terminal shows AssertionError
AssertionError: <function numbers_only.<locals>.decorator at 0xffffa1234bc0> != DEF.GHIJKLMNOPQRSI have to call the add function after
__getattribute__returns itI add parentheses at the end of the line to make it a call
179 def test_calculator_w_getattribute(self): 180 self.assertEqual( 181 src.calculator.__getattribute__('add')( 182 self.random_first_number, 183 self.random_second_number 184 ), 185 src.calculator.add( 186 self.random_first_number, 187 self.random_second_number 188 ) 189 )the test passes
I add a variables and a dictionary
179 def test_calculator_w_getattribute(self): 180 x = self.random_first_number 181 y = self.random_second_number 182 calculator_tests = {'add': x+y} 183 184 self.assertEqual(I use the dictionary to change the expectation of the assertion
184 self.assertEqual( 185 src.calculator.__getattribute__('add')( 186 self.random_first_number, 187 self.random_second_number 188 ), 189 # src.calculator.add( 190 # self.random_first_number, 191 # self.random_second_number 192 # ) 193 calculator_tests['add'] 194 )the test is still green
I use the variables for
self.random_first_numberandself.random_second_numberin the assertion184 self.assertEqual( 185 src.calculator.__getattribute__('add')( 186 # self.random_first_number, 187 # self.random_second_number 188 x, y 189 ), 190 # src.calculator.add( 191 # self.random_first_number, 192 # self.random_second_number 193 # ) 194 calculator_tests['add'] 195 )still green
I remove the commented lines
184 def test_calculator_w_getattribute(self): 185 x = self.random_first_number 186 y = self.random_second_number 187 calculator_tests = {'add': x+y} 188 189 self.assertEqual( 190 src.calculator.__getattribute__('add')(x, y), 191 calculator_tests['add'] 192 )green
I add subtraction to the
calculator_testsdictionary179 def test_calculator_w_getattribute(self): 180 x = self.random_first_number 181 y = self.random_second_number 182 calculator_tests = { 183 'add': x+y, 184 'subtract': x-y, 185 }then I add an assertion
187 self.assertEqual( 188 src.calculator.__getattribute__('add')(x, y), 189 calculator_tests['add'] 190 ) 191 self.assertEqual( 192 src.calculator.__getattribute__('subtract')(x, y), 193 calculator_tests['add'] 194 )the terminal shows AssertionError
AssertionError: OPQ.RSTUVWXYZABCDE != FGH.IJKLMNOPQRSTUVI change the expectation
191 self.assertEqual( 192 src.calculator.__getattribute__('subtract')(x, y), 193 calculator_tests['subtract'] 194 )the test passes
I add multiplication to the
calculator_testsdictionary179 def test_calculator_w_getattribute(self): 180 x = self.random_first_number 181 y = self.random_second_number 182 calculator_tests = { 183 'add': x+y, 184 'subtract': x-y, 185 'multiply': x*y, 186 }then I add an assertion
192 self.assertEqual( 193 src.calculator.__getattribute__('subtract')(x, y), 194 calculator_tests['subtract'] 195 ) 196 self.assertEqual( 197 src.calculator.__getattribute__('multiply')(x, y), 198 calculator_tests['subtract'] 199 )the terminal shows AssertionError
AssertionError: WXYZA.BCDEFGHIJKLMNO != PQ.RSTUVWXYZABCDEI change the expectation
196 self.assertEqual( 197 src.calculator.__getattribute__('multiply')(x, y), 198 calculator_tests['multiply'] 199 )the test passes
I add division to the
calculator_testsdictionary179 def test_calculator_w_getattribute(self): 180 x = self.random_first_number 181 y = self.random_second_number 182 calculator_tests = { 183 'add': x+y, 184 'subtract': x-y, 185 'multiply': x*y, 186 'divide': x/y, 187 }then I add an assertion
197 self.assertEqual( 198 src.calculator.__getattribute__('multiply')(x, y), 199 calculator_tests['multiply'] 200 ) 201 self.assertEqual( 202 src.calculator.__getattribute__('divide')(x, y), 203 calculator_tests['multiply'] 204 )the terminal shows AssertionError
AssertionError: F.GHIJKLMNOPQRST != UVWXY.ZABCDEFGHI change the expectation
201 def test_calculator_w_getattribute(self): 202 x = self.random_first_number 203 y = self.random_second_number 204 calculator_tests = { 205 'add': x+y, 206 'subtract': x-y, 207 'multiply': x*y, 208 'divide': x/y, 209 } 210 211 self.assertEqual( 212 src.calculator.__getattribute__('add')(x, y), 213 calculator_tests['add'] 214 ) 215 self.assertEqual( 216 src.calculator.__getattribute__('subtract')(x, y), 217 calculator_tests['subtract'] 218 ) 219 self.assertEqual( 220 src.calculator.__getattribute__('multiply')(x, y), 221 calculator_tests['multiply'] 222 ) 223 self.assertEqual( 224 src.calculator.__getattribute__('divide')(x, y), 225 calculator_tests['divide'] 226 ) 227 228 229# Exceptions seenthe test passes
I add a for loop
179 def test_calculator_w_getattribute(self): 180 x = self.random_first_number 181 y = self.random_second_number 182 calculator_tests = { 183 'add': x+y, 184 'subtract': x-y, 185 'multiply': x*y, 186 'divide': x/y, 187 } 188 189 for operation in calculator_tests: 190 with self.subTest(operation=operation): 191 self.assertEqual( 192 src.calculator.__getattribute__(operation)( 193 x, y 194 ), 195 'BOOM!!!' 196 ) 197 198 self.assertEqual( 199 src.calculator.__getattribute__('add')(x, y), 200 calculator_tests['add'] 201 )the terminal shows AssertionError
SUBFAILED(operation='add') ... - AssertionError: FGH.IJKLMNOPQRSTU != 'BOOM!!!' SUBFAILED(operation='subtract') ... - AssertionError: VWX.YZABCDEFGHIJKL != 'BOOM!!!' SUBFAILED(operation='multiply') ... - AssertionError: MNOP.QRSTUVWXYZAB != 'BOOM!!!' SUBFAILED(operation='divide') ... - AssertionError: CD.EFGHIJKLMNOPQR != 'BOOM!!!'I change the expectation
191 self.assertEqual( 192 src.calculator.__getattribute__(operation)( 193 x, y 194 ), 195 calculator_tests[operation] 196 )the test passes
I remove the other assertions
179 def test_calculator_w_getattribute(self): 180 x = self.random_first_number 181 y = self.random_second_number 182 calculator_tests = { 183 'add': x+y, 184 'subtract': x-y, 185 'multiply': x*y, 186 'divide': x/y, 187 } 188 189 for operation in calculator_tests: 190 with self.subTest(operation=operation): 191 self.assertEqual( 192 src.calculator.__getattribute__(operation)( 193 x, y 194 ), 195 calculator_tests[operation] 196 ) 197 198 199# Exceptions seenis this simpler than test_calculator_functions?
I add
calculator_teststo the setUp method to make it a class attribute12 def setUp(self): 13 self.random_first_number = a_random_number() 14 self.random_second_number = a_random_number() 15 16 x = self.random_first_number 17 y = self.random_second_number 18 19 try: 20 division_result = x / y 21 except ZeroDivisionError: 22 division_result = 'brmph?! I cannot divide by 0. Try again...' 23 24 self.calculator_tests = { 25 'add': x+y, 26 'subtract': x-y, 27 'multiply': x*y, 28 'divide': division_result 29 } 30 31 self.arithmetic_tests = {I use the new class attribute in test_calculator_w_getattribute
186 def test_calculator_w_getattribute(self): 187 x = self.random_first_number 188 y = self.random_second_number 189 # calculator_tests = { 190 # 'add': x+y, 191 # 'subtract': x-y, 192 # 'multiply': x*y, 193 # 'divide': x/y, 194 # } 195 calculator_tests = self.calculator_tests 196 197 for operation in calculator_tests:the test is still green
I use
self.calculator_testsin the for loop197 # for operation in calculator_tests: 198 for operation in self.calculator_tests: 199 with self.subTest(operation=operation): 200 self.assertEqual( 201 src.calculator.__getattribute__(operation)( 202 x, y 203 ), 204 calculator_tests[operation] 205 )still green
I use it in the expectation of the assertion
200 self.assertEqual( 201 src.calculator.__getattribute__(operation)( 202 x, y 203 ), 204 # calculator_tests[operation] 205 self.calculator_tests[operation] 206 )green
I use
self.random_first_numberforxandself.random_second_numberforyin the assertion200 self.assertEqual( 201 src.calculator.__getattribute__(operation)( 202 # x, y 203 self.random_first_number, 204 self.random_second_number 205 ), 206 # calculator_tests[operation] 207 self.calculator_tests[operation] 208 )still green
I remove the commented lines and the variables because they are no longer used
186 def test_calculator_w_getattribute(self): 187 for operation in self.calculator_tests: 188 with self.subTest(operation=operation): 189 self.assertEqual( 190 src.calculator.__getattribute__(operation)( 191 self.random_first_number, 192 self.random_second_number 193 ), 194 self.calculator_tests[operation] 195 ) 196 197 198# Exceptions seenthe test is still green
I remove test_calculator_functions because test_calculator_w_getattribute has the same tests
153 def test_calculator_sends_message_when_input_is_not_a_number(self): 154 for bad_input in ( 155 None, 156 True, False, 157 str(), 'text', 158 tuple(), (0, 1, 2, 'n'), 159 list(), [0, 1, 2, 'n'], 160 set(), {0, 1, 2, 'n'}, 161 dict(), {'key': 'value'}, 162 ): 163 for operation in self.arithmetic_tests: 164 with self.subTest( 165 operation=operation, 166 bad_input=bad_input, 167 ): 168 self.assertEqual( 169 self.arithmetic_tests[operation]['function']( 170 bad_input, a_random_number() 171 ), 172 'brmph?! Numbers only. Try again...' 173 ) 174 175 def test_calculator_w_getattribute(self): 176 for operation in self.calculator_tests: 177 with self.subTest(operation=operation): 178 self.assertEqual( 179 src.calculator.__getattribute__(operation)( 180 self.random_first_number, 181 self.random_second_number 182 ), 183 self.calculator_tests[operation] 184 ) 185 186 187# Exceptions seenthe tests are still green
I change the name of test_calculator_w_getattribute to test_calculator_functions
175 def test_calculator_functions(self): 176 for operation in self.calculator_tests: 177 with self.subTest(operation=operation): 178 self.assertEqual( 179 src.calculator.__getattribute__(operation)( 180 self.random_first_number, 181 self.random_second_number 182 ), 183 self.calculator_tests[operation] 184 ) 185 186 187# Exceptions seen
I use
self.calculator_testsin the for loop of test_calculator_sends_message_when_input_is_not_a_number153 def test_calculator_sends_message_when_input_is_not_a_number(self): 154 for bad_input in ( 155 None, 156 True, False, 157 str(), 'text', 158 tuple(), (0, 1, 2, 'n'), 159 list(), [0, 1, 2, 'n'], 160 set(), {0, 1, 2, 'n'}, 161 dict(), {'key': 'value'}, 162 ): 163 # for operation in self.arithmetic_tests: 164 for operation in self.calculator_tests: 165 with self.subTest( 166 operation=operation, 167 bad_input=bad_input, 168 ):I use the
__getattribute__method in the assertion169 self.assertEqual( 170 # self.arithmetic_tests[operation]['function']( 171 src.calculator.__getattribute__(operation)( 172 bad_input, a_random_number() 173 ), 174 'brmph?! Numbers only. Try again...' 175 )the test passes
I remove the commented lines
153 def test_calculator_sends_message_when_input_is_not_a_number(self): 154 for bad_input in ( 155 None, 156 True, False, 157 str(), 'text', 158 tuple(), (0, 1, 2, 'n'), 159 list(), [0, 1, 2, 'n'], 160 set(), {0, 1, 2, 'n'}, 161 dict(), {'key': 'value'}, 162 ): 163 for operation in self.calculator_tests: 164 with self.subTest( 165 operation=operation, 166 bad_input=bad_input, 167 ): 168 self.assertEqual( 169 src.calculator.__getattribute__(operation)( 170 bad_input, a_random_number() 171 ), 172 'brmph?! Numbers only. Try again...' 173 ) 174 175 def test_calculator_functions(self):
I use
self.calculator_testsin the for loop of test_calculator_raises_type_error_when_given_more_than_two_inputs143 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self): 144 # for operation in self.arithmetic_tests: 145 for operation in self.calculator_tests: 146 with ( 147 self.subTest(operation=operation), 148 self.assertRaises(TypeError), 149 ): 150 self.arithmetic_tests[operation]['function']( 151 [0, 1, 2] 152 ) 153 154 def test_calculator_sends_message_when_input_is_not_a_number(self):I use the
__getattribute__method in the assertion150 # self.arithmetic_tests[operation]['function']( 151 src.calculator.__getattribute__(operation)( 152 [0, 1, 2] 153 )the test passes
I remove the commented lines
143 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self): 144 for operation in self.calculator_tests: 145 with ( 146 self.subTest(operation=operation), 147 self.assertRaises(TypeError), 148 ): 149 src.calculator.__getattribute__(operation)( 150 [0, 1, 2] 151 ) 152 153 def test_calculator_sends_message_when_input_is_not_a_number(self):the test is still green
I use
self.calculator_testsin the for loop of test_calculator_w_dictionary_items99 def test_calculator_w_dictionary_items(self): 100 two_numbers = { 101 'first_input': self.random_first_number, 102 'second_input': self.random_second_number, 103 } 104 105 # for operation in self.arithmetic_tests: 106 for operation in self.calculator_tests: 107 with self.subTest(operation=operation):I use the
__getattribute__method in the assertion108 self.assertEqual( 109 # self.arithmetic_tests[operation]['function']( 110 src.calculator.__getattribute__(operation)( 111 **two_numbers 112 ), 113 self.arithmetic_tests[operation]['expectation'] 114 )I use
self.calculator_testsin the expectation of the assertion108 self.assertEqual( 109 # self.arithmetic_tests[operation]['function']( 110 src.calculator.__getattribute__(operation)( 111 **two_numbers 112 ), 113 # self.arithmetic_tests[operation]['expectation'] 114 self.calculator_tests[operation] 115 )the test passes
I remove the commented lines
99 def test_calculator_w_dictionary_items(self): 100 two_numbers = { 101 'first_input': self.random_first_number, 102 'second_input': self.random_second_number, 103 } 104 105 for operation in self.calculator_tests: 106 with self.subTest(operation=operation): 107 self.assertEqual( 108 src.calculator.__getattribute__(operation)( 109 **two_numbers 110 ), 111 self.calculator_tests[operation] 112 ) 113 114 self.assertEqual( 115 src.calculator.add( 116 two_numbers['first_input'], 117 two_numbers['second_input'] 118 ), 119 self.random_first_number+self.random_second_number 120 ) 121 self.assertEqual( 122 src.calculator.divide( 123 two_numbers['first_input'], 124 two_numbers['second_input'] 125 ), 126 self.random_first_number/self.random_second_number 127 ) 128 self.assertEqual( 129 src.calculator.multiply( 130 two_numbers['second_input'], 131 two_numbers['second_input'] 132 ), 133 self.random_second_number*self.random_second_number 134 ) 135 self.assertEqual( 136 src.calculator.subtract( 137 two_numbers['first_input'], 138 two_numbers['first_input'] 139 ), 140 self.random_first_number-self.random_first_number 141 ) 142 143 def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
I use
self.calculator_testsin the for loop of test_calculator_w_list_items50 def test_calculator_w_list_items(self): 51 # two_numbers = [ 52 # self.random_first_number, 53 # self.random_second_number 54 # ] 55 a_dictionary = { 56 'x': self.random_first_number, 57 'y': self.random_second_number 58 } 59 two_numbers = list(a_dictionary.values()) 60 61 # for operation in self.arithmetic_tests: 62 for operation in self.calculator_tests: 63 with self.subTest(operation=operation):I use the
__getattribute__method in the assertion64 self.assertEqual( 65 # self.arithmetic_tests[operation]['function']( 66 src.calculator.__getattribute__(operation)( 67 *two_numbers 68 ), 69 self.arithmetic_tests[operation]['expectation'] 70 )I use
self.calculator_testsin the expectation of the assertion64 self.assertEqual( 65 # self.arithmetic_tests[operation]['function']( 66 src.calculator.__getattribute__(operation)( 67 *two_numbers 68 ), 69 # self.arithmetic_tests[operation]['expectation'] 70 self.calculator_tests[operation] 71 )the test passes
I remove the new commented lines
50 def test_calculator_w_list_items(self): 51 # two_numbers = [ 52 # self.random_first_number, 53 # self.random_second_number 54 # ] 55 a_dictionary = { 56 'x': self.random_first_number, 57 'y': self.random_second_number 58 } 59 two_numbers = list(a_dictionary.values()) 60 61 for operation in self.calculator_tests: 62 with self.subTest(operation=operation): 63 self.assertEqual( 64 src.calculator.__getattribute__(operation)( 65 *two_numbers 66 ), 67 self.calculator_tests[operation] 68 ) 69 70 self.assertEqual( 71 src.calculator.add( 72 two_numbers[0], 73 two_numbers[1] 74 ), 75 self.random_first_number+self.random_second_number 76 ) 77 self.assertEqual( 78 src.calculator.divide( 79 two_numbers[-2], 80 two_numbers[-1] 81 ), 82 self.random_first_number/self.random_second_number 83 ) 84 self.assertEqual( 85 src.calculator.multiply( 86 two_numbers[1], 87 two_numbers[-1] 88 ), 89 self.random_second_number*self.random_second_number 90 ) 91 self.assertEqual( 92 src.calculator.subtract( 93 two_numbers[-2], 94 two_numbers[0] 95 ), 96 self.random_first_number-self.random_first_number 97 ) 98 99 def test_calculator_w_dictionary_items(self):
I comment out
self.arithmetic_testsin the setUp method to see if there are any tests that still use it12 def setUp(self): 13 self.random_first_number = a_random_number() 14 self.random_second_number = a_random_number() 15 16 x = self.random_first_number 17 y = self.random_second_number 18 19 try: 20 division_result = x / y 21 except ZeroDivisionError: 22 division_result = 'brmph?! I cannot divide by 0. Try again...' 23 24 self.calculator_tests = { 25 'add': x+y, 26 'subtract': x-y, 27 'multiply': x*y, 28 'divide': division_result 29 } 30 31 # self.arithmetic_tests = { 32 # 'addition': { 33 # 'function': src.calculator.add, 34 # 'expectation': x+y, 35 # }, 36 # 'subtraction': { 37 # 'function': src.calculator.subtract, 38 # 'expectation': x-y, 39 # }, 40 # 'division': { 41 # 'function': src.calculator.divide, 42 # 'expectation': division_result, 43 # }, 44 # 'multiplication': { 45 # 'function': src.calculator.multiply, 46 # 'expectation': x*y, 47 # } 48 # } 49 50 def test_calculator_w_list_items(self):the tests are still green
I remove the commented lines
12 def setUp(self): 13 self.random_first_number = a_random_number() 14 self.random_second_number = a_random_number() 15 16 x = self.random_first_number 17 y = self.random_second_number 18 19 try: 20 division_result = x / y 21 except ZeroDivisionError: 22 division_result = 'brmph?! I cannot divide by 0. Try again...' 23 24 self.calculator_tests = { 25 'add': x+y, 26 'subtract': x-y, 27 'multiply': x*y, 28 'divide': division_result 29 } 30 31 def test_calculator_w_list_items(self):
I make a function for the division result because I can
10class TestCalculator(unittest.TestCase): 11 12 def get_division_result(x, y): 13 try: 14 return x / y 15 except ZeroDivisionError: 16 return 'brmph?! I cannot divide by 0. Try again...' 17 18 def setUp(self):I use the new function in
self.calculator_testsTypeError: TestCalculator.get_division_result() takes 2 positional arguments but 3 were givenI forgot to make
get_division_resulta staticmethod since it does not do anything with the rest of the classI wrap
get_division_resultwith the staticmethod decorator10class TestCalculator(unittest.TestCase): 11 12 @staticmethod 13 def get_division_result(x, y): 14 try: 15 return x / y 16 except ZeroDivisionError: 17 return 'brmph?! I cannot divide by 0. Try again...' 18 19 def setUp(self):the test passes
I remove the commented line and the exception handler from the setUp method
19def setUp(self): 20 self.random_first_number = a_random_number() 21 self.random_second_number = a_random_number() 22 23 x = self.random_first_number 24 y = self.random_second_number 25 26 self.calculator_tests = { 27 'add': x+y, 28 'subtract': x-y, 29 'multiply': x*y, 30 'divide': self.get_division_result(x, y) 31 } 32 33def test_calculator_w_list_items(self):
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 open
calculator.pyfrom thesrcfolder in the editorthen I delete everything in
calculator.py, the terminal shows 3 passing tests and AttributeError for 70 failing tests, I start with the last oneAttributeError: module 'src.calculator' has no attribute 'add'I add the name to
calculator.py1addNameError: 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 - yTypeError: divide() got an unexpected keyword argument 'first_input'
I add a name to the divide function
5def divide(x, y, first_input): 6 return x / yTypeError: divide() missing 1 required positional argument: 'first_input'I add a default value to make it a choice
5def divide(x, y, first_input=None): 6 return x / yTypeError: divide() got an unexpected keyword argument 'second_input'I add
second_inputwith a default value to the function5def divide(x, y, first_input=None, second_input=None): 6 return x / yTypeError: divide() missing 2 required positional arguments: 'x' and 'y'the test must be calling divide with positional arguments in some cases and with keyword arguments in others. My solution does not work, I remove
xandyfrom the parentheses5def divide(first_input=None, second_input=None): 6 return x / yNameError: name 'x' is not definedI change the names in the return statement
5def divide(first_input=None, second_input=None): 6 return first_input / second_inputTypeError: multiply() got an unexpected keyword argument 'first_input'I use the
Rename Symbolfeature to change the names in the parentheses9def multiply(first_input, y): 10 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 add function
1def add(first_input, second_input): 2 return first_input + second_inputSUBFAILED(operation='divide', bad_input={'key': 'value'}) ... - TypeError: unsupported operand type(s) for /: 'dict' and 'float'
I add an if statement to the divide function
5def divide(first_input=None, second_input=None): 6 if isinstance(first_input, dict): 7 return None 8 return first_input / second_inputthe terminal shows AssertionError
AssertionError: None != 'brmph?! Numbers only. Try again...'I change the return statement of the if 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_inputSUBFAILED(operation='multiply', bad_input={'key': 'value'}) ... - TypeError: unsupported operand type(s) for *: 'dict' and 'float'I add an if statement to the multiply function
11def multiply(first_input, second_input): 12 if isinstance(first_input, dict): 13 return None 14 return first_input * second_inputthe terminal shows AssertionError
AssertionError: None != 'brmph?! Numbers only. Try again...'I change the return statement of the if statement
11def multiply(first_input, second_input): 12 if isinstance(first_input, dict): 13 return 'brmph?! Numbers only. Try again...' 14 return first_input * second_inputSUBFAILED(operation='subtract', bad_input={'key': 'value'}) ... - TypeError: unsupported operand type(s) for -: 'dict' and 'float'same problem, same solution
I add an if statement with the same message in the subtract function
17def subtract(first_input, second_input): 18 if isinstance(first_input, dict): 19 return 'brmph?! Numbers only. Try again...' 20 return first_input - second_inputSUBFAILED(operation='add', bad_input={'key': 'value'}) ... - TypeError: unsupported operand type(s) for +: 'dict' and 'float'I add the same if statement to the add function
1def add(first_input, second_input): 2 if isinstance(first_input, dict): 3 return 'brmph?! Numbers only. Try again...' 4 return first_input + second_inputSUBFAILED(operation='divide', bad_input={'n', 0, 2, 1}) ... - TypeError: unsupported operand type(s) for /: 'set' and 'float'
I add set to the if statement in the divide function
7def divide(first_input=None, second_input=None): 8 if isinstance(first_input, (dict, set)): 9 return 'brmph?! Numbers only. Try again...' 10 return first_input / second_inputSUBFAILED(operation='multiply', bad_input={0, 1, 2, 'n'}) ... - TypeError: unsupported operand type(s) for *: 'set' and 'float'I do the same thing to the if statement in the multiply function
13def multiply(first_input, second_input): 14 if isinstance(first_input, (dict, set)): 15 return 'brmph?! Numbers only. Try again...' 16 return first_input * second_inputSUBFAILED(operation='subtract', bad_input={0, 1, 2, 'n'}) ... - 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_inputSUBFAILED(operation='add', bad_input={0, 1, 2, 'n'}) ... - TypeError: unsupported operand type(s) for +: 'set' and 'float'I add set to the if statement in the add function
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_inputSUBFAILED(operation='divide', bad_input=[0, 1, 2, 'n']) ... - TypeError: unsupported operand type(s) for /: 'list' and 'float'
I add list to the if statement of the divide function
7def divide(first_input=None, second_input=None): 8 if isinstance(first_input, (dict, set, list)): 9 return 'brmph?! Numbers only. Try again...' 10 return first_input / second_inputSUBFAILED(operation='multiply', bad_input=[0, 1, 2, 'n']) ... - TypeError: can't multiply sequence by non-int of type 'float'the error message is different but the function got
[0, 1, 2, 'n']as input, it looks I will have to do this for all the other operationsI add list to the if statement in the other functions
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 5 6 7def divide(first_input=None, second_input=None): 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, second_input): 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_inputSUBFAILED(operation='divide', bad_input=(0, 1, 2, 'n')) ... - TypeError: unsupported operand type(s) for /: 'tuple' and 'float'another data type, there has to be a better way
I add tuple to the isinstance built-in function in the if statement of the divide function
7def divide(first_input=None, second_input=None): 8 if isinstance(first_input, (dict, set, list, tuple)): 9 return 'brmph?! Numbers only. Try again...' 10 return first_input / second_inputSUBFAILED(operation='multiply', bad_input=(0, 1, 2, 'n')) ... - TypeError: can't multiply sequence by non-int of type 'float'same problem, same solution
I add tuple to the if statement of the other functions
7def add(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 divide(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 multiply(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 23 24 25def subtract(first_input, second_input): 26 if isinstance(first_input, (dict, set, list, tuple)): 27 return 'brmph?! Numbers only. Try again...' 28 return first_input - second_inputTypeError: 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=None, second_input=None): 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, second_input): 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(operation='divide', bad_input=False) ... - AssertionError: 0.0 != 'brmph?! Numbers only. Try again...'the input is a boolean in this case
I add bool to the if statements of the functions
1def add(first_input, second_input): 2 if isinstance( 3 first_input, 4 (dict, set, list, tuple, str, bool) 5 ): 6 return 'brmph?! Numbers only. Try again...' 7 return first_input + second_input 8 9 10def divide(first_input=None, second_input=None): 11 if isinstance( 12 first_input, 13 (dict, set, list, tuple, str, bool) 14 ): 15 return 'brmph?! Numbers only. Try again...' 16 return first_input / second_input 17 18 19def multiply(first_input, second_input): 20 if isinstance( 21 first_input, 22 (dict, set, list, tuple, str, bool) 23 ): 24 return 'brmph?! Numbers only. Try again...' 25 return first_input * second_input 26 27 28def subtract(first_input, second_input): 29 if isinstance( 30 first_input, 31 (dict, set, list, tuple, str, bool) 32 ): 33 return 'brmph?! Numbers only. Try again...' 34 return first_input - second_inputSUBFAILED(operation='divide', bad_input=None) ... - TypeError: unsupported operand type(s) for /: 'NoneType' and 'float'
I add None to the call to the isinstance built-in function in the if statement of the divide function
10def divide(first_input=None, second_input=None): 11 if isinstance( 12 first_input, 13 (dict, set, list, tuple, str, bool, None) 14 ): 15 return 'brmph?! Numbers only. Try again...' 16 return first_input / second_inputTypeError: isinstance() arg 2 must be a type, a tuple of types, or a unionI cannot use None in the isinstance built-in function
I undo the change then add a new condition
10def divide(first_input=None, second_input=None): 11 if isinstance( 12 first_input, 13 (dict, set, list, tuple, str, bool) 14 ) or first_input is None: 15 return 'brmph?! Numbers only. Try again...' 16 return first_input / second_inputSUBFAILED(operation='multiply', bad_input=None) ... - TypeError: unsupported operand type(s) for *: 'NoneType' and 'float'same problem, same …
I add the new condition to the if statement of the other functions
1def add(first_input, second_input): 2 if isinstance( 3 first_input, 4 (dict, set, list, tuple, str, bool) 5 ) or first_input is None: 6 return 'brmph?! Numbers only. Try again...' 7 return first_input + second_input 8 9 10def divide(first_input=None, second_input=None): 11 if isinstance( 12 first_input, 13 (dict, set, list, tuple, str, bool) 14 ) or first_input is None: 15 return 'brmph?! Numbers only. Try again...' 16 return first_input / second_input 17 18 19def multiply(first_input, second_input): 20 if isinstance( 21 first_input, 22 (dict, set, list, tuple, str, bool) 23 ) or first_input is None: 24 return 'brmph?! Numbers only. Try again...' 25 return first_input * second_input 26 27 28def subtract(first_input, second_input): 29 if isinstance( 30 first_input, 31 (dict, set, list, tuple, str, bool) 32 ) or first_input is None: 33 return 'brmph?! Numbers only. Try again...' 34 return first_input - second_inputthe terminal shows AssertionError
AssertionError: TypeError not raisedI remove the default values from the divide function because it is the only one that is different from the others
10def divide(first_input, second_input): 11 if isinstance( 12 first_input, 13 (dict, set, list, tuple, str, bool) 14 ) or first_input is None: 15 return 'brmph?! Numbers only. Try again...' 16 return first_input / second_inputthe test passes. Time to play
REFACTOR: make it better
I add a decorator function
1def check_input(function): 2 def wrapper(first_input, second_input): 3 if isinstance( 4 first_input, 5 (dict, set, list, tuple, str, bool) 6 ) or first_input is None: 7 return 'brmph?! Numbers only. Try again...' 8 return function(first_input, second_input) 9 return wrapper 10 11 12def add(first_input, second_input):I use it to wrap the add function
12@check_input 13def add(first_input, second_input): 14 if isinstance( 15 first_input, 16 (dict, set, list, tuple, str, bool) 17 ) or first_input is None: 18 return 'brmph?! Numbers only. Try again...' 19 return first_input + second_inputthe test is still green
I remove the if statement
12@check_input 13def add(first_input, second_input): 14 return first_input + second_input 15 16 17def divide(first_input, second_input):still green
I do the same thing to the divide function
17@check_input 18def divide(first_input, second_input): 19 if isinstance( 20 first_input, 21 (dict, set, list, tuple, str, bool) 22 ) or first_input is None: 23 return 'brmph?! Numbers only. Try again...' 24 return first_input / second_inputgreen
I remove the if statement
17@check_input 18def divide(first_input, second_input): 19 return first_input / second_input 20 21 22def multiply(first_input, second_input):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 are problems.
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
It looks like the test for how the calculator handles ZeroDivisionError never runs. I need to make that test better
I wonder what else I missed. I am still learning
close the project
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
the test for handling ZeroDivisionError has a problem
code from the 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