# Exceptions The hardest thing about programming is error handling. It's easy to write a program which works well when your data is going down the golden path which you laid out for it, but when there's something unexpected all hell can break loose. Errors can range from failing loudly, failing silently, silently overwriting important data, to crashing your computer. Given the choice I'd take the first one every time (too often at my job errors are of the last kind). Traditionally, languages like C could only return one variable from a function. Often a special value of that variable was chosen. This is sometimes called 'in-band error handling' and can be problematic because it's way to easy to reuse that special value, or forget to check it. ```python def do_work(): # Work if work_was_successful: return result else return -1 result = do_work() if result == -1: # Handle error pass do_work() # Oops, forgot to check the status. ``` Python takes a different approach to error handling. It has special errors called exceptions. Exceptions have to be dealt with, also known as caught, otherwise the function will exit. If the calling function doesn't deal with the exception it will bubble up all the way to the top and kill the program. This follows the "Explicit is better than implicit" mentality and keeps you from screwing up silently. Because you aren't using the return value to both indicate the result of the function, and whether there was an error, this is known as "out of band" error handling. Exceptions can be raised (sometimes also called thrown in other languages like Java), using the `raise` keyword. Anything can be raised as an exception, but it's best if you create an explicit type of exception, or reuse one of the built in types of exceptions. ```python raise 'A string message' class MySpecialPurposeException(Exception): # Inherits from the base Exception. pass raise MySpecialPurposeException('A message') raise NameError # Seen any of exceptions like this? ``` Now that we know how to raise exceptions, how can we deal with them? We can use a special construct called a `try`/`catch` or `try`/`except` block (again, the names change slightly depending on the language, and you will hear both) ```python try: f = open('some_file_which_may_not_exist', 'r') print(f.read()) except IOError as io_exception: print('An exception was thrown', io_exception) ``` You can also swallow all of the exceptions a function may throw like this: ```python try: f = open('some_file_which_may_not_exist', 'r') print(f.read()) except: print('Swallow all of the exceptions!') ``` This is considered bad practice because the "Explicit is better than implicit". Exceptions can also be used as a form of flow control, that is a way of directing program flow just like if statements and for loops. We'll encounter this soon enough.