<jupyter_start><jupyter_text>Errors and exceptions 
There are (at least) two distinguishable kinds of errors: syntax errors and
exceptions.
 Syntax Errors

Syntax errors, also known as parsing errors, are perhaps the most common kind
of complaint you get while you are still Learning Python (even after that though!)<jupyter_code>while True print("Hello World")<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. The error is
caused by (or at least detected at) the token preceding the arrow: in the
example, the error is detected at the function print() since
a colon (':') is missing before it. File name and line nmber are printed so
you know where to look in case the input came from a script.
 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. Most exceptions are not handled by programs and result in error messages.<jupyter_code>10*(1/0)
4 + spam*3
'2' + 2<jupyter_output><empty_output><jupyter_text>The string printed as exception type is the name of the built-in exception
that occured, for all built in exceptions. This is a useful, though not
necessary convention for user-defined exceptions as well
 Handling exceptions<jupyter_code>while True:
    try:
        x = int(input("Please enter a number: "))
        break
    except ValueError:
        print("Oops! That wasn't a valud number. Try again")<jupyter_output>Oops! That wasn't a valud number. Try again
Oops! That wasn't a valud number. Try again<jupyter_text>The try statement works as follows:

First the try clause (statements between the try and except keywords) is
executed.
If no exception occurs, the except clause is skipped and execution of the
try statement is finished.
If an exception occurs during execution of the try clause, the rest of the
clause is skipped. Then if its type matches the exception named after the
except keyword, the except clause is executed, and then
execution continue after the try statement.
If an exception occurs which does not match the exception named in the
except clause, it is passed on to outer try statements; if no
handler is found, it is an unhandled exception and execution stops
with a message.

A try statement may have more than one except clause, to specify
handlers for different exceptions. At most one handler will be executed.
Handlers only handle exceptions that occur in the corresponding try clause,
not in other handlers of the same try statement. An except
clause may name multiple exceptions as a parenthesized tuple, for example:



except (RuntimeError, TypeError, NameError):
  pass



(Used Alt + 2 + 5 + 5 for indent. Clear space and re-indent if copying code
snippet)

A class in an except clause is compatible with an exception if it is the
same class or a base class thereof (but not the other way around — an
except clause listing a derived class is not compatible with a base class).
For example, the following code will print B, C, D in that order:<jupyter_code>class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")

print("With order reversed: ")
class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except B:
        print("B")
    except D:
        print("D")
    except C:
        print("C")

# The first matching clause is triggered

# Blanket except clause (use with caution):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise
# Optional else clause, when present must follow all except clauses
# Can be used when (e.g.:) a piece of code that must be executed when the try
# clause does not raise an exception

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines') # Opened successfully
        f.close()
# The except clause may specify a variable after the exception name.
# The variable is bound to an exception instance with the arguments stored in
# instance.args.
# One may also instantiate an exception first before raising it and add any
# attributes to it as desired

try:
    raise Exception('spam', 'eggs')
except Exception as inst:
    print(type(inst))
    print(inst.args)
    print(inst)

# The exception instance arguments stored in .args
# __str__ allows args to be printed directly, but may be overridden in
# exception subclasses unpack args

    x, y = inst.args
    print('x = ', x)
    print('y = ', y)

print('\n\n')
# Exceptions occuring in functions inside try clause
def this_fails():
    """What a surprise"""
    x = 1/0

try:
    this_fails()
except ZeroDivisionError as err:
    print("handling run-time error:", err)<jupyter_output><class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x =  spam
y =  eggs



handling run-time error: division by zero<jupyter_text>Raising Exceptions<jupyter_code># The raise exception allows the programmer to force a specified exception to
# occur.

# raise NameError('HiThere')

# The lone argument to raise is the exception to be raised. If an exception
# class is passed, it will be implicitly instantiated by calling its constructor
# with no arguments:

raise ValueError
# If you need to determine whether an exception was raised but don't intend to
# handle it, a simpler form of the raise statement allows you to re-raise the
# exception:

