Wednesday, May 17, 2017

Exception Handling and Pickling

This exercise for the 2017 Spring Quarter Foundations of Programming: Python course, demonstrates and explains Python's exception handling and pickling features.

In this article, I go through a short explanation of each of these items.

Exception Handling

Python uses the Try/Except construct to enable a coder to provide clear explanations and corresponding corrective instructions to utilize when errors happen. The Try/Except construct at its most basic level enables a developer to identify and handle conditions that break the code in a program.   This video provides a clear, concise walkthrough of Try/Except.  The narrator in the video notes that when Python encounters an error it presents a standard error type with an accompanying message.  Here is an example of such an error message: 

Traceback (most recent call last):
  File "script.py", line 62, in <module>
    lstItems = inputs(list)
  File "script.py", line 57, in inputs
    first_number = int(input("Enter the first number:"))

ValueError: invalid literal for int() with base 10: 'text'

The yellow-highlighted text shows an output that occurred during the execution of the script.py program where a user entered the word "text" instead of the integer number that program was expecting as indicated by the red-highlighted code snippet.  Now, this error makes sense to a coder but may not be clear to someone without such coding background. For both the non-coder or another programmer who must in the future support the system where this code exists, making a clearer, more informative error message available will improve the readability of program and its future supportability. 

The 
ValueError type is one of many built-in exceptions available in Python.  Alternatively, this list provides concise descriptions of some of the more common exceptions provided in the built-in exception list. 

Let's now improve the code to produce more informative error messages. Consider the code shown below that produced the error noted earlier.

# Take two numbers and demonstrate some errors
def inputs():
    '''
    :param: None
    :return: list comprised of two numbers generated in this function.
    '''
    first_number = int(input("Enter the first number:"))
    second_number = int(input("Enter the second number: "))
    return [first_number, second_number] # package results into a list and return to main program.

# MAIN
lstItems = inputs() # generate list using inputs function
first,second = lstItems[0],lstItems[1] # assign variables to list elements
# Some operations:
division_result = first/second # perform division
multiplication_result = first*second # perform multiplication
print("Division result:",division_result)
print("Multiplication result:", multiplication_result)

This code in the 'MAIN' section calls a function that in turn takes two inputs, first_number and second_number from the user and then performs division and multiplication operations with those numbers.  The previous error occurred when 'text' was entered at the first prompt instead of an integer. Here is the standard Python error be if zero was entered for second_number:

Traceback (most recent call last):
  File "script.py", line 66, in <module>
    division_result = first/second
ZeroDivisionError: division by zero

In these two error examples two exception types, ValueError (noted earlier) and ZeroDivisionError occur.  Since two different conditions cause these respective exception types to be indicated -- 'raised' in Python terms -- the code is modified as follows to handle each condition:

try:
    def inputs():
        '''
        :param: None
        :return: list comprised of two numbers generated in this function.
        '''
        first_number = int(input("Enter the first number:"))
        second_number = int(input("Enter the second number: "))
        return [first_number, second_number] # package results into a list and return to main program.

    # MAIN
    lstItems = inputs() # generate list using inputs function
    first,second = lstItems[0],lstItems[1] # assign variables to list elements
    # Some operations:
    division_result = first/second # perform division

    multiplication_result = first*second # perform multiplication
    print("Division result:",division_result)
    print("Multiplication result:", multiplication_result)
except ValueError:
    # Handle situations where something other than an integer is entered.
    print("You entered something other than an integer number. Please try again.")
except ZeroDivisionError:
    # Handle cases where '0' is entered in the second number input.

    print("You entered a zero for your second number. That is not permitted. Please try again.")


Observe now what the program reports if (1) something other than an integer is entered for one of those numbers, and (2) zero is entered for the second number:

(1) Non-integer Input:

/anaconda/bin/python /Volumes/C/_PythonClass/script.py
Enter the first number:1
Enter the second number: text
You entered something other than an integer number. Please try again.

Process finished with exit code 0

(2) Zero entered for the second number:

/anaconda/bin/python /Volumes/C/_PythonClass/script.py
Enter the first number:4
Enter the second number: 0
You entered a zero for your second number. That is not permitted. Please try again.

Process finished with exit code 0

Note that both of these error messages are clearer -- and a bit friendlier -- than the default messages that Python generates.  

The next section goes discusses Python's pickling feature.

Pickling

Python uses the pickle module as the documentation notes to "serializ[e] and de-serializ[e] a Python object structure".  Serialization, as discussed in various ways herehere, and in a particularly-helpful description here, is used in Python and other languages for tasks that involve preserving a state of an object for later use by the same program or invoked by another one elsewhere. Examples where this capability is important include saving a state of a game program, or as discussed in this article saving a complex structure utilized in a trained, machine learning algorithm.  Pickling in particular is useful because of its speed advantage in loading a 'pickled' data set compared to reading in the same data from a database or in another format such as JSON.

This video provides a tutorial about pickle.

How does pickle work?  It takes an input like a tuple, list, or a dictionary, and writes the input to a binary file structure.  When the contents of that binary file are later wanted, pickle opens the binary, loads it for use in subsequent processing.

Here is a simple example of pickling in which a dictionary that contains airlines and some of the aircraft models they operate is picked into a binary file.  The dictionary is pickled into a binary file, carriers.dat, the file is closed, the file is reopened, the content is retrieved into a new dictionary, and the contents of that dictionary are then printed to screen.

import pickle # Load the pickle module# Build a dictionary of some airlines and some models they fly.dctAirlinesModels = {"Alaska": "737", "Delta": "A320", "SkyWest": "E175LR", "Air Canada": "787"}
# Place the dictionary's contents into a binary file:pickle_out = open("carriers.dat", "wb")  # open a binary file for writingpickle.dump(dctAirlinesModels, pickle_out)  # places the dctAirlinesModels dictionary into the carriers.dat filepickle_out.close()

# Retrieve the contents of the binary filepickle_retrieve = open("carriers.dat", "rb")  # open binary file in read modeairline_models = pickle.load(pickle_retrieve)  # load the contents into the airline_models dictionarypickle_retrieve.close()  # done retrieving carriers.dat contents.  Close the binary file
# Display the contents of the airline_models dictionaryprint("Airline     Model\n=========== ==========")  # Header for display of airline_modelsfor airline in airline_models:
    print("{0:<11s} {1:<10s}".format(airline, airline_models[airline])) # formatted for printing

The output printed to screen is:

/anaconda/bin/python /Volumes/C/_PythonClass/Assignment07_try_pickling.py
Airline              Model
=========== ==========
Alaska             737       
Delta               A320      
SkyWest          E175LR    
Air Canada      787       

The contents of the carriers.dat file is:
� }q(X Alaskaq X 737q X Deltaq X A320q X SkyWestq X E175LRq X
Air Canadaq X 787q u.

Note that at best the entries are obfuscated.  As noted many places in the documentation and in tutorial files, pickling does not encrypt contents.  Other approaches like this one demonstrated using PyCrypto perform file-encryption tasks.

Summary

This post showed a couple of examples to demonstrate how exception handling and pickling work in Python.  These examples plus the referenced documentation, linked examples, and tutorial videos should provide enough material to get started using these features.


No comments:

Post a Comment