Skip to main content

Handling User Errors Quiz

This lecture follows a quiz in the course that asks several questions about error handling. Let's take a moment to go over the quiz questions and their answers!

Question 1

A quick question to warm you up!

Ben and Dan are two novice software developers. They are trying to build a function that asks user for a number and calculate its power of 2. Here is their first version of code:

def power_of_two():
n = input('Please enter a number: ')
n_square = n ** 2
return n_square

What do you think of the code, will it work? Before submitting your answer, think about why.

Answer

The answer here is that this code will never work, because the input() function always returns a string, and you can't square a string.

Question 2

In this second question, Ben and Dan improve the code slightly:

def power_of_two():
user_input = input('Please enter a number: ')
n = float(user_input)
n_square = n ** 2
return n_square

Answer

Now the code will work on some inputs, but not others. That's because if the user decides to not enter a number (e.g. a letter), the program will crash.

Question 3

Now, Ben and Dan add some error handling:

def power_of_two():
user_input = input('Please enter a number: ')
try:
n = float(user_input)
except ValueError:
print('Your input was invalid.')
finally:
n_square = n ** 2
return n_square

Dan wants to test this function with some different inputs when the program asks for a number. He decides to test with two cases. First case: he enters 4, which is a valid numeric input. Second case: he enters 'dan' which is an invalid input.

What do you expect the function to return with Dan's input (Denoted as [4, 'dan'])? If the program breaks due to an error, we mark the function's return value as Error, and if it does not return anything, we mark the return value as None.

This one's a bit of a tricky one! Type the code out and try it in your editor for optimal chances of getting it right!

Potential answers:

  • [16.0, None]
  • [Error, Error]
  • [16.0, 0.0]
  • [16.0, Error]
  • None of the above

Answer

The finally block gets executed regardless of the occurrence of ValueError, so the correct answer is [16.0, Error]. We only defined n in the try block. If the input was invalid, n never gets its value assigned, and thus will raise an error when we try to access it in the finally block.

Question 4

As Dan found the error in the code, he decided to make the following change:

def power_of_two():
user_input = input('Please enter a number: ')
try:
n = float(user_input)
except ValueError:
print('Your input was invalid. Using default value 0')
n = 0
else:
n_square = n ** 2
return n_square

Dan wants to test with the same test cases from last time: [4, 'dan'].

What do you expect the function to return with the two inputs (denoted as [4, 'dan']) this time? If the program breaks due to an error, we mark the function's return value as Error, and if it does not return anything, we mark the return value as None.

Answer

This one's tricky! The else block only gets executed if no error occurs in the try block. So the code will finish after assigning n with the default value 0 if it ever reaches the except block. Since there is no return statement other than in the else clause, the function returns None.

Question 5

Now that Dan's code still cannot work, Ben insisted on using a finally block, here is his code:

def power_of_two():
user_input = input('Please enter a number: ')
try:
n = float(user_input)
except ValueError:
print('Your input was invalid. Using default value 0')
n = 0
else:
n_square = n ** 2
finally:
return n_square

They decided to use the same test cases: [4, 'dan'].

What do you expect the function to return with the two inputs (denoted as [4, 'dan']) this time? If the program breaks due to an error, we mark the function's return value as Error, and if it does not return anything, we mark the return value as None.

Potential answers:

  • [16.0, None]
  • [Error, Error]
  • [16.0, 0.0]
  • [16.0, Error]
  • None of the above.

Answer

In this case, the answer is [16.0, Error]. The finally block gets executed regardless of the occurrence of ValueError, while the else block only gets executed if no error occurs in the try block. However, we only defined n_square in the else block. If the input was invalid, n_square never gets its value assigned, thus will raise an error when we try to access it in the finally block.

Question 6

Ben and Dan just won't give up, here's their last try:

def power_of_two():
user_input = input('Please enter a number: ')
try:
n = float(user_input)
except ValueError:
print('Your input was invalid. Using default value 0')
return 0
finally:
n_square = n ** 2
return n_square

They decided to use the same test cases: [4, 'dan'].

What do you expect the function to return with the two inputs (denoted as [4, 'dan']) this time? If the program breaks due to an error, we mark the function's return value as Error, and if it does not return anything, we mark the return value as None.

Potential answers:

  • [16.0, None]
  • [16.0, 0.0]
  • [16.0, 0]
  • [16.0, Error]
  • None of the above.

Answer

The answer is [16.0, Error]. It's a tricky one. The finally block will get executed regardless of the return statement in both try block and except block. However, we only defined n in the try block. If the input was invalid, n never gets its value assigned, thus will raise an error when we try to access it in the finally block.

Improvements to the code

This is the latest code that Ben and Dan wrote. As you know, it still has some issues!

def power_of_two():
user_input = input('Please enter a number: ')
try:
n = float(user_input)
except ValueError:
print('Your input was invalid. Using default value 0')
return 0
finally:
n_square = n ** 2
return n_square

To fix this, we need to use the else clause!

First, let's think about what parts of the code can raise an exception. In our case, that's converting the user input to a float.

That line is already inside the try block, so that's good!

def power_of_two():
user_input = input('Please enter a number: ')
try:
n = float(user_input)

Next up, let's write what should happen if there is no error:

def power_of_two():
user_input = input('Please enter a number: ')
try:
n = float(user_input)
else:
n_square = n ** 2
return n_square

Doing it this way is not absolutely necessary, of course, and you may like writing the except part first. It's up to you.

But now we've got the "happy path" of our code, or what happens if there are no errors.

So let's handle any errors that come up. In our case that's just ValueError, so let's add an except clause for that.

def power_of_two():
user_input = input('Please enter a number: ')
try:
n = float(user_input)
except ValueError:
print('Your input was invalid. Using default value 0')
return 0
else:
n_square = n ** 2
return n_square

So all that was needed in the end was to change the finally to an else, because there wasn't anything that we needed to run no matter what.

A small improvement we might do is change the return 0 for return 0.0, so that the function always returns a float:

def power_of_two():
user_input = input('Please enter a number: ')
try:
n = float(user_input)
except ValueError:
print('Your input was invalid. Using default value 0')
return 0.0
else:
n_square = n ** 2
return n_square

That way the caller of power_of_two() can always expect a float to come back, and not "sometimes a float, sometimes an int". Consistency helps!