try:
    raise NameError('Hola')
except NameError:
    print('An exception flew by')
    raise<jupyter_output><empty_output><jupyter_text>Exception Chaining<jupyter_code># The raise statements allows an optional 'from' which enables chaining
# exceptions

def func():
    raise IOError

try:
    func()
except IOError as blahblah:
    raise RuntimeError('Failed to open database') from blahblah

# Exception chaining happens automatically when an exception is raised
# inside an except or finally section
# Exception chaining can be disabled by using 'from None'

try:
    open('nonexistentfile.txt')
except IOError:
    raise RuntimeError from None<jupyter_output><empty_output><jupyter_text>User defined Exceptions

Programs may name their own exceptions by creating a new exception class Exceptions should typically be derived from the Exception
class, either directly or indirectly.

Exception classes can be defined which do anything any other class can
do, but are usually kept simple, often only offering a number of
attributes that allow information about the error to be extracted by
handlers for the exception. When creating a module that can raise several
distinct errors, a common practice is to create a base class for
exceptions defined by that module, and subclass that to create specific
exception classes for different error conditions:<jupyter_code>class Error(Exception):
    """Base class for exceptions in this module"""
    pass

class InputError(Error):
    """Exception raised for errors in the input:
    
    Attributes:
        expression -- input expression in which the error occured
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not allowed
    
    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message<jupyter_output><empty_output><jupyter_text>Clean up actions

The try statement has another optional clause which is intended
to define clean-up actions that must be executed under all circumstances.<jupyter_code>try:
    raise KeyboardInterrupt
finally:
    print('See ya later, World!')<jupyter_output><empty_output><jupyter_text>If a finally clause is present, the finally clause
will execute as the last task before the try statement
completes. The finally clause runs whether or not the
try statement produces an exception. The following points
discuss more complex cases when an exception occurs:

If an exception occurs during execution of the try clause, the exception
may be handled by an except clause. If the exception is not handled by an
except clause, the exception is re-raised after the finally clause has
been executed.

An exception could occur during execution of an except or else clause.
Again, the exception is re-raised after the finally clause has been executed.

If the try statement reaches a break, continue or return statement, the
finally clause will execute just prior to the break, continue or return
statement’s execution.

If a finally clause includes a return statement, the returned value will
be the one from the finally clause’s return statement, not the value from
the try clause’s return statement.<jupyter_code>def bool_return():
    try:
        return True
    finally:
        return False
bool_return()
def divido(x, y):
    try:
        result = x/y
    except ZeroDivisionError:
        print("division by zero!")
    else:
        print("result is", result)
    finally:
        print("This is the finally clause")

divido(2, 1)
divido(2, 0)
divido('2', '1')<jupyter_output><empty_output><jupyter_text>Observe that the finally clause is executed in any event. The TypeError
raised by dividing two strings is not handled by the except clause and
therefore re-raised after the finally clause has been executed.
 Pre-defined Clean-up Actions<jupyter_code>import os
pathtofile = os.path.abspath('./IOandfiles/workfile.txt')

# For this we will take into consideration a case we see in th IOandfiles
# tutorial for handling files

with open(pathtofile) as f:
    for line in f:
        print(line, end='')

# After the statement is executed, the file object f is always closed, even
# even if a problem was encountered while processing the lines. Objects which,
# like files, provide predefined clean-up actions will indicate this in their
# documentation<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.06381615088811574, 'fraction_numerical': 0.0020142830983336385, 'mean_word_length': 3.601095197978096, 'pattern_counts': {'":': 0, '<': 40, '<?xml version=': 0, '>': 40, 'https://': 0, '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': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '666522', 'license': 'MIT', 'n_tokens_mistral': 2970, 'n_tokens_neox': 2870, 'n_words': 1433, 'path': 'ErrNExceptions.ipynb', 'repo_name': 'swap-10/LetsPython', 'size': '10922'}