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_non_callables(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 Encountered
30# AssertionError
31# AttributeError
32# TypeError
requirements¶
I open a terminal to run makePythonTdd.sh with
type_erroras the name of the project./makePythonTdd.sh type_erroron Windows without Windows Subsystem for Linux use makePythonTdd.ps1 instead of makePythonTdd.sh
./makePythonTdd.ps1 type_error
it makes the folders and files that are needed, installs packages, runs the first test, and the terminal shows AssertionError
E AssertionError: True is not false tests/test_type_error.py:7: AssertionError
I hold
ctrl(Windows/Linux) oroption or command(MacOS) on the keyboard and use the mouse to click ontests/test_type_error.py:7to open it in the editorthen I change True to False to make the test pass
7 self.assertFalse(False)I change the name of the class to match the CapWords format to follow Python convention
4class TestTypeError(unittest.TestCase):
test_type_error_w_non_callables¶
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 unittest 2import src.type_error
I change
test_failuretotest_type_error_w_non_callables5class TestTypeError(unittest.TestCase): 6 7 def test_type_error_w_non_callables(self): 8 src.type_error.none()
the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'none'
I add it to the list of Exceptions encountered
11# Exceptions Encountered 12# AssertionError 13# AttributeError
GREEN: make it pass¶
I open
type_error.pyfrom thesrcfolder to open it in the editor of my Integrated Development Environment (IDE), then add the name and point it to None1none = NoneTypeError: 'NoneType' object is not callable
the
()to the right ofsrc.type_error.nonemakes it a call, and the namenonepoints to None which is NOT callableI add the error to the list of Exceptions encountered in
test_type_error.py11# Exceptions Encountered 12# AssertionError 13# AttributeError 14# TypeError
I make
nonea function intype_error.pyto make it callable1def none(): 2 return None
the test passes
REFACTOR: make it better¶
I add another failing line to
test_type_error.py7 def test_type_error_w_non_callables(self): 8 src.type_error.none() 9 src.type_error.false()
the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'false'
I add the name to
type_error.pyand point it to False1def none(): 2 return None 3 4 5false = False
TypeError: 'bool' object is not callable
I make the variable a function
1def none(): 2 return None 3 4def false(): 5 return False
the terminal shows green again
I add a line to test the other boolean in
test_type_error.py7 def test_type_error_w_non_callables(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'
I add the name and point it to True in
type_error.py5def false(): 6 return False 7 8 9true = True
TypeError: 'bool' object is not callable
I make it a function
5def false(): 6 return False 7 8 9def true(): 10 return True
the test passes. I can call a function but I cannot call a boolean
I add another line to
test_type_error.py7 def test_type_error_w_non_callables(self): 8 src.type_error.none() 9 src.type_error.false() 10 src.type_error.true() 11 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.py9def true(): 10 return True 11 12 13a_list = [1, 2, 3, 'n']
TypeError: 'list' object is not callable
I make a_list a function
9def true(): 10 return True 11 12 13def a_list(): 14 return [1, 2, 3, 'n']
the test passes. I can call a function but I cannot call a list
I add a new failing line to
test_type_error.py9 src.type_error.false() 10 src.type_error.true() 11 src.type_error.a_list() 12 src.type_error.a_dictionary() 13 14 15# Exceptions Encountered
the terminal shows AttributeError
AttributeError: module 'src.type_error' has no attribute 'a_dictionary'
I add the name to and point it to a dictionary in
type_error.py13def a_list(): 14 return [1, 2, 3, 'n'] 15 16 17a_dictionary = {'key': 'value'}
TypeError: 'dict' object is not callable
I change it to a function
13def a_list(): 14 return [1, 2, 3, 'n'] 15 16 17def a_dictionary(): 18 return {'key': 'value'}
the terminal shows green again.
It is safe to say that I cannot call data structures but I can call functions
test_type_error_w_function_signatures¶
When I call a function I have to match its definition also known as its signature
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 Encountered
the 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 None
TypeError: function_00() takes 0 positional arguments but 1 was given
because
function_00is called with'a'as input but the definition does not accept any inputsI add a name in parentheses to the function definition
21def function_00(the_input): 22 return None
the 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 None
TypeError: function_01() takes 1 positional argument but 2 were given
I add another name in parentheses so that the call to the function and its definition match
25def function_01(first_input, second_input): 26 return None
the 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_input, second_input): 26 return None 27 28 29def function_02(first_input, second_input): 30 return None
TypeError: function_02() takes 2 positional arguments but 3 were given
I add another name in parentheses to make the number of inputs match in
type_error.p29def function_02(first_input, second_input, input_3): 30 return None
the 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_input, second_input, input_3): 30 return None 31 32 33def function_03(first_input, second_input, input_3): 34 return None
TypeError: function_03() takes 3 positional arguments but 4 were given
I add a 4th name in parentheses to the definition
33def function_03(first_input, second_input, input_3, input_4): 34 return None
the 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 Encountered
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.1
TypeError: can only concatenate str (not "float") to str
I cannot add something that is not a string to a string. I 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.1
the 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 Encountered
the terminal shows all tests are passing
test_calculator_raises_type_error¶
the calculator is needed for this part
I exit the tests by pressing
ctrl+con the keyboard in the terminalI change directory to the
calculatorfoldercd calculatorthe terminal shows
.../pumping_python/calculator
I activate the virtual environment
source .venv/bin/activatethe terminal shows
(.venv) .../pumping_python/calculator
I run the tests with pytest-watch
pytest-watchthe terminal shows
======================== test session starts ========================= platform linux -- Python 3.X.7, pytest-9.0.1, pluggy-1.6.0 rootdir: /workspaces/pumping_python/pumping_python/calculator collected 4 items tests/test_calculator.py .... [100%] ========================= 4 passed in 0.01s ==========================
I hold
ctrl(Windows/Linux) oroption or command(MacOS) on the keyboard and use the mouse to click ontests/test_calculator.pyto open it in the editor
RED: make it fail¶
I add a new failing test to show that I can NOT do an arithmetic operation with something that is not a number
33 def test_division(self):
34 while self.random_y == 0:
35 with self.assertRaises(ZeroDivisionError):
36 src.calculator.divide(self.random_x, self.random_y)
37 self.random_y = a_random_number()
38 else:
39 self.assertEqual(
40 src.calculator.divide(self.random_x, self.random_y),
41 self.random_x/self.random_y
42 )
43
44 def test_calculator_raises_type_error(self):
45 src.calculator.add(self.random_x, None)
46
47
48# Exceptions Encountered
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
GREEN: make it pass¶
I add assertRaises
44 def test_calculator_raises_type_error(self):
45 with self.assertRaises(TypeError):
46 src.calculator.add(self.random_x, None)
the test passes
REFACTOR: make it better¶
I add a failing line for division
44 def test_calculator_raises_type_error(self): 45 with self.assertRaises(TypeError): 46 src.calculator.add(self.random_x, None) 47 src.calculator.divide(self.random_x, None)
TypeError: unsupported operand type(s) for /: 'int' and 'NoneType'
I add assertRaises
44 def test_calculator_raises_type_error(self): 45 with self.assertRaises(TypeError): 46 src.calculator.add(self.random_x, None) 47 with self.assertRaises(TypeError): 48 src.calculator.divide(self.random_x, None)
the test passes
I add another failing line, this time for multiplication
44 def test_calculator_raises_type_error(self): 45 with self.assertRaises(TypeError): 46 src.calculator.add(self.random_x, None) 47 with self.assertRaises(TypeError): 48 src.calculator.divide(self.random_x, None) 49 src.calculator.multiply(self.random_x, None)
TypeError: unsupported operand type(s) for *: 'int' and 'NoneType'
I add assertRaises
44 def test_calculator_raises_type_error(self): 45 with self.assertRaises(TypeError): 46 src.calculator.add(self.random_x, None) 47 with self.assertRaises(TypeError): 48 src.calculator.divide(self.random_x, None) 49 with self.assertRaises(TypeError): 50 src.calculator.multiply(self.random_x, None)
the test passes
I add another one for subtraction
44 def test_calculator_raises_type_error(self): 45 with self.assertRaises(TypeError): 46 src.calculator.add(self.random_x, None) 47 with self.assertRaises(TypeError): 48 src.calculator.divide(self.random_x, None) 49 with self.assertRaises(TypeError): 50 src.calculator.multiply(self.random_x, None) 51 src.calculator.subtract(self.random_x, None)
TypeError: unsupported operand type(s) for -: 'int' and 'NoneType'
I add the assertRaises method
44 def test_calculator_raises_type_error(self): 45 with self.assertRaises(TypeError): 46 src.calculator.add(self.random_x, None) 47 with self.assertRaises(TypeError): 48 src.calculator.divide(self.random_x, None) 49 with self.assertRaises(TypeError): 50 src.calculator.multiply(self.random_x, None) 51 with self.assertRaises(TypeError): 52 src.calculator.subtract(self.random_x, None) 53 54 55# Exceptions Encountered
the test passes
how to check if input is good¶
I want to add a condition to the calculator to make sure that what the functions receive are numbers. I can do this with TypeError
I go to the explorer in the Integrated Development Environment (IDE) to open the
calculatorfolderI open the
srcfolder and click oncalculator.pyto open it in the editor
RED: make it fail¶
I add an exception handler to the add function in calculator.py
13def add(input_1, input_2):
14 try:
15 return input_1 + input_2
16 except TypeError:
17 return 'I only work with numbers'
the terminal shows AssertionError
AssertionError: TypeError not raised
because the add function now sends a message when TypeError is raised
GREEN: make it pass¶
I change the assertRaises to assertEqual in test_calculator.py
44def test_calculator_raises_type_error(self):
45 self.assertEqual(
46 src.calculator.add(self.random_x, None),
47 'I only work with numbers'
48 )
49 with self.assertRaises(TypeError):
50 src.calculator.divide(self.random_x, None)
51 with self.assertRaises(TypeError):
52 src.calculator.multiply(self.random_x, None)
53 with self.assertRaises(TypeError):
54 src.calculator.subtract(self.random_x, None)
the test passes
REFACTOR: make it better¶
I add an exception handler to the divide function in
calculator.py9def divide(input_1, input_2): 10 try: 11 return input_1 / input_2 12 except TypeError: 13 return 'I only work with numbers' 14 15 16def add(input_1, input_2):
the terminal shows AssertionError
AssertionError: TypeError not raised
because the
addfunction now sends a message when TypeError is raisedI change assertRaises to assertEqual in
test_calculator.py44 def test_calculator_raises_type_error(self): 45 self.assertEqual( 46 src.calculator.add(self.random_x, None), 47 'I only work with numbers' 48 ) 49 self.assertEqual( 50 src.calculator.divide(self.random_x, None), 51 'I only work with numbers' 52 ) 53 with self.assertRaises(TypeError): 54 src.calculator.multiply(self.random_x, None) 55 with self.assertRaises(TypeError): 56 src.calculator.subtract(self.random_x, None)
I have the same exception handler in both functions in
calculator.py. To follow The Do Not Repeat Yourself (DRY) Principle I can write a function that handles TypeError. The problem is how it takes care of the lines that change, that isresult = input_1 + input_2result = input_1 / input_2
I add a new function to
calculator.py1def handle_type_error(function): 2 def wrapper(input_1, input_2): 3 try: 4 return function(input_1, input_2) 5 except TypeError: 6 return 'I only work with numbers' 7 return wrapper 8 9 10def subtract(input_1, input_2):
this new function (
handle_type_error) takes a function as input. It has a function inside it namedwrapper, which runs the input function and handles TypeError when raisedI use the new function as a wrapper for the add function
18def divide(input_1, input_2): 19 try: 20 return input_1 / input_2 21 except TypeError: 22 return 'I only work with numbers' 23 24 25@handle_type_error 26def add(input_1, input_2): 27 return input_1 + input_2
the test is still green
I wrap the divide function as well
18@handle_type_error 19def divide(input_1, input_2): 20 return input_1 / input_2 21 22 23@handle_type_error 24def add(input_1, input_2): 25 return input_1 + input_2
the test is still green
I use the wrapper with the multiply function
14@handle_type_error 15def multiply(input_1, input_2): 16 return input_1 * input_2
the terminal shows AssertionError
AssertionError: TypeError not raised
I change the assertRaises to assertEqual in
test_calculator.py44 def test_calculator_raises_type_error(self): 45 self.assertEqual( 46 src.calculator.add(self.random_x, None), 47 'I only work with numbers' 48 ) 49 self.assertEqual( 50 src.calculator.divide(self.random_x, None), 51 'I only work with numbers' 52 ) 53 self.assertEqual( 54 src.calculator.multiply(self.random_x, None), 55 'I only work with numbers' 56 ) 57 with self.assertRaises(TypeError): 58 src.calculator.subtract(self.random_x, None)
the test passes
I add the wrapper to the subtract function
10@handle_type_error 11def subtract(input_1, input_2): 12 return input_1 - input_2 13 14 15@handle_type_error 16def multiply(input_1, input_2): 17 return input_1 * input_2
the terminal shows AssertionError
AssertionError: TypeError not raised
I change assertRaises to assertEqual in
calculator.py44 def test_calculator_raises_type_error(self): 45 self.assertEqual( 46 src.calculator.add(self.random_x, None), 47 'I only work with numbers' 48 ) 49 self.assertEqual( 50 src.calculator.divide(self.random_x, None), 51 'I only work with numbers' 52 ) 53 self.assertEqual( 54 src.calculator.multiply(self.random_x, None), 55 'I only work with numbers' 56 ) 57 self.assertEqual( 58 src.calculator.subtract(self.random_x, None), 59 'I only work with numbers' 60 ) 61 62 63# Exceptions Encountered
the test passes
I change the name of the test to be more descriptive
44 def test_calculator_sends_a_message_when_inputs_are_not_numbers(self): 45 self.assertEqual(
The
calculatornow uses TypeError to send a message when bad inputs are passed, but there is a case I have not consideredPython allows using the
+operator with strings but it does not work for the other arithmetic operations. I add a test to show this with the addition function57 self.assertEqual( 58 src.calculator.subtract(self.random_x, None), 59 'I only work with numbers' 60 ) 61 62 def test_calculator_with_strings(self): 63 self.assertEqual( 64 src.calculator.add('hello ', 'world'), 65 None 66 ) 67 68 69# Exceptions Encountered
the terminal shows
AssertionError: 'hello world' != None
I change the expectation to match
62 def test_calculator_with_strings(self): 63 self.assertEqual( 64 src.calculator.add('hello ', 'world'), 65 'hello world' 66 )
the test passes
I add another assertion for division
62 def test_calculator_with_strings(self): 63 self.assertEqual( 64 src.calculator.add('hello ', 'world'), 65 'hello world' 66 ) 67 self.assertEqual( 68 src.calculator.divide('hello ', 'world'), 69 'hello world' 70 )
the terminal shows AssertionError
AssertionError: 'I only work with numbers' != 'hello world'
the divide function raised TypeError which was handled by
handle_type_errorso I get a message backI change the expectation in
test_calculator.pyself.assertEqual( src.calculator.divide('hello ', 'world'), 'I only work with numbers' )
the test passes
I want the add function to show the same message when it gets a string. I add a condition to
handle_type_errorincalculator.py1def handle_type_error(function): 2 def wrapper(input_1, input_2): 3 if isinstance(input_1, str) or isinstance(input_2, str): 4 return 'I only work with numbers' 5 try: 6 return function(input_1, input_2) 7 except TypeError: 8 return 'I only work with numbers' 9 return wrapper
the terminal shows AssertionError
AssertionError: 'I only work with numbers' != 'hello world'
that’s more like it. The condition I added uses the isinstance function to check if
input_1andinput_2are stringsI change the expectation in
test_calculator.py62 def test_calculator_with_strings(self): 63 self.assertEqual( 64 src.calculator.add('hello ', 'world'), 65 'I only work with numbers' 66 ) 67 self.assertEqual( 68 src.calculator.divide('hello ', 'world'), 69 'I only work with numbers' 70 )
the test passes
The message in
handle_type_erroris a duplication which means if I would have to change it in two places if needed. I use a variable instead so that any changes would only need to happen in one place1def handle_type_error(function): 2 def wrapper(input_1, input_2): 3 error_message = 'I only work with numbers' 4 if isinstance(input_1, str) or isinstance(input_2, str): 5 return error_message 6 try: 7 return function(input_1, input_2) 8 except TypeError: 9 return error_message 10 return wrapper
the test is still green
I change the name from
handle_type_errorto be more descriptive incalculator.pyTip
you can use the
Rename Symbolfunction of the Integrated Development Environment (IDE) to change everywherehandle_type_erroris at once1def check_input(function): 2 def wrapper(input_1, input_2): 3 error_message = 'I only work with numbers' 4 if isinstance(input_1, str) or isinstance(input_2, str): 5 return error_message 6 try: 7 return function(input_1, input_2) 8 except TypeError: 9 return error_message 10 return wrapper 11 12 13@check_input 14def subtract(input_1, input_2): 15 return input_1 - input_2 16 17 18@check_input 19def multiply(input_1, input_2): 20 return input_1 * input_2 21 22 23@check_input 24def divide(input_1, input_2): 25 return input_1 / input_2 26 27 28@check_input 29def add(input_1, input_2): 30 return input_1 + input_2
the terminal still shows green
I add another assertion for multiplication in
test_calculator.py67 self.assertEqual( 68 src.calculator.divide('hello ', 'world'), 69 'I only work with numbers' 70 ) 71 self.assertEqual( 72 src.calculator.multiply('hello', 'world'), 73 None 74 )
the terminal shows AssertionError
AssertionError: 'I only work with numbers' != None
I change the expectation to match
71 self.assertEqual( 72 src.calculator.multiply('hello', 'world'), 73 'I only work with numbers' 74 )
the test passes
I add another one for subtraction
71 self.assertEqual( 72 src.calculator.multiply('hello', 'world'), 73 'I only work with numbers' 74 ) 75 self.assertEqual( 76 src.calculator.subtract('hello', 'world'), 77 None 78 )
the terminal shows AssertionError
AssertionError: 'I only work with numbers' != None
I make the expectation match
75 self.assertEqual( 76 src.calculator.subtract('hello', 'world'), 77 'I only work with numbers' 78 ) 79 80 81# Exceptions Encountered
the test passes
review¶
The calculator program can take 2 inputs and check if the inputs are good then do * addition * subtraction * multiplication and * division
Even though the program claims to only work with numbers, I did not add a test for floats, do you want to add those tests?
I ran tests for TypeError with * objects that are not callable * function definitions and * objects that do not mix * and the calculator program to use TypeError to make sure it gets the right inputs
Would you like to test Lists?
Click Here for the code I typed in this Chapter