Tuesday, July 17, 2018

Errors and Exception


Syntax Errors
-------------

- parsing errors
- common errors when you are still learning python

>>> while True print('Hello world')
  File "", line 1
    while True print('Hello world')
                   ^
SyntaxError: invalid syntax

Exceptions
----------

- errors occured during execution
- types:
    a. built-in exceptions
    b. user-definedexceptions
- can be handled

Examples of Built-in exceptions
>>> 10 * (1/0)
Traceback (most recent call last):
  File "", line 1, in
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "", line 1, in
TypeError: Can't convert 'int' object to str implicitly
EOFError
- means you hit CTRL + D
- useful in input() and raw_input()


Handling Exceptions
-------------------

- exceptions can be handled use `try .. except` clause
- execution is as follows:
    * First, the try clause (the statement(s) 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
      that try clause is skipped. Then if its type matches the exception named
      after the except keyword, the except clause is executed, and then
      execution continues after the try statement (jumping to another except or
      finally clause).
    * If an exception occurs which does not match the exception named in the
      except clause, it is passed or to outer try statements; if no handler is
      found, it is an unhandled exception and execution stops with a message
- `try` statement can have multiple `except` clause or handlers
- a handler is the clause within `except` statement
    * handles exceptions that occure in the corresponding `try` statement
    * doesn't handle exceptions generated on other handlers within same `try`
      statement

Basic `try .. except` clause
>>> while True:
...     try:
...         x = int(input("Please enter a number: "))
...         break
...     except ValueError:
...         print("Oops!  That was no valid number.  Try again...")
...
Multiple exceptions
... except (RuntimeError, TypeError, NameError):
...     pass
Class in except clause
*NEED MORE UNDERSTADNING*
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:

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")

Note that if the except clauses were reversed (with except B first),
it would have printed B, B, B — the first matching except clause is
triggered.
`except` for everything else
A last `except` clause may serve as a wildcard.
 
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

Be careful in using it since it can mask a real programming error.
Using `else` clause in `try`
statements
An optional `else` statement can follow a `try` statement. The `else` clause
will be executed when there is no exception encountered.
 
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')
        f.close()

Using `else` is sometimes better than putting additional code in the `try`
statement because it prevents catching an exception that wasn't raise by the
code being protected by the `try .. except` statement.
Exception arguments
`except` clause may store the exception into a variable.

try:
    raise Exception('spam', 'eggs')
except Exception as inst:
    print(type(inst))    # the exception instance
    print(inst.args)     # arguments stored in .args
    print(inst)          # __str__ allows args to be printed directly,
                         # but may be overridden in exception subclasses
    x, y = inst.args     # unpack args
    x, y = inst     # unpack args
    print('x =', x)
    print('y =', y)
Exceptions inside functions
Exceptions can also be handled even they are generated from inside a function.
 
>>> def this_fails():
...     x = 1/0
...
>>> try:
...     this_fails()
... except ZeroDivisionError as err:
...     print('Handling run-time error:', err)
...
Handling run-time error: division by zero

Raising Exceptions
------------------

- allows programmers to force an exception to occur
- syntax: `raise ExceptionName`
- types of ExceptionNames:
    a. exception instance
    b. exception class (class derived from an exception)

Raising an exception
>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "", line 1, in
NameError: HiThere
Using exception class
raise ValueError  # shorthand for 'raise ValueError()'
Re-raising an exception
This can be used if you want to determine whether an exception occured but you dont't want to
handle it.
 
>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print('An exception flew by!')
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "", line 2, in
NameError: HiThere

User-defined Exceptions
-----------------------

- usually derived from `Exception` class
- common practice:
    1. create a base class for that module
    2. create subclasses for different error conditions
- standard naming convention appending "Error" at the exception name
 
Using a user-defined exception
This program asks the user for a number. If the number is smaller than, it will raise
`ValueTooSmallError` and `ValueTooLargeError` if number is larger.
# define Python user-defined exceptions
class Error(Exception):
   """Base class for other exceptions"""
   pass

class ValueTooSmallError(Error):
   """Raised when the input value is too small"""
   pass

class ValueTooLargeError(Error):
   """Raised when the input value is too large"""
   pass

# our main program
# user guesses a number until he/she gets it right

# you need to guess this number
number = 10

while True:
   try:
       i_num = int(input("Enter a number: "))
       if i_num < number:
           raise ValueTooSmallError
       elif i_num > number:
           raise ValueTooLargeError
       break
   except ValueTooSmallError:
       print("This value is too small, try again!")
       print()
   except ValueTooLargeError:
       print("This value is too large, try again!")
       print()

print("Congratulations! You guessed it correctly.")

Here is a sample output of the program:
Enter a number: 12
This value is too large, try again!

Enter a number: 0
This value is too small, try again!

Enter a number: 8
This value is too small, try again!

Enter a number: 10
Congratulations! You guessed it correctly.

Clean-up actions: `finally` statement
-------------------------------------

- `finally` statement allows to execute actions regardless of the outcome of
  `try` clause
- when does `finally` clause run?
    * when there is no exception
        -> `try` executes
        -> `finally` executes
    * when there is a handled exception
        -> exception is handled
        -> `finally` executes      
    * when there is an unhandled exception
        -> `finally` executes
        -> exception is re-reaised
    * when `try` ends in a break, continue, etc..
- uses:
    a. enables programs that must free up resources regardless of the
       outcome of the main code (e.g network connections, files, etc.)

Simple demo
>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "", line 1, in
  File "", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
Another example
The `with` statement can be used to open and close a file regardless on what action you have done on the file object.
for line in open("myfile.txt"):
    print(line, end="")

Assertions
----------

- way of testing a condition
    * if true, returns nothing and program continues
    * if false, it raises an "AssertionError"
- uses:
    a. used to trace a bug in a program (internal self check)
    b. informs programmer about unrecoverable errors
- not used to catch an error and handle it

Assertion in action
As an example, this function computes the final price of a product after applying a
discount and makes sure that the discounted price is always greater than $ 0 and
is less than the original price of the product.
def apply_discount(product, discount):
    price = int(product['price'] * (1.0 - discount))
    assert 0 <= price <= product['price']
    return price

So when we applied a valid discount, the program works neatly without errors.
#
# Our example product: Nice shoes for $149.00
#
>>> shoes = {'name': 'Fancy Shoes', 'price': 14900}
#
# 25% off -> $111.75
#
>>> apply_discount(shoes, 0.25)
11175
 
But when we put an invalid discount, the program raises an `AssertionError`.
#
# A "200% off" discount:
#
>>> apply_discount(shoes, 2.0)
Traceback (most recent call last):
  File "", line 1, in
    apply_discount(prod, 2.0)
  File "", line 4, in apply_discount
    assert 0 <= price <= product['price']
AssertionError

#
# A "-30% off" discount:
#
>>> apply_discount(shoes, -0.3)
Traceback (most recent call last):
  File "", line 1, in
    apply_discount(prod, -0.3)
  File "", line 4, in apply_discount
    assert 0 <= price <= product['price']
AssertionError
Another example
Here, we initially set the value of `x` to 1 and use `assert` to test the condition.
>>> x = 1
>>> assert x == 1
>>> assert x == 2
Traceback (most recent call last):
  File "", line 1, in
AssertionError
>>>

No comments:

Post a Comment