TypeError
what causes TypeError?
TypeError is raised when an object is used in a way that it should not be. This helps understand how to use functions and classes
preview
Here are the tests I have by the end of the chapter
1import unittest
2import src.type_error
3
4
5class TestTypeError(unittest.TestCase):
6
7 def test_type_error_w_the_uncallables(self):
8 src.type_error.none()
9 src.type_error.false()
10 src.type_error.true()
11 src.type_error.a_list()
12 src.type_error.a_dictionary()
13
14 def test_type_error_w_function_signatures(self):
15 src.type_error.function_00('a')
16 src.type_error.function_01('a', 'b')
17 src.type_error.function_02('a', 'b', 'c')
18 src.type_error.function_03('a', 'b', 'c', 'd')
19
20 def test_type_error_w_objects_that_do_not_mix(self):
21 with self.assertRaises(TypeError):
22 None + 1
23 with self.assertRaises(TypeError):
24 'text' + 0.1
25 with self.assertRaises(TypeError):
26 (1, 2, 3, 'n') - {1, 2, 3, 'n'}
27
28
29# Exceptions seen
30# AssertionError
31# AttributeError
32# TypeError
start the project
I open a terminal
I type pwd to make sure I am in the
pumping_pythonfolderpwdthe terminal goes back to the command line
.../pumping_pythonNote
if you are not in the
pumping_pythonfolder, trycd ~/pumping_pythonI choose
type_erroras the name of this projectI open
makePythonTdd.shormakePythonTdd.ps1in the editorTip
Here is a quick way to open
makePythonTdd.shormakePythonTdd.ps1if you are using Visual Studio Codecode makePythonTdd.shon Windows without Windows Subsystem for Linux use
code makePythonTdd.ps1I change everywhere I have
exceptionsto the name of this project inmakePythonTdd.sh1#!/bin/bash 2mkdir type_error 3cd type_error 4mkdir src 5touch src/type_error.py 6mkdir tests 7touch tests/__init__.py 8 9echo "import unittest 10 11 12class TestTypeError(unittest.TestCase): 13 14 def test_failure(self): 15 self.assertFalse(True) 16 17 18# Exceptions seen 19# AssertionError 20" > tests/test_type_error.pyon Windows without Windows Subsystem for Linux use
makePythonTdd.ps1instead ofmakePythonTdd.sh1mkdir type_error 2cd type_error 3mkdir src 4New-Item src/type_error.py 5mkdir tests 6New-Item tests/__init__.py 7 8"import unittest 9 10 11class TestTypeError(unittest.TestCase): 12 13 def test_failure(self): 14 self.assertFalse(True) 15 16# Exceptions seen 17# AssertionError 18" | Out-File tests/test_type_error.pyI run the program in the terminal
./makePythonTdd.shon Windows without Windows Subsystem for Linux use
makePythonTdd.ps1instead ofmakePythonTdd.sh./makePythonTdd.ps1the terminal shows AssertionError
======================================= FAILURES ======================================= _____________________________ TestTypeError.test_failure ______________________________ self = <tests.test_exceptions.TestTypeError testMethod=test_failure> def test_failure(self): > self.assertFalse(True) E AssertionError: True is not false tests/test_type_error.py:7: AssertionError =============================== short test summary info ================================ FAILED tests/test_type_error.py::TestMagic::test_failure - AssertionError: True is not false ================================== 1 failed in X.YZs ===================================I hold ctrl (Windows/Linux) or
option or command(MacOS) on the keyboard and use the mouse to click ontests/test_type_error.py:7to open it in the editor-
7 self.assertFalse(False)the test passes
test_type_error_w_the_uncallables
There are objects that can NOT be called
RED: make it fail
I add an import statement at the top of
test_type_error.py1import src.type_error 2import unittestI change
test_failuretotest_type_error_w_the_uncallableswith a failing line5class TestTypeError(unittest.TestCase): 6 7 def test_type_error_w_the_uncallables(self): 8 src.type_error.none()the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'none'there is nothing in
type_error.pyin thesrcfolder yetI add AttributeError to the list of Exceptions seen
11# Exceptions seen 12# AssertionError 13# AttributeError
GREEN: make it pass
I open
type_error.pyfrom thesrcfolder in the editor of my Integrated Development Environment (IDE), then add the name and point it to None1none = NoneTypeError: 'NoneType' object is not callablethe
()to the right ofsrc.type_error.nonemakes it a call
I add TypeError to the list of Exceptions seen in
test_type_error.py11# Exceptions seen 12# AssertionError 13# AttributeError 14# TypeErrorI make
nonea function intype_error.pyto make it callable1def none(): 2 return Nonethe test passes
REFACTOR: make it better
I add another failing line to
test_type_error.py7 def test_type_error_w_the_uncallables(self): 8 src.type_error.none() 9 src.type_error.false()the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'false'nothing is named
falseintype_error.pyI add the name to
type_error.pyand point it to False1def none(): 2 return None 3 4 5false = FalseTypeError: 'bool' object is not callableI change the variable to a function
1def none(): 2 return None 3 4def false(): 5 return Falsethe terminal shows green again
I add a line to test the other boolean in
test_type_error.py7 def test_type_error_w_the_uncallables(self): 8 src.type_error.none() 9 src.type_error.false() 10 src.type_error.true()the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'true'there is nothing named
trueintype_error.pyI add the name and point it to True in
type_error.py5def false(): 6 return False 7 8 9true = TrueTypeError: 'bool' object is not callableI make it a function
5def false(): 6 return False 7 8 9def true(): 10 return Truethe test passes. I can call a function, I cannot call a boolean or None
I add a line for a string
7 def test_type_error_w_the_uncallables(self): 8 src.type_error.none() 9 src.type_error.false() 10 src.type_error.true() 11 src.type_error.a_string()the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'a_string'I add the name and point it to a string in
type_error.py9def true(): 10 return True 11 12 13a_string = 'a string'TypeError: 'str' object is not callableI change
a_stringto a function9def true(): 10 return True 11 12 13def a_string(): 14 return 'a string'the test passes. I can call a function. I cannot call a string, a boolean or None
I add a failing line for a tuple (anything in parentheses
(), separated by a comma)7 def test_type_error_w_the_uncallables(self): 8 src.type_error.none() 9 src.type_error.false() 10 src.type_error.true() 11 src.type_error.a_string() 12 src.type_error.a_tuple()the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'a_tuple'I add the name and point it to a tuple
13def a_string(): 14 return 'a string' 15 16 17a_tuple = (1, 2, 3, 'n')TypeError: 'tuple' object is not callableI change it to a function
13def a_string(): 14 return 'a string' 15 16 17def a_tuple(): 18 return (1, 2, 3, 'n')the test passes. I can call a function. I cannot call a tuple, string, boolean or None
I add another line to
test_type_error.py7 def test_type_error_w_the_uncallables(self): 8 src.type_error.none() 9 src.type_error.false() 10 src.type_error.true() 11 src.type_error.a_string() 12 src.type_error.a_tuple() 13 src.type_error.a_list()the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'a_list'I add the name and point it to a list in
type_error.py17def a_tuple(): 18 return (1, 2, 3, 'n') 19 20 21a_list = [1, 2, 3, 'n']TypeError: 'list' object is not callableI change
a_listto a function17def a_tuple(): 18 return (1, 2, 3, 'n') 19 20 21def a_list(): 22 return [1, 2, 3, 'n']the test passes. I can call a function, I cannot call a list, tuple, string, boolean or None
I add another failing line to
test_type_error.py11 src.type_error.a_string() 12 src.type_error.a_tuple() 13 src.type_error.a_list() 14 src.type_error.a_set()the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'a_set'I add the name and point it to a set in
type_error.py21def a_list(): 22 return [1, 2, 3, 'n'] 23 24 25a_set = {1, 2, 3, 'n'}TypeError: 'set' object is not callableI make it a function
21def a_list(): 22 return [1, 2, 3, 'n'] 23 24 25def a_set(): 26 return {1, 2, 3, 'n'}the test passes. I can call a function. I cannot call a set, list, tuple, string, boolean or None
I add the last failing line for this test to
test_type_error.py12 src.type_error.a_tuple() 13 src.type_error.a_list() 14 src.type_error.a_set() 15 src.type_error.a_dictionary() 16 17 18# Exceptions seenthe terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'a_dictionary'I add the name and point it to a dictionary in
type_error.py25def a_set(): 26 return {1, 2, 3, 'n'} 27 28 29a_dictionary = {'key': 'value'}TypeError: 'dict' object is not callableI change it to a function
13def a_set(): 14 return {1, 2, 3, 'n'} 15 16 17def a_dictionary(): 18 return {'key': 'value'}the terminal shows green again. I can call a function. I cannot call a dictioanry, set, list, tuple, string, boolean or None
It is safe to say that I cannot call data structures because they are not callable. I can call functions, they are callable
test_type_error_w_function_signatures
When I call a function I have to match its definition also known as its signature or I get TypeError
RED: make it fail
I add a new test to
test_type_error.py12 src.type_error.a_dictionary() 13 14 def test_type_error_w_function_signatures(self): 15 src.type_error.function_00('a') 16 17 18# Exceptions seenthe terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'function_00'
GREEN: make it pass
I add the function to
type_error.py17def a_dictionary(): 18 return {'key': 'value'} 19 20 21def function_00(): 22 return NoneTypeError: function_00() takes 0 positional arguments but 1 was givenbecause
function_00is called with'a'as input and the definition does not allow any inputsI add a name in parentheses to the function definition
21def function_00(the_input): 22 return Nonethe test passes
I have to call a function in a way that matches its definition or I get TypeError
REFACTOR: make it better
I add a new failing line to
test_type_error.py14def test_type_error_w_function_signatures(self): 15 src.type_error.function_00('a') 16 src.type_error.function_01('a', 'b')the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'function_01'. Did you mean: 'function_00'?I add the function to
type_error.py21def function_00(the_input): 22 return None 23 24 25def function_01(the_input): 26 return NoneTypeError: function_01() takes 1 positional argument but 2 were giventhe definition only allows one input, and the test sent two
I change the first name, then add another name in parentheses so that the call to the function and its definition match
25def function_01(first, second): 26 return Nonethe test passes
I add another failing line to
test_type_error.py14 def test_type_error_w_function_signatures(self): 15 src.type_error.function_00('a') 16 src.type_error.function_01('a', 'b') 17 src.type_error.function_02('a', 'b', 'c')the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'function_02'. Did you mean: 'function_00'?I add the function to
type_error.py25def function_01(first, second): 26 return None 27 28 29def function_02(first, second): 30 return NoneTypeError: function_02() takes 2 positional arguments but 3 were givenI change the name of the first input, then add another name in parentheses to make the number of inputs match in
type_error.py29def function_02(first, second, third): 30 return Nonethe test passes
I add one more failing line in
test_type_error.py14 def test_type_error_w_function_signatures(self): 15 src.type_error.function_00('a') 16 src.type_error.function_01('a', 'b') 17 src.type_error.function_02('a', 'b', 'c') 18 src.type_error.function_03('a', 'b', 'c', 'd')the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'function_03'. Did you mean: 'function_00'?I add the function to
type_error.py29def function_02(first, second, third): 30 return None 31 32 33def function_03(first, second, third): 34 return NoneTypeError: function_03() takes 3 positional arguments but 4 were givenI add a 4th name in parentheses to the definition
33def function_03(first, second, third, fourth): 34 return Nonethe test passes
I have to call a function with the same number of inputs its definition expects
test_type_error_w_objects_that_do_not_mix
Some operations do not work if the objects are not of the same type
RED: make it fail
I add a new test with a failing line in test_type_error.py
18 src.type_error.function_03('a', 'b', 'c', 'd')
19
20 def test_type_error_w_objects_that_do_not_mix(self):
21 None + 1
22
23
24# Exceptions seen
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
I cannot do arithmetic with None
GREEN: make it pass
I add the assertRaises method
20 def test_type_error_w_objects_that_do_not_mix(self):
21 with self.assertRaises(TypeError):
22 None + 1
the test passes
REFACTOR: make it better
I add another failing line to the test
20 def test_type_error_w_objects_that_do_not_mix(self): 21 with self.assertRaises(TypeError): 22 None + 1 23 'text' + 0.1TypeError: can only concatenate str (not "float") to strI use assertRaises to handle the Exception
20 def test_type_error_w_objects_that_do_not_mix(self): 21 with self.assertRaises(TypeError): 22 None + 1 23 with self.assertRaises(TypeError): 24 'text' + 0.1the test passes
I add another failing line
20 def test_type_error_w_objects_that_do_not_mix(self): 21 with self.assertRaises(TypeError): 22 None + 1 23 with self.assertRaises(TypeError): 24 'text' + 0.1 25 (1, 2, 3, 'n') - {1, 2, 3, 'n'}TypeError: unsupported operand type(s) for -: 'tuple' and 'set'I add assertRaises
20 def test_type_error_w_objects_that_do_not_mix(self): 21 with self.assertRaises(TypeError): 22 None + 1 23 with self.assertRaises(TypeError): 24 'text' + 0.1 25 with self.assertRaises(TypeError): 26 (1, 2, 3, 'n') - {1, 2, 3, 'n'} 27 28# Exceptions seenthe terminal shows all tests are passing
close the project
I close the file(s) I have open in the editor(s)
I click in the terminal and exit the tests with ctrl+c on the keyboard
the terminal shows
.../pumping_pythonI am back in the
pumping_pythondirectoryNote
on Windows without Windows Subsystem for Linux
I deactivate the virtual environment
deactivatethe terminal goes back to the command line,
(.venv)is no longer on the left side...\pumping_python\type_errorI change directory to the parent of
type_errorcd ..the terminal shows
...\pumping_pythonI am back in the
pumping_pythondirectory
test_calculator_raises_type_error
I want to use TypeError with exception handlers to make sure that the calculator program only works with numbers. Calculators in the real world only work with numbers
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/activateon Windows without Windows Subsystem for Linux use
.venv/bin/activate.ps1instead ofsource .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 4 items tests/test_calculator.py .... [100%] ============================ 4 passed in X.YZs =============================I hold ctrl on the keyboard and click on
tests/test_calculator.pyto open it in the editor
RED: make it fail
I add a new failing test to show that the calculator raises TypeError when one of the inputs is None, just like in test_type_error_w_objects_that_do_not_mix
42 def test_division(self):
43 try:
44 self.assertEqual(
45 src.calculator.divide(
46 self.random_first_number,
47 self.random_second_number
48 ),
49 self.random_first_number/self.random_second_number
50 )
51 except ZeroDivisionError:
52 self.assertEqual(
53 src.calculator.divide(self.random_first_number, 0),
54 'undefined: I cannot divide by 0'
55 )
56
57 def test_calculator_raises_type_error_when_given_none(self):
58 src.calculator.add(None, None)
59
60
61# Exceptions seen
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'
GREEN: make it pass
I add assertRaises
57 def test_calculator_raises_type_error_when_given_none(self):
58 with self.assertRaises(TypeError):
59 src.calculator.add(None, None)
the test passes
REFACTOR: make it better
I add a failing line for division
44 def test_calculator_raises_type_error_when_given_none(self): 45 with self.assertRaises(TypeError): 46 src.calculator.add(None None) 47 src.calculator.divide(None, None)TypeError: unsupported operand type(s) for /: 'NoneType' and 'NoneType'I add assertRaises
58 with self.assertRaises(TypeError): 59 src.calculator.add(self.random_first_number, None) 60 with self.assertRaises(TypeError): 61 src.calculator.divide(self.random_first_number, None)the test passes
I add another failing line, this time for multiplication
60 with self.assertRaises(TypeError): 61 src.calculator.divide(None, None) 62 src.calculator.multiply(None, None)TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'I add assertRaises
60 with self.assertRaises(TypeError): 61 src.calculator.divide(None, None) 62 with self.assertRaises(TypeError): 63 src.calculator.multiply(None, None)the test passes
I add another one for subtraction
62 with self.assertRaises(TypeError): 63 src.calculator.multiply(None, None) 64 src.calculator.subtract(None, None)TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'I add the assertRaises method
57 def test_calculator_raises_type_error_when_given_none(self): 58 with self.assertRaises(TypeError): 59 src.calculator.add(None, None) 60 with self.assertRaises(TypeError): 61 src.calculator.divide(None, None) 62 with self.assertRaises(TypeError): 63 src.calculator.multiply(None, None) 64 with self.assertRaises(TypeError): 65 src.calculator.subtract(None, None) 66 67 68# Exceptions seenthe test passes
The calculator raises TypeError when given None as input. What does it do when the input is a boolean, string, tuple, list, set or a dictionary?
test_calculator_raises_type_error_when_given_strings
RED: make it fail
I add a new test with an assertion from test_what_is_an_assertion to test the add function with strings
64 with self.assertRaises(TypeError):
65 src.calculator.subtract(None, None)
66
67 def test_calculator_with_strings(self):
68 self.assertEqual(src.calculator.add('1', '1'), '2')
69
70
71# Exceptions seen
the terminal shows AssertionError
AssertionError: '11' != '2'
GREEN: make it pass
I change the expectation to match reality
68 self.assertEqual(src.calculator.add('1', '1'), '11')
the test passes
REFACTOR: make it better
I add an assertion for the divide function
67 def test_calculator_with_strings(self): 68 self.assertEqual(src.calculator.add('1', '1'), '11') 69 self.assertEqual(src.calculator.divide('1', '1'), '11')TypeError: unsupported operand type(s) for /: 'str' and 'str'I change assertEqual to assertRaises
68 def test_calculator_with_strings(self): 69 self.assertEqual(src.calculator.add('1', '1'), '11') 70 with self.assertRaises(TypeError): 71 src.calculator.divide('1', '1')the test passes
I try it with the multiply function
69 with self.assertRaises(TypeError): 70 src.calculator.divide('1', '1') 71 src.calculator.multiply('1', '1')TypeError: can't multiply sequence by non-int of type 'str'I add assertRaises
69 with self.assertRaises(TypeError): 70 src.calculator.divide('1', '1') 71 with self.assertRaises(TypeError): 72 src.calculator.multiply('1', '1')I add an assertion for the subtract function
71 with self.assertRaises(TypeError): 72 src.calculator.multiply('1', '1') 73 src.calculator.subtract('1', '1')TypeError: unsupported operand type(s) for -: 'str' and 'str'I add assertRaises
57 def test_calculator_raises_type_error_when_given_none(self): 58 with self.assertRaises(TypeError): 59 src.calculator.add(None, None) 60 with self.assertRaises(TypeError): 61 src.calculator.divide(None, None) 62 with self.assertRaises(TypeError): 63 src.calculator.multiply('1', '1') 64 with self.assertRaises(TypeError): 65 src.calculator.subtract('1', '1')the test passes
how to test if something is an instance of an object in a program
I want the add function to raise TypeError when it gets a string, the same way the other functions raise TypeError when one of the inputs is a string. I can use the isinstance function which is like the assertIsInstance method from when I tested None
I change the assertEqual in
test_calculator_with_stringsto assertRaises intest_calculator.py67 def test_calculator_with_strings(self): 68 with self.assertRaises(TypeError): 69 src.calculator.add('1', '1') 70 with self.assertRaises(TypeError): 71 src.calculator.divide('1', '1')the terminal shows AssertionError
AssertionError: TypeError not raisedthen I add an if statement to the add function in
calculator.py16def add(first_input, second_input): 17 if isinstance(first_input, str) or isinstance(second_input, str): 18 raise TypeError( 19 'Excuse me?! I only work with numbers, Please try again...' 20 ) 21 else: 22 return first_input + second_inputthe test passes
Note
the isinstance function like the assertIsInstance method checks if the first input it is given is an instance (child) of the object it is given as the second input. It is part of Python’s Built-in Functions
the if statement
if isinstance(first_input, str) or isinstance(second_input, str):is True ifthe statement is only False if the two inputs are not strings. Remember Logical Disjunction from the Truth Table, which only returns False when the two inputs are False?
I change the name of the test to be clearer
67 def test_calculator_raises_type_error_when_given_strings(self): 68 with self.assertRaises(TypeError): 69 src.calculator.add('1', '1') 70 with self.assertRaises(TypeError): 71 src.calculator.divide('1', '1') 72 with self.assertRaises(TypeError): 73 src.calculator.multiply('1', '1') 74 with self.assertRaises(TypeError): 75 src.calculator.subtract('1', '1') 76 77 78# Exceptions seen
test_calculator_sends_message_when_input_is_not_a_number
I want the calculator functions to send a message when the input is not a number, not raise TypeError which causes the program to stop. I want the user to be able to try again with different input
RED: make it fail
I change the assertRaises to assertEqual for the add function in test_calculator_raises_type_error_when_given_none
57 def test_calculator_raises_type_error_when_given_none(self):
58 self.assertEqual(
59 src.calculator.add(None, None),
60 'Excuse me?! I only work with numbers, Please try again...'
61 )
62 with self.assertRaises(TypeError):
63 src.calculator.divide(None, None)
64 with self.assertRaises(TypeError):
65 src.calculator.multiply(None, None)
66 with self.assertRaises(TypeError):
67 src.calculator.subtract(None, None)
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'
GREEN: make it pass
I add an exception handler to the else clause of the add function in calculator.py
16def add(first_input, second_input):
17 if isinstance(first_input, str) or isinstance(second_input, str):
18 raise TypeError(
19 'Excuse me?! I only work with numbers, Please try again...'
20 )
21 else:
22 try:
23 return first_input + second_input
24 except TypeError:
25 return 'Excuse me?! I only work with numbers, Please try again...'
the test passes
REFACTOR: make it better
I want the same thing to happen when the add function gets a string as input. I change the assertRaises to assertEqual for the add function in
test_calculator_raises_type_error_when_given_stringsintest_calculator.py70 def test_calculator_raises_type_error_when_given_strings(self): 71 self.assertEqual( 72 src.calculator.add('1', '1'), 73 'Excuse me?! I only work with numbers, Please try again...' 74 ) 75 with self.assertRaises(TypeError): 76 src.calculator.divide('1', '1')TypeError: Excuse me?! I only work with numbers, Please try again...I change the raise statement to a return statement in the add function in
calculator.py16def add(first_input, second_input): 17 if isinstance(first_input, str) or isinstance(second_input, str): 18 return 'Excuse me?! I only work with numbers, Please try again...' 19 else: 20 try: 21 return first_input + second_input 22 except TypeError: 23 return 'Excuse me?! I only work with numbers, Please try again...'the test passes
I make the same change to the assertRaises to assertEqual for the divide function in
test_calculator_raises_type_error_when_given_stringsintest_calculator.py70 def test_calculator_raises_type_error_when_given_strings(self): 71 self.assertEqual( 72 src.calculator.add('1', '1'), 73 'Excuse me?! I only work with numbers, Please try again...' 74 ) 75 self.assertEqual( 76 src.calculator.divide('1', '1'), 77 'Excuse me?! I only work with numbers, Please try again...' 78 )TypeError: unsupported operand type(s) for /: 'str' and 'str'I add another except clause to the exception handler in the divide function in
calculator.py9def divide(first_input, second_input): 10 try: 11 return first_input / second_input 12 except ZeroDivisionError: 13 return 'undefined: I cannot divide by 0' 14 except TypeError: 15 return 'Excuse me?! I only work with numbers, Please try again...'the terminal shows AssertionError
AssertionError: TypeError not raisedtest_calculator_raises_type_error_when_given_nonefails because it expects TypeError when the inputs are not numbersI change the assertRaises to assertEqual for the divide function in
test_calculator_raises_type_error_when_given_noneintest_calculator.py57def test_calculator_raises_type_error_when_given_none(self): 58 self.assertEqual( 59 src.calculator.add(None, None), 60 'Excuse me?! I only work with numbers, Please try again...' 61 ) 62 self.assertEqual( 63 src.calculator.divide(None, None), 64 'Excuse me?! I only work with numbers, Please try again...' 65 )the test passes
I change the assertRaises to assertEqual for the multiply function in
test_calculator_raises_type_error_when_given_none62 self.assertEqual( 63 src.calculator.divide(None, None), 64 'Excuse me?! I only work with numbers, Please try again...' 65 ) 66 self.assertEqual( 67 src.calculator.multiply(None, None), 68 'Excuse me?! I only work with numbers, Please try again...' 69 )TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'I add an exception handler to the multiply function in
calculator.py5def multiply(first_input, second_input): 6 try: 7 return first_input * second_input 8 except TypeError: 9 return 'Excuse me?! I only work with numbers, Please try again...'the terminal shows AssertionError
AssertionError: TypeError not raisedI change the assertRaises to assertEqual for the multiply function in
test_calculator_raises_type_error_when_given_stringsintest_calculator.py79 self.assertEqual( 80 src.calculator.divide('1', '1'), 81 'Excuse me?! I only work with numbers, Please try again...' 82 ) 83 self.assertEqual( 84 src.calculator.multiply('1', '1'), 85 'Excuse me?! I only work with numbers, Please try again...' 86 )the test passes
I change the assertRaises to assertEqual for the subtract function in
test_calculator_raises_type_error_when_given_strings74 def test_calculator_raises_type_error_when_given_strings(self): 75 self.assertEqual( 76 src.calculator.add('1', '1'), 77 'Excuse me?! I only work with numbers, Please try again...' 78 ) 79 self.assertEqual( 80 src.calculator.divide('1', '1'), 81 'Excuse me?! I only work with numbers, Please try again...' 82 ) 83 self.assertEqual( 84 src.calculator.multiply('1', '1'), 85 'Excuse me?! I only work with numbers, Please try again...' 86 ) 87 self.assertEqual( 88 src.calculator.subtract('1', '1'), 89 'Excuse me?! I only work with numbers, Please try again...' 90 ) 91 92 93# Exceptions seenTypeError: unsupported operand type(s) for -: 'str' and 'str'I add an exception handler to the subtract function in
calculator.py1def subtract(first_input, second_input): 2 try: 3 return first_input - second_input 4 except TypeError: 5 return 'Excuse me?! I only work with numbers, Please try again...'the terminal shows AssertionError
AssertionError: TypeError not raisedI change the assertRaises to assertEqual for the subtract function in
test_calculator_raises_type_error_when_given_noneintest_calculator.py57 def test_calculator_raises_type_error_when_given_none(self): 58 self.assertEqual( 59 src.calculator.add(None, None), 60 'Excuse me?! I only work with numbers, Please try again...' 61 ) 62 self.assertEqual( 63 src.calculator.divide(None, None), 64 'Excuse me?! I only work with numbers, Please try again...' 65 ) 66 self.assertEqual( 67 src.calculator.multiply(None, None), 68 'Excuse me?! I only work with numbers, Please try again...' 69 ) 70 self.assertEqual( 71 src.calculator.subtract(None, None), 72 'Excuse me?! I only work with numbers, Please try again...' 73 ) 74 75 76 def test_calculator_raises_type_error_when_given_strings(self):the test passes
That was a lot of doing the same thing over and over again.
test_calculator_raises_type_error_when_given_noneandtest_calculator_raises_type_error_when_given_stringsboth look the same and the calculator no longer raises TypeError when any of the inputs are NOT a number. I remove the name oftest_calculator_raises_type_error_when_given_stringsto make its assertions part oftest_calculator_raises_type_error_when_given_none70 self.assertEqual( 71 src.calculator.subtract(None, None), 72 'Excuse me?! I only work with numbers, Please try again...' 73 ) 74 self.assertEqual( 75 src.calculator.add('1', '1'), 76 'Excuse me?! I only work with numbers, Please try again...' 77 ) 78 self.assertEqual( 79 src.calculator.divide('1', '1'), 80 'Excuse me?! I only work with numbers, Please try again...' 81 ) 82 self.assertEqual( 83 src.calculator.multiply('1', '1'), 84 'Excuse me?! I only work with numbers, Please try again...' 85 ) 86 self.assertEqual( 87 src.calculator.subtract('1', '1'), 88 'Excuse me?! I only work with numbers, Please try again...' 89 ) 90 91 92# Exceptions seenI change the name from
test_calculator_raises_type_error_when_given_nonetotest_calculator_sends_message_when_input_is_not_a_numberto be clearer57 def test_calculator_sends_message_when_input_is_not_a_number(self): 58 self.assertEqual(the tests are still green
I have the same error message 8 times in this test. I can use a variable to make it better
57 def test_calculator_sends_message_when_input_is_not_a_number(self): 58 error_message = 'Excuse me?! I only work with numbers, Please try again...' 59 self.assertEqual( 60 src.calculator.add(None, None), 61 error_message 62 ) 63 self.assertEqual( 64 src.calculator.divide(None, None), 65 error_message 66 ) 67 self.assertEqual( 68 src.calculator.multiply(None, None), 69 error_message 70 ) 71 self.assertEqual( 72 src.calculator.subtract(None, None), 73 error_message 74 ) 75 self.assertEqual( 76 src.calculator.add('1', '1'), 77 error_message 78 ) 79 self.assertEqual( 80 src.calculator.divide('1', '1'), 81 error_message 82 ) 83 self.assertEqual( 84 src.calculator.multiply('1', '1'), 85 error_message 86 ) 87 self.assertEqual( 88 src.calculator.subtract('1', '1'), 89 error_message 90 )still green. All these tests assertions the same
self.assertEqual( src.calculator.function(NOT_A_NUMBER, ALSO_NOT_A_NUMBER), error_message )there has to be a better way to test the calculator with inputs that are NOT numbers
how to make a decorator function
All the functions in the calculator program have the same exception handler
try:
something
except TypeError:
return 'Excuse me?! I only work with numbers, Please try again...'
the divide function is different because it has another except clause
except ZeroDivisionError:
return 'undefined: I cannot divide by 0'
the other part that is different for all the functions are the calculations
return first_input - second_input
return first_input * second_input
return first_input /
return first_input + second_input
what is a decorator function?
A decorator or wrapper function takes another functions as input and returns a function. I can use it to remove the exception handler that is the same in all of the calculator functions
I add a new function to
calculator.py1def only_takes_numbers(function): 2 def wrapper(first_input, second_input): 3 try: 4 return function(first_input, second_input) 5 except TypeError: 6 return 'Excuse me?! I only work with numbers, Please try again...' 7 return wrapper 8 9 10def subtract(first_input, second_input):I use it to wrap the subtract function
10@only_takes_numbers 11def subtract(first_input, second_input): 12 try:the test is still green
I remove the parts that are also in the
only_takes_numbersfunction15@only_takes_numbers 16def subtract(first_input, second_input): 17 return first_input - second_inputstill green
I do the same thing with the multiply function
15@only_takes_numbers 16def multiply(first_input, second_input): 17 try:the terminal shows green
I remove the parts that are also in the
only_takes_numbersfunction15@only_takes_numbers 16def multiply(first_input, second_input): 17 return first_input * second_inputthe tests are still passing
on to the divide function
20@only_takes_numbers 21def divide(first_input, second_input): 22 try:still green
I remove the except clause for TypeError
2@only_takes_numbers 3def divide(first_input, second_input): 4 try: 5 return first_input / second_input 6 except ZeroDivisionError: 7 return 'undefined: I cannot divide by 0'all the tests are still green
one more to go, I wrap the add function with the
only_takes_numbersFunction definitions28@only_takes_numbers 29def add(first_input, second_input): 30 if isinstance(first_input, str) or isinstance(second_input, str):the test is still passing
I remove the exception handler from the else clause
28@only_takes_numbers 29def add(first_input, second_input): 30 if isinstance(first_input, str) or isinstance(second_input, str): 31 return 'Excuse me?! I only work with numbers, Please try again...' 32 else: 33 return first_input + second_inputgreen! Lovely!
I can have fun and make a function for the condition in the if statement in the add function in
calculator.py28def is_string(something): 29 return isinstance(something, str) 30 31 32@only_takes_numbers 33def add(first_input, second_input): 34 if is_string(first_input) or is_string(second_input): 35 return 'Excuse me?! I only work with numbers, Please try again...' 36 else: 37 return first_input + second_inputthe test is still green
This removes the duplication of
strin the call to the isinstance functionisinstance(first_input, str) isinstance(second_input, str):it adds 2 lines of code to remove 6 characters. WOW!
I can make a function for the whole if statement in the add function
28def one_input_is_a_string(first_input, second_input): 29 return isinstance(first_input, str) or isinstance(second_input, str) 30 31 32@only_takes_numbers 33def add(first_input, second_input): 34 if one_input_is_a_string(first_input, second_input): 35 return 'Excuse me?! I only work with numbers, Please try again...' 36 else: 37 return first_input + second_inputthe test is still green.
the
one_input_is_a_stringfunction looks the same as Logical Disjunction from the Truth Tablethis makes it easier to change the condition later without touching the add function
it still adds 2 lines of code
I can also make a decorator function for the if statement<if statements> to practice making them
1def reject_strings(function): 2 def wrapper(first_input, second_input): 3 if isinstance(first_input, str) or isinstance(second_input, str): 4 return 'Excuse me?! I only work with numbers, Please try again...' 5 else: 6 return function(first_input, second_input) 7 return wrapper 8 9 10def only_takes_numbers(function):then use it to wrap the add function
37@reject_strings 38@only_takes_numbers 39def add(first_input, second_input):the test is still green
I remove the if statement from the add function
37@reject_strings 38@only_takes_numbers 39def add(first_input, second_input): 40 return first_input + second_inputthe test is still green
the
reject_stringsandonly_takes_numbersfunctions have parts that are the samedef wrapper(first_input, second_input): ... return 'Excuse me?! I only work with numbers, Please try again...' ... return function(first_input, second_input) return wrapperI add the if statement to the
only_takes_numbersfunction10def only_takes_numbers(function): 11 def wrapper(first_input, second_input): 12 if isinstance(first_input, str) or isinstance(second_input, str): 13 return 'Excuse me?! I only work with numbers, Please try again...' 14 else: 15 try: 16 return function(first_input, second_input) 17 except TypeError: 18 return 'Excuse me?! I only work with numbers, Please try again...' 19 return wrapperthe test is still green
I remove the
reject_stringsdecoration from the add function37 return 'undefined: I cannot divide by 0' 38 39 40@only_takes_numbers 41def add(first_input, second_input): 42 return first_input + second_inputgreen
I remove the
reject_stringsfunction1def only_takes_numbers(function):all the tests are still passing. The world is my oyster!
close the project
I close
test_calculator.pyandcalculator.pyin the editor(s)I click in the terminal and exit the tests with ctrl+c on the keyboard
I 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
The calculator program can take 2 inputs and check if they are both numbers, add, subtract, multiply and divide them
Even though the program says it only works with numbers, I did not add tests for tuples, lists, sets, and dictionaries, though they are touched in test_type_error_w_objects_that_do_not_mix Do you want to add them?
I ran tests for TypeError with
code from the chapter
what is next?
you know