Exceptions are useful for more than just signalling errors. They can also be used to help you handle the error, and potentially even fix the problem (true self-healing program!).
Consider this cut down version of the .setHeight
function from the last session...
def setHeight(height):
if height < 0 or height > 2.5:
raise ValueError("Invalid height: %s. This should be between 0 and 2.5 m" % height)
print("setting the height to %s" % height)
The code currently correctly detects if the user supplies a height that is below 0 or above 2.5. However, what about when the user tries to set the height to something that is not a number?
setHeight("cat")
We get a weird error message that says we have a TypeError
, as you cannot order a string and an integer.
One way to address this is to ask that height
is converted to a float
, using height = float(height)
def setHeight(height):
height = float(height)
if height < 0 or height > 2.5:
raise ValueError("Invalid height: %s. This should be between 0 and 2.5 m" % height)
print("setting the height to %s" % height)
However, this hasn't made the error any easier to understand, as we now get a ValueError
raised...
setHeight("cat")
The solution is for us to handle the exception, using a try...except
block
def setHeight(height):
try:
height = float(height)
except:
raise TypeError("Invalid height: '%s'. You can only set the height to a numeric value" % height)
if height < 0 or height > 2.5:
raise ValueError("Invalid height: %s. This should be between 0 and 2.5 m" % height)
print("setting the height to %s" % height)
setHeight("cat")
What's happened here? The try:
line starts a try-block. The code that is in the try-block is run. If any of this code raises an exception, then execution stops in the try-block, and switches instead to the code in the except-block (everything within the except:
block). In our case, float(height)
raised an exception, so execution jumped to the except-block, in which we ran the raise TypeError(...)
code.
Now the error is much more informative, allowing the user to better understand what has gone wrong. However, exception handling can do more than this. It can allow you to fix the problem. Consider this example...
setHeight("1.8 m")
We as humans can see that this could be an acceptable input. However, the computer needs help to understand. We can add code to the except-block that can try to resolve the problem. For example, imagine we had a function that could interpret heights from strings...
def string_to_height(height):
"""This function tries to interpret the passed argument as a height
in meters. The format should be 'X m', 'X meter' or 'X meters',
where 'X' is a number
"""
# convert height to a string - this always works
height = str(height)
words = height.split(" ")
if len(words) == 2:
if words[1] == "m" or words[1] == "meter" or words[1] == "meters":
try:
return float(words[0])
except:
pass
# Getting here means that we haven't been able to extract a valid height
raise TypeError("Cannot extract a valid height from '%s'" % height)
We can now call this function from within the except-block of setHeight
def setHeight(height):
try:
height = float(height)
except:
height = string_to_height(height)
if height < 0 or height > 2.5:
raise ValueError("Invalid height: %s. This should be between 0 and 2.5 m" % height)
print("setting the height to %s" % height)
setHeight("1.8 m")