<jupyter_start><jupyter_text>Copyright 2019 Google LLC.<jupyter_code># Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.<jupyter_output><empty_output><jupyter_text>Intermediate Python - Exceptions In this colab, we will move into a more advanced concept called exceptions. You'll learn how to handle pre-built exceptions and how to build your own exceptions.  Exceptions Inevitably in any coding language, things will go wrong. Data might be of the wrong type, memory might run out, an object that you try to iterate on might be non-iterable, the list goes on and on.Exceptions are a way to handle these cases, and tell you where you went wrong. Below is an example of an exception when you try to divide by zero.<jupyter_code>1 / 0<jupyter_output><empty_output><jupyter_text>Dividing by zero is undefined in mathematics. Whenever you try to divide by zero in Python, you will get the `ZeroDivisionError` exception.In practice, you'd likely never hard-code a zero as a denominator. However, you might have two computed variables that you want to calculate the ratio of.<jupyter_code>my_array = [2, 3, 4]
your_array = []

ratio = len(my_array) / len(your_array)<jupyter_output><empty_output><jupyter_text>There are a few ways to handle this scenario. One is defensive programming, where you check if the denominator is zero using an `if` statement. When you change the number of entries in `your_array`, you will see the output of the cell change.<jupyter_code>my_array = [2, 3, 4]
your_array = []

ratio = 0
if len(your_array) != 0:
  ratio = len(my_array) / len(your_array)
else:
  print("Couldn't calculate ratio, denominator is zero")<jupyter_output><empty_output><jupyter_text>Another option is to allow an exception to be thrown, but then catch the exception. You can do this using the `try` keyword, which tries to complete any code within the block, unless an exception matching the `except` keyword is thrown.<jupyter_code>my_array = [2, 3, 4]
your_array = []

ratio = 0
try:
  ratio = len(my_array) / len(your_array)
except ZeroDivisionError:
  print("Couldn't calculate ratio, denominator is zero")<jupyter_output><empty_output><jupyter_text>In the example above we caught the `ZeroDivisionError`. This code block could have been written to catch any exception by leaving out the error name.<jupyter_code>my_array = [2, 3, 4]
your_array = []

ratio = 0
try:
  ratio = len(my_array) / len(your_array)
except:
  print("Couldn't calculate ratio, some error occurred")<jupyter_output><empty_output><jupyter_text>Catching every possible exception in the `except` block is easy, but can be problematic because you can hide bigger problems in your program. Typically it is best to catch and handle specific errors only.If an exception is thrown and not handled with an `except`, it terminates your program. In some cases, this is what you want to happen. For instance, if the program is out of memory, there isn't much you can do at the moment to handle the problem.There are varying opinions on whether it is better practice to prevent or handle exceptions. In the example above, is it best to check if a value is zero before dividing by it, or is it best to wrap division in a `try`/`except` block?In general, using exceptions for control flow is probably not a good idea. As the name suggests, exceptions should be used for "exceptional" cases - things that you don't expect.Let's look at some other common exceptions you'll see. You'll get a `KeyError` if you try to access an element in a dictionary with square braces and the key doesn't exist.<jupyter_code>my_dict = {
  "a": 1234
}

my_dict["b"]<jupyter_output><empty_output><jupyter_text>You'll get an `IndexError` if you try to access an index in a string, list, or tuple and that index doesn't exist.<jupyter_code>my_array = [1, 2, 3, 4]
my_array[56]<jupyter_output><empty_output><jupyter_text>The comprehensive list of built-in exceptions can be found in the [official Python documentation](https://docs.python.org/3/library/exceptions.html). Built-in in exceptions are core exceptions provided by Python.  Creating Your Own ExceptionsTo create your own error, you simply need to create a class that inherits from the built-in `Exception` class and then `raise` an instance of that class.<jupyter_code>class MyVeryOwnError(Exception):
  pass

raise MyVeryOwnError<jupyter_output><empty_output><jupyter_text>You can then use your error just like any system error. The custom exception is raised in `my_func` if the input is zero. When you change the value of the input to `my_func` in the `try` block, it changes whether the exception is thrown.<jupyter_code>class MyVeryOwnError(Exception):
  pass

def my_func(x):
  if x == 0:
    raise MyVeryOwnError
  else:
    return x

try:
  print(my_func(0))
except MyVeryOwnError:
  print("Handling my custom exception")<jupyter_output><empty_output><jupyter_text>Exercises  Exercise 1 What are some reasons that you might want to create your own exception?  Student Solution *Your answer here* --- **Solution** To provide more readable and specific information when the code throws an error, which is helpful for debugging.This article has a nice explanation: https://dbader.org/blog/python-custom-exceptions  Exercise 2 Handle the exception in the code block below using `try`/`except`. If the addition can't be done, print "Unable to add".  Student Solution<jupyter_code>left = 1
right = "2"

### YOUR CODE HERE ###

left + right<jupyter_output><empty_output><jupyter_text>---  Exercise 3 Using `if`/`else` or some other flow control, prevent the exception in the code below from being thrown.  Student Solution<jupyter_code>array_one = [1, 2, 3]
array_two = [4, 5]

### YOUR CODE HERE ###

for i in range(len(array_one)):
  print(array_one[i] + array_two[i])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07522264631043257, 'fraction_numerical': 0.007792620865139949, 'mean_word_length': 5.11175898931001, 'pattern_counts': {'":': 1, '<': 49, '<?xml version=': 0, '>': 49, 'https://': 3, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '496287', 'license': 'Apache-2.0', 'n_tokens_mistral': 1834, 'n_tokens_neox': 1766, 'n_words': 832, 'path': 'content/00_prerequisites/01_intermediate_python/01-exceptions.ipynb', 'repo_name': 'skykykykykykykykykykykys/applied-machine-learning-intensive', 'size': '6288'}