<jupyter_start><jupyter_text>Exceptions and More File I/O Error handling is a trick issue in software. Common solutions, like print statements and error codes, have serious problems.[Exceptions](https://en.wikipedia.org/wiki/Exception_handling) are a solution to some error-handling problems... and they create some problems!So far, if we hit an error, like bad input, we could:1. Return an error code; or2. Print an error message.The problems? Error code returns can be ignored, and for 'headless' programs, there is no one there to read the message.Problem one:<jupyter_code># we expect format s1:
s1 = "(347)882-8881"
i = s1.find(')')
area_code = s1[1:i]
print("s1 area code:", area_code)<jupyter_output><empty_output><jupyter_text>What will happen if we get s2?<jupyter_code>s2 = "347-882-8881"
i = s2.find(')')
print("i =", i)
area_code = s2[1:i]
print("s2 area code:", area_code)<jupyter_output><empty_output><jupyter_text>What will go wrong?a) `i` is not a valid index  b) `find()` doesn't work on strings  c) the programmer ignored the error return from `find()`  d) Syntax error Or consider the following code running on the chip in your car's braking system:<jupyter_code>def brakes_failing():
    # some code
    return True

if brakes_failing():
    # turn_on_brake_emergency_light()
    print("Caution: failure in automatic braking system!")<jupyter_output><empty_output><jupyter_text>Who is going to see that message? Do you see anyone inside that brake above looking at a monitor?  The Solution: ExceptionsExceptions were developed to handle these problems, as well as the issue of centralizing error handling.In Python, exceptions are a type of Python object, one that encapsulates an error.In Python we *raise* an exception when something goes wrong in our code. An unhandled exception will rise up the *call stack* until it is *handled*. We can *handle* the exception with a `try` block. Or, if there is no handler, the exception terminates our program.<jupyter_code>some_string = "Week 10 of the NFL slate is stacked with great matchups."

# some_index = int(input("Which character do you want (by index)? "))
# print("The char is: {}".format(some_string[some_index]))

good_input = False
while not good_input:
    try:
        some_index = int(input("Which index character do you want? "))
        print("The char is: {}".format(some_string[some_index]))
        good_input = True
    except ValueError:
        print("The value inputted is not an int.")
    except IndexError:
        print("The value you entered is not a valid index into the string.")

print("Still in control!")<jupyter_output><empty_output><jupyter_text>Python Builtin Exceptions`ModuleNotFoundError` is thrown when an imported module can not be found.(`seaborn` is a popular Python graphing package.)<jupyter_code># import seaborn

graphics = False
try:
    import seaborn  # this is a Python graphics package
except ModuleNotFoundError:
    print("No graphics package: graphics will be turned off!")
else:
    graphics = True
    print("Graphics capabilities are turned on!")
finally:
    print("Done with graphics check.")

print("Do we get here?")

# later, the user tries to graph a function...
if graphics:
    # do something fancy on the screen
    print("Do fancy graphics!")
else:
    print("Sorry, seaborn package not installed.")<jupyter_output>No graphics package: graphics will be turned off!
Done with graphics check.
Do we get here?
Sorry, seaborn package not installed.<jupyter_text>`ImportError` is thrown when a specified name can not be found.<jupyter_code># from random import shuffle
from random import SGDFhjdgfhjsdgf<jupyter_output><empty_output><jupyter_text>`TypeError` is thrown when an operation or function is applied to an objectof an inappropriate type.<jupyter_code>'2' + 2<jupyter_output><empty_output><jupyter_text>`ValueError` is thrown when a function's argument is of an inappropriate type.<jupyter_code># int('Not a number!')
try:
    int('Not a number!')
except ValueError:
    print("ValueError bomb defused!")
