<jupyter_start><jupyter_text>Author: <NAME> References: _docs.python.org_ and _realpython.com_  In this notebook, I will go through the basics of errors and exceptions in Python  There are (at least) three distinguishable kinds of errors: _name errors_ , _syntax errors_ and _exceptions_.  _Name Errors_ Probably, the first most common type of errors, especially for the beginners. Here are few examples of code which will give the Name Error:<jupyter_code>print('Hello world') # --> does not give an error
grint('Hello world') # --> gives a Syntax error because there is no function called "grint"<jupyter_output><empty_output><jupyter_text>_Syntax Errors_ Probably, the second most common type of errors. Here are few examples of code which will give the Syntax Error:<jupyter_code>print('Hello world' # suppose, you forgot to close parentheses
print('Hello world) # or you did not close the quote<jupyter_output><empty_output><jupyter_text>The parser repeats the offending line and displays a little 'arrow' pointing at the earliest point in the line where the error was detected.  It can give a clue about what kind of error it is and where it happened  _Exceptions_ Even if a statement or expression is syntactically correct, it may cause an error when an attempt is made to execute it.  _Errors detected during execution are called exceptions_ and are not unconditionally fatal: we will learn how to handle them in Python programs. Most exceptions are not handled by programs. Let's produce several exceptions, namely, ZeroDivisionError, NameError and TypeError.<jupyter_code>10/0 # gives an error, because we are not allowed to divide by zero
10 + undefined_variable # --> gives an error because we are using an undefined variable
'1' + 4 # --> gives an error because we cannot perform such an poeration on quantities with different types
# in this case, string and integer<jupyter_output><empty_output><jupyter_text>So, how to handle exceptions?  When you run a block of code where you think an exception/error might occur, the way to handle it is to use a _try-catch_ block. In simple words, after the word _try:_ we run a code. If it fails, we either raise or catch an exception.$$try:    ...    run the code which might have some problem/error/excpetionexcept:    ...    what to do in case of the exception$$ Let's demonstrate it by calling a simple function of division one variable by another. We will on purpose _divide a number by zero_ to create an error - and we will catch it<jupyter_code># defining a division function
def div(x, y):
    result = x / y
    return result
# calling a division function wthout any error
div(5,6)
# Making an error on purpose by dividing by zero
div(5,0)<jupyter_output><empty_output><jupyter_text>we can modify the code to catch the errors with a _try-catch_ block.<jupyter_code># defining a division function (with modification)
def div(x, y):
    try:
        result = x / y
    except:
        print('exception happened')
        result = None
        
    return result
div(5,0)<jupyter_output>exception happened<jupyter_text>Important! If you are using a try-catch block _you should always provide some information about an exception_ in case the exception happens. It is possible to use a command _to skip the_ block. The program continues executing the next lines of codes etc without giving any notice or message to a user.$$try:    ...    run the code which might have some problem/error/excpetionexcept:    pass$$ However, it is a _terrible practice_, since noeither, you, nor user or debugger of the program can see that something went wrong in case it did. _Be careful with skipping (passing) the blocks of code, especially those which can indicate on the error!_ _Solution: be specific!_<jupyter_code>def div(x, y):
    try:
        result = x / y
    except:
        print('Division by zero is not allowed! - my message')
        result = None
        
    return result

div(5,0)<jupyter_output>Division by zero is not allowed! - my message<jupyter_text>Using only _except:_ key word would catch all kinds of exceptions.  _There are plenty different types of exceptions._ If you expect that the specific part of the code would give some error, it might be wise to catch them specifically.  For a dedicated reader, here you can check for all sorts of [Build-in Exceptions](https://docs.python.org/3/library/exceptions.html)<jupyter_code># To catch all of the exceptions and to print the error message use Exception key word.
def div(x, y):
    try:
        result = x / y
    except Exception as e:
        print(e)
        print('Division by zero is not allowed! - my message')
        result = None
        
    return result
# calling a function which would return an error
div(5,0)<jupyter_output>division by zero
Division by zero is not allowed! - my message<jupyter_text>Or you might be really specific with the exception which occured<jupyter_code># To catch all of the exceptions and to print the error message use Exception key word.
def div(x, y):
    result = None
    try:
        result = x / y
    except ZeroDivisionError as e:
        print(e)
    except AttributeError as e:
        print(e)
    except IndexError as e:
        print(e)
    # ...
    # etc. ... as many exceptions as you would like to catch ...    
    return result

# calling a function which would return an error
div(5,0) # --> raises ZeroDivisionError<jupyter_output>division by zero<jupyter_text>What about checking the code by raising the error/exception manually? The  _raise_ statement allows the programmer _to force a specified exception to occur_. For example:<jupyter_code>raise NameError('HiThere')<jupyter_output><empty_output><jupyter_text>You have to provide either an exception instance or an exception class (a class that derives from Exception).  To instantiate (call) an exception class, simply call its constructor without argument, such as<jupyter_code>raise NameError<jupyter_output><empty_output><jupyter_text>There is a possibility to re-raise an exception if we do not want to handle it but simply to know whether the exception was raised or not<jupyter_code>try:
    raise NameError('HiThere')
except NameError:
    print('An exception walked by!')
    raise<jupyter_output>An exception walked by!<jupyter_text>What if we want to run the program/code despite the possible exceptions? Then, my dear sir, this part of the tutorial is just for you!  The try statement has another optional clause which is intended to define clean-up actions that must _be executed under all circumstances._ The _finally:_ clause. It will be executed right before the try statement is completed. For example<jupyter_code>try:
    raise KeyboardInterrupt # or any other possible exception/error
finally:
    print('Goodbye, world!')<jupyter_output>Goodbye, world!
{'fraction_non_alphanumeric': 0.059850374064837904, 'fraction_numerical': 0.0029338418659234267, 'mean_word_length': 4.130173062452972, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 49, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, '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': '666958', 'license': 'MIT', 'n_tokens_mistral': 1833, 'n_tokens_neox': 1801, 'n_words': 915, 'path': 'Learning_Exceptions.ipynb', 'repo_name': 'RuslanBrilenkov/Python_Tutorials', 'size': '6827'}