Sunday, May 16, 2021

Python Errors and Exceptions

Syntax Errors 

------------- 

 

- parsing errors 

- common errors when you are still learning python 

 

>>> while True print('Hello world') 

  File "<stdin>", 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 "<stdin>", line 1, in <module> 

ZeroDivisionError: division by zero 

>>> 4 + spam*3 

Traceback (most recent call last): 

  File "<stdin>", line 1, in <module> 

NameError: name 'spam' is not defined 

>>> '2' + 2 

Traceback (most recent call last): 

  File "<stdin>", line 1, in <module> 

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 "<stdin>", line 1, in <module> 

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 "<stdin>", line 2, in <module> 

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 "<stdin>", line 1, in <module> 

  File "<stdin>", 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 "<input>", line 1, in <module> 

    apply_discount(prod, 2.0) 

  File "<input>", 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 "<input>", line 1, in <module> 

    apply_discount(prod, -0.3) 

  File "<input>", 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 "<stdin>", line 1, in <module> 

AssertionError 

>>>  

 

Tutorials 

--------- 

 

Ways of supressing exceptions 

# Using contextlib 
 

>>> import contextlib 

>>> with contextlib.suppress(FileNotFoundError): 

...   os.remove('ghostfile') 

>>> 

 

Sources 

------- 

 

No comments:

Post a Comment