Understanding "Raise" for raising errors

https://www.codecademy.com/courses/learn-python-3/lessons/python-control-flow/exercises/try-and-except-statements

I’m not understanding how exactly the keyword “raise” is used.

I can’t see the lesson, as I’m not a Pro user, so I don’t know what context around the raise keyword is already provided in the lesson.

What I can explain, however, is that raise is used in Python to raise exceptions. The simplest way I can explain an exception is that they are errors which make themselves known during the execution of your program.

Let’s use a very basic example:

a = 100
b = 10

while b >= 0:
    out = "One %dth of %d is %d!" % (b,a,(a/b))
    print(out)
    b = b - 1

print("Done")

My simple little program above is just calculating fractions of 100. There are no syntax errors in the program, so Python will happily start executing it like so:

Python 3.6.1 (default, Dec 2015, 13:05:11)
[GCC 4.8.2] on linux

One 10th of 100 is 10!
One 9th of 100 is 11!
One 8th of 100 is 12!
One 7th of 100 is 14!
One 6th of 100 is 16!
One 5th of 100 is 20!
One 4th of 100 is 25!
One 3th of 100 is 33!
One 2th of 100 is 50!
One 1th of 100 is 100!
Traceback (most recent call last):
  File "test.py", line 5, in <module>
    out = "One %dth of %d is %d!" % (b,a,(a/b))
ZeroDivisionError: division by zero

Here we can see I’m getting a ZeroDivisionError - because my while loop doesn’t stop when the divisor (b) is equal to one. This is an example of an exception.

There are two broad types of exceptions: handled exceptions, which you have anticipated in your code and have written more code to deal with, so they don’t crash your program; and unhandled exceptions, which your program didn’t expect and doesn’t know how to deal with.

In my case, I have an unhandled exception in the form of a ZeroDivisionError.

If we labour the example a little, let’s say I don’t know why this particular snippet of code is complaining about a divide by zero. “There’s nothing wrong with my code”, I say! We can amend it like so to get a clearer picture, since we know what type of error we’re getting back:

a = 100
b = 10

try:
  while b >= 0:
      out = "One %dth of %d is %d!" % (b,a,(a/b))
      print(out)
      b = b - 1

except ZeroDivisionError:
    print("Here is the variable state at time of divide by zero!")
    out = "a = %d / b = %d / b >= 0: %r" % (a,b,(b >= 0))
    print(out)

print("Done")

If we run the program again…

Python 3.6.1 (default, Dec 2015, 13:05:11)
[GCC 4.8.2] on linux

One 10th of 100 is 10!
One 9th of 100 is 11!
One 8th of 100 is 12!
One 7th of 100 is 14!
One 6th of 100 is 16!
One 5th of 100 is 20!
One 4th of 100 is 25!
One 3th of 100 is 33!
One 2th of 100 is 50!
One 1th of 100 is 100!
Here is the variable state at time of divide by zero!
a = 100 / b = 0 / b >= 0: True
Done

This time, because I have added a try ... except structure and have handled the ZeroDivisionError we don’t get a traceback. Instead, I get the output of my code in the except block which is making it obvious to me that my divisor - the variable b - is equal to zero, and the condition for my while loop - b >= 0 - is still True so the loop will continue with the current variable values.

This is all well and good for a small, simple program like the one I threw together here. Let’s say, though, that you’re working on a much larger project and this code is but a small function within a larger program. You’re confident that your function works correctly, but just in case it doesn’t, we can use the try...except structure to help if we do get an error.

def giveMeFactors(n,nd):
    # n = numerator (e.g. 100)
    # nd = number of divisors to calculate.

    try:
        a = n
        b = nd
    
        while b >= 0:
            out = "One %dth of %d is %d!" % (b,a,(a/b))
            print(out)
            b = b - 1
    except:
        # This EXCEPT catches anything, because I haven't specified a type
        print("Hmm... An unhandled exception was caused by the function 'giveMeFactors'.")
        out = "n = %r , nd = %r , a = %r , b = %r , b >= 0: %r" % (n,nd,a,b,(b >= 0))
        print("Here are the values of all the variables involved...")
        print(out)
        print("Throwing exception up the stack!")
        raise
    print("Done")
    
    return None

