Why does this code return "zero division error"?

def divide_two_numbers(x, y):
    result = x / y
    return result

try:
  result = divide_two_numbers(2,0)
  print(result)
except NameError:
  print("A NameError occurred.")
except ValueError:
  print("A ValueError occurred.") 
except ZeroDivisionError:
  print("A ZeroDivisionError occurred.")

How does it know what error has occurred and which of the three errors to print?
And how do I show indentation when posting here?

1 Like

Thank you for asking!!! So few people do! Just look at the menu bar at the top of the text box when you open it to type. You’ll see a </> icon. If you click on it when the cursor is on a blank line, it will produce a highlighted area (saying “type or paste code here”) into which you can paste your code. If you click it when your cursor is within a line, the highlight will say “Preformatted text” and will allow you to highlight a snippet.

That’s how try-except works. The specific exceptions that try-except will recognize are built in to Python just as are the functions len(), max() and min() that you are used to using all the time.
(In a Python console, at the >>> prompt, type dir(__builtins__) (note the double underscores) to see what I mean.)

If you coded your try-except naming an exception not on that list, your code would raise a NameError, unless you have previously defined it as a user-defined exception. I believe the lesson you are on eventually discusses these.

5 Likes

Just to add a little bit to @patrickd314’s post…

Your except blocks are catching the exceptions that Python is raising, giving you the chance to correct the problem or take some other action (“handle the exception”) to keep your program going rather than having it crash out completely with a traceback.

If you want, for testing or curiosity purposes, to see the actual error that Python is throwing then you can add the raise keyword to the end of your except block like so:

def divide_two_numbers(x, y):
    result = x / y
    return result

try:
  result = divide_two_numbers(2,0)
  print(result)
except NameError:
  print("A NameError occurred.")
  raise
except ValueError:
  print("A ValueError occurred.") 
  raise
except ZeroDivisionError:
  print("A ZeroDivisionError occurred.")
  raise

You’ll then get this back from the console:

A ZeroDivisionError occurred.
Traceback (most recent call last):
  File "spam.py", line 6, in <module>
    result = divide_two_numbers(2,0)
  File "spam.py", line 2, in divide_two_numbers
    result = x / y
ZeroDivisionError: division by zero

The raise keyword essentially just asks Python to “re-raise” whatever the last exception was. (You can also use it to raise specific exceptions, if you want.)

In your code, you wouldn’t necessarily include it - I’ve done so simply as an example to show you the traceback that your except ZeroDivisionError: block is handling.

A better example of its use would be:

def ask_name():
    name = input("Please enter your name: ")
    return name

def ask_age():
    age = input("Please enter your age: ")
    return age

def check21(age):
    if age > 21:
        return True
    else:
        return False

def check_id(name,age):
    try:
        print("Just need to see some id...")
        print("So, you're "  + name + "...")

        if check21(age):
            print("Yup, come on in!")
        else:
            print("Sorry, you're not old enough to be here.")
    except:
        print("Whoops! That exception was too quick for me!")
        raise

try:
    your_name = ask_name()
    your_age = ask_age()

    check_id(your_name,your_age)
except TypeError:
    print("Hmm... seems you're mixing your types up again!")
except:
    print("A wild exception has appeared!")

Apologies in advance, this is a slightly laboured example so not necessarily reflective of actual useful programming. Can you see how the raise keyword is useful here?

You’ve probably noticed that in my “ask_age” function I’m taking input from the console and not changing it to an int type. This is fine, as far as that function is concerned.

We only run into an issue when we get to the if check21(age): line. This passes the string value we got from the console (at this point, contained in the “your_age” variable) into the function and tries to use a numerical comparison operator (>) between a string and an integer.

We can’t do this, so Python throws a TypeError exception. We don’t have a specific except block to handle that inside of “check_id”, so the line “Whoops! That exception was too quick for me!” is written to the console.

raise then hands the exception up the call stack - that is, it goes back to whatever code block was being processed when we called “check_id”. At the top level of the program, we do have an except block capable of handling a TypeError, so we run the code inside of the except block there.

In this way, as you’re building bigger and more complicated Python programs, you can design better ways to handle both expected and unexpected exceptions that might come up.

Hopefully that made sense. :slight_smile:

3 Likes