print("Does the program continue?")<jupyter_output>ValueError bomb defused!
Does the program continue?<jupyter_text>`ZeroDivisionError` is thrown when the second operator in the division is zero.<jupyter_code>100/0<jupyter_output><empty_output><jupyter_text>`KeyboardInterrupt` is thrown when the user hits the interrupt key (normally Control-C) during the execution of the program. **NOTE**: This works from the command line, but **not** inside a notebook!<jupyter_code>try:
    while True:
        print("*", end='')

except KeyboardInterrupt:
    print("\nProgram terminated by user.")<jupyter_output>****************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************[...]<jupyter_text>Exceptions and Files Here is a common pattern:1. Ask the user for some input.2. Try something with the input.3. Catch any exception and ask the user again.4. No exception: go on to next task!<jupyter_code># input_file = open("jhsjkdfhsdgfj.txt", "r")

file_name = input("Enter file name: ")
file_opened = False
while not file_opened:
    try:
        input_file = open(file_name, "r")
    except FileNotFoundError:
        print("File ", file_name, "not found; try again!")
        file_name = input("Enter file name: ")
    except PermissionError:
        print("You don't have permission to read file ", file_name, "; try again!")
        file_name = input("Enter file name: ")        
    else:
        file_opened = True
print("That last name was OK!")
input_file.close()<jupyter_output>Enter file name: input.txt
That last name was OK!<jupyter_text>What goes above to handle no read permission?a) finally:  b) else:  c) except PermissionError:  d) except ValueError:<jupyter_code>file_name = input("What file do you want to open? ")
line_num_str = input("What line of the file do you want to see? ")

try:
    input_file = open(file_name, "r")
    find_line = int(line_num_str)

    for line_num, line_str in enumerate(input_file):
        if line_num == find_line:
            print("Line {} of file {} is: {}".format(line_num,
                                                     file_name,
                                                     line_str))
            break
    else:
        print("Line {} of file {} not found.".format(find_line,
                                                     file_name))

    input_file.close()

except FileNotFoundError:
    print("File {} not found.".format(input_file))
except ValueError:
    print("Line {} isn't a legal line number.".format(line_num_str))

print("End of program")<jupyter_output><empty_output><jupyter_text>Try-Except-Else-Finally<jupyter_code>file_name = "grudes.txt"
try:
    input_file = open(file_name)
except FileNotFoundError:
    print("File does not exist:", file_name, file="kajhsdkjh/ajshdjkasdh/jkahsdjh")
else:
    # executed if try block is error-free
    print("Processing", file_name)
finally:
    # executed irrespective of exception occured or not
    print("All done!")
    
print("We never get here!")<jupyter_output>All done!<jupyter_text>Raising an exceptionWe can *raise* (or *throw*) a new exception ourselves. Let's see how:<jupyter_code># pattern: prefer data to logic:
VALID_PHONE_CHARS = [' ', '0', '1', '2', '3', '4', '5',
                     '6', '7', '8', '9', '0', '-', '(',
                     ')', '#']
def validate_phone_num(number):
    for char in number:
        if char not in VALID_PHONE_CHARS:
            raise(ValueError("{} is not valid in a phone number".format(char)))
    return True

good_num = False
phone_num = input("Please enter your phone number:")

while not good_num:
    try:
        validate_phone_num(phone_num)
    except ValueError as val_err:
        print("-"*40)
        print("Bad phone #: {}".format(val_err))
        print("-"*40)
        # ask again:
        phone_num = input("Please enter your phone number:")
    else:
        good_num = True

print("Phone number is:", phone_num)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.20468820098698967, 'fraction_numerical': 0.006841633019291162, 'mean_word_length': 4.538509316770186, 'pattern_counts': {'":': 0, '<': 52, '<?xml version=': 0, '>': 52, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 7, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '516098', 'license': 'Apache-2.0', 'n_tokens_mistral': 2498, 'n_tokens_neox': 2371, 'n_words': 843, 'path': 'notebooks/FilesAndExceptions.ipynb', 'repo_name': 'gcallah/IntroPython', 'size': '8916'}