…and here’s the output…

Python 3.6.1 (default, Dec 2015, 13:05:11)
[GCC 4.8.2] on linux

One 10th of 100 is 10!
One 9th of 100 is 11!
One 8th of 100 is 12!
One 7th of 100 is 14!
One 6th of 100 is 16!
One 5th of 100 is 20!
One 4th of 100 is 25!
One 3th of 100 is 33!
One 2th of 100 is 50!
One 1th of 100 is 100!
Hmm... An unhandled exception was caused by the function 'giveMeFactors'.
Here are the values of all the variables involved...
n = 100 , nd = 10 , a = 100 , b = 0 , b >= 0: True
Throwing exception up the stack!
Traceback (most recent call last):
  File "test.py", line 25, in <module>
    giveMeFactors(100,10)
  File "test.py", line 10, in giveMeFactors
    out = "One %dth of %d is %d!" % (b,a,(a/b))
ZeroDivisionError: division by zero

The point of the raise keyword here is simple. If you’re working on a big project, you may end up in the position where you have functions calling other functions, which go on to call other functions themselves. What raise lets you do is pass the exception you encountered back up to the code which called the function that triggered the exception. I don’t have an except block capable of handling this exception in the code which called giveMeFactors(), so I have an unhandled exception and Python gives me the expected traceback.

Let’s say I have a function called doMath(), which goes on to call my giveMeFactors() function. What the raise keyword lets me do is say “Oh, ok… giveMeFactors() had a problem. Let’s give that error to doMath(), because that function might know what to do with it!”

Does that make sense?

So, if a function gives an error, “raise” will point to where in the function the error originates.

If the function is called by another function, “raise” will point the error to that other function instead? And if that other function can do something to the error it might correct it?

Sorry, I’m still new at this and am still a bit confused.

raise, when used by itself, says “Show the Traceback (Python-generated error message).” So, as in @thepitycoder 's example, if raise were not present, once the except clause has printed out it’s message, the function would continue to run (if possible). However, with raise added, following the execution of the the except clause, the Traceback is printed and the function halted by the error.

The example used in the lesson, however, is a bit different, and is in my opinion rather cryptic, as no example is given of where you would want to do such a thing:

def raises_value_error():
  raise ValueError  

try:  
  raises_value_error()
except ValueError:
  print("You raised a ValueError!")

# Output:
You raised a ValueError!

The fact is, that you can define your own errors as classes (which you’ll get to later in the course), and use raise to call them. Copy, paste and run the example below, inputting various int values inside or outside of the desired range:

class BadInput(Exception):
    def __init__(self, n):
        print("Bad input", n)

def input_stuff():
    x = input("Input a number from 1 - 10: ")
    if not 1 < int(x) < 10:
        raise BadInput(x)
    return x

print(input_stuff())

Not quite. Python will, without you needing to write any additional code or use the raise statement, already tell you where it encounters any problems while it’s running your code. That’s what the Traceback is.

raise is there to let you do a few things.

  1. Exceptions are objects in Python, which means that you can create your own exceptions if you want or need to. If you were writing a program, and defined a custom exception, you would use the raise statement to throw that custom exception. Python wouldn’t use your custom exception object otherwise.

  2. You can use raise within except blocks to “pass on” the original exception, in case there’s a suitable hander further up the call stack.

  3. You can use raise to elaborate on exceptions - say you had some exception handling code in an except block, but the code here can’t recover from the error. You can use raise to expand on the original exception, calling an exception from the original exception. Here’s an example:

try:
  print(1 / 0)
except Exception as exc:
  raise RuntimeError("Something bad happened") from exc

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

If you have a function which contains a try ... except structure, and the except: block contains the statement raise on it’s own, then yes - Python will “pass on” the exception unchanged to whatever code called the function which raised that exception. If the “parent” code has a handler for that exception - that is, an except block that can deal with it - then it will.

No need to apologise. :slight_smile: Hopefully we’re not making you more confused with the answers!

raise on its own re-raises the last occuring exception in the current scope. If you use it on its own without there being an existing exception, you do technically raise an exception in the form of a RuntimeError but this will simply tell you that your raise call was itself the error. :slight_smile:

1 Like