views:

251

answers:

5

A friend of mine wanted help learning to program, so he gave me all the programs that he wrote for his previous classes. The last program that he wrote was an encryption program, and after rewriting all his programs in Python, this is how his encryption program turned out (after adding my own requirements).

#! /usr/bin/env python

################################################################################

"""\
CLASS INFORMATION
-----------------
    Program Name: Program 11
    Programmer:   Stephen Chappell
    Instructor:   Stephen Chappell for CS 999-0, Python
    Due Date:     17 May 2010

DOCUMENTATION
-------------
    This is a simple encryption program that can encode and decode messages."""

################################################################################

import sys

KEY_FILE = 'Key.txt'

BACKUP = '''\
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO\
 PQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
_@/6-UC'GzaV0%5Mo9g+yNh8b">Bi=<Lx [sQn#^R.D2Xc(\
Jm!4e${lAEWud&t7]H\`}pvPw)FY,Z~?qK|3SOfk*:1;jTrI''' #`

################################################################################

def main():
    "Run the program: loads key, runs processing loop, and saves key."
    encode_map, decode_map = load_key(KEY_FILE)
    try:
        run_interface_loop(encode_map, decode_map)
    except SystemExit:
        pass
    save_key(KEY_FILE, encode_map)

def run_interface_loop(encode_map, decode_map):
    "Shows the menu and runs the appropriate command."
    print('This program handles encryption via a customizable key.')
    while True:
        print('''\
MENU
====
(1) Encode
(2) Decode
(3) Custom
(4) Finish''')
        switch = get_character('Select: ', tuple('1234'))
        FUNC[switch](encode_map, decode_map)

def get_character(prompt, choices):
    "Gets a valid menu option and returns it."
    while True:
        sys.stdout.write(prompt)
        sys.stdout.flush()
        line = sys.stdin.readline()[:-1]
        if not line:
            sys.exit()
        if line in choices:
            return line
        print(repr(line), 'is not a valid choice.')

################################################################################

def load_key(filename):
    "Gets the key file data and returns encoding/decoding dictionaries."
    plain, cypher = open_file(filename)
    return dict(zip(plain, cypher)), dict(zip(cypher, plain))

def open_file(filename):
    "Load the keys and tries to create it when not available."
    while True:
        try:
            with open(filename) as file:
                plain, cypher = file.read().split('\n')
                return plain, cypher
        except:
            with open(filename, 'w') as file:
                file.write(BACKUP)

def save_key(filename, encode_map):
    "Dumps the map into two buffers and saves them to the key file."
    plain = cypher = str()
    for p, c in encode_map.items():
        plain += p
        cypher += c
    with open(filename, 'w') as file:
        file.write(plain + '\n' + cypher)

################################################################################

def encode(encode_map, decode_map):
    "Encodes message for the user."
    print('Enter your message to encode (EOF when finished).')
    message = get_message()
    for char in message:
        sys.stdout.write(encode_map[char] if char in encode_map else char)

def decode(encode_map, decode_map):
    "Decodes message for the user."
    print('Enter your message to decode (EOF when finished).')
    message = get_message()
    for char in message:
        sys.stdout.write(decode_map[char] if char in decode_map else char)

def custom(encode_map, decode_map):
    "Allows user to edit the encoding/decoding dictionaries."
    plain, cypher = get_new_mapping()
    for p, c in zip(plain, cypher):
        encode_map[p] = c
        decode_map[c] = p

################################################################################

def get_message():
    "Gets and returns text entered by the user (until EOF)."
    buffer = []
    while True:
        line = sys.stdin.readline()
        if line:
            buffer.append(line)
        else:
            return ''.join(buffer)

def get_new_mapping():
    "Prompts for strings to edit encoding/decoding maps."
    while True:
        plain = get_unique_chars('What do you want to encode from?')
        cypher = get_unique_chars('What do you want to encode to?')
        if len(plain) == len(cypher):
            return plain, cypher
        print('Both lines should have the same length.')

def get_unique_chars(prompt):
    "Gets strings that only contain unique characters."
    print(prompt)
    while True:
        line = input()
        if len(line) == len(set(line)):
            return line
        print('There were duplicate characters: please try again.')

################################################################################

# This map is used for dispatching commands in the interface loop.
FUNC = {'1': encode, '2': decode, '3': custom, '4': lambda a, b: sys.exit()}

################################################################################

if __name__ == '__main__':
    main()

For all those Python programmers out there, your help is being requested. How should the formatting (not necessarily the coding by altered to fit Python's style guide? My friend does not need to be learning things that are not correct. If you have suggestions on the code, feel free to post them to this wiki as well.


EDIT: For those interested, here is the original code source in C that my friend gave me for translation.

/******************************************************************************
 CLASS INFORMATION
 -----------------
   Program Name: Program 11 - Encodes/Decodes
   Programmer:   Evgnto nAl.Wi a 
   Instructor:   fsSP21 .C emgr2rCHoMal4 gyia ro-rm n ,
   Date Due:     MAY 4, 2010

 PLEDGE STATEMENT
 ---------------- 
   I pledge that all of the code in this program is my own _original_ work for 
   the spring 2010 semester. None of the code in this program has been copied 
   from anyone or anyplace unless I was specifically authorized to do so by my
   CS 214 instructor. This program has been created using properly licensed 
   software.
                                     Signed: ________________________________

 DOCUMENTATION
 -------------
   This program Encodes and decodes a program. It also gives you the option of
   making your own code.     
******************************************************************************/

#include <stdio.h>
#define ENCODE 1
#define DECODE 2 
#define OWNCODE 3
#define QUIT 4

void printPurpose();
void encode(char input[], char code1[]);
void decode(char input[], char code1[]);
void ownCode();

int main()
{
   char charIn;
   char code1[100] = "";
   char input[100] = ""; 

   int a;
   int b;
   int closeStatus;
   int number;
   a = 0;
   b = 0;

   FILE *code;

   printPurpose();

   if (!(code = fopen("code.txt", "r")))
   {
      printf("Error opening code.txt for reading");

      return 100; 
   }

   while ((charIn = fgetc(code)) != '\n')
   {   
      input[a] = charIn;
      a++;
   }
   while ((charIn = fgetc(code)) != EOF)
   { 
      code1[b] = charIn;
      b++;
   }  

   scanf("%d", &number);

   while (number != QUIT)
   {
      if (number == ENCODE)
         encode(input, code1);
      else if (number == DECODE)   
         decode(input, code1);
      else if (number == OWNCODE)
         ownCode();

      printf("\n\n");
      printPurpose();
      scanf("%d", &number);
   }
   printf("Thank you for using my program Mr. Halsey!!!!!!!!\n");

   closeStatus = fclose(code);

   if (closeStatus == EOF)
   {   
       printf("File close error.\n");
       return 201;
   }
   else
      printf("Exit successfully.\n");

   return 0;

}
/******************************************************************************
 Prints the purpose of the program
******************************************************************************/
void printPurpose()
{
   printf("This program Encodes, Decodes, and allows you to make your own"); 
   printf("code.\n");
   printf("Enter a 1 to Encode\n");
   printf("Enter a 2 to Decode\n");
   printf("Enter a 3 to make your own code\n");
   printf("Enter a 4 to Quit\n");
}

/******************************************************************************
 Encodes the sentence entered
******************************************************************************/
void encode(char input[], char code1[])
{
   char sentence[100] = "";
   char x;
   char y;

   int index;
   int index2;
   index = 0;

   printf("Enter your sentence\n");

   gets(sentence);
   gets(sentence);
   printf("Your encoded message is >");
   for (index2 = 0; (y = sentence[index2]) != '\0'; index2++)
   {
      while ((x = input[index]) != y)
         index++;
         if (x == y)
            printf("%c", code1[index]);        
         else 
            printf("error");
         index = 0;
   }

   return;
}

/******************************************************************************
 Decodes the sentence entered
******************************************************************************/
void decode(char input[], char code1[])
{
   char encodedMessage[100] = "";
   char x;
   char y;

   int index1;
   int index2;
   index2 = 0;

   printf("Enter encoded message\n");
   gets(encodedMessage);
   gets(encodedMessage);
   printf("Your decoded message is >");
   for (index1 = 0; (y = encodedMessage[index1]) != '\0'; index1++)
   {
      while (( x = code1[index2]) != y)
         index2++;
         if (x == y)
            printf("%c", input[index2]);         
         else
            printf("error");
         index2 = 0;
   }

   return;
}

/******************************************************************************
 Reads in your code and encodes / decodes 
******************************************************************************/
void ownCode()
{
   char charactersUsed[50] = "";
   char codeUsed[50]       = "";
   char sentence[50]       = "";

   int index1; 
   int index2;
   int number;
   int x;
   int y;
   index2 = 0;
   y      = 0;

   printf("Enter the characters you want to use.\n");
   printf("Use less than 50 characters and DO NOT REPEAT the characters.\n");

   gets(charactersUsed);
   gets(charactersUsed);

   printf("Enter the code for the characters.\n");
   gets(codeUsed);

   printf("\nEnter 1 to encode and 2 to decode or 3 to return to main menu.\n");
   scanf("%d", &number);

   while (number != OWNCODE)
   {
      if (number == ENCODE)
      {
         printf("Enter your sentence.\n");

         gets(sentence);
         gets(sentence);

         printf("Your encoded message is > ");

         for (index1 = 0; (y = sentence[index1]) != '\0'; index1++)
         {
            while (( x = charactersUsed[index2]) != y)
               index2++;
               if (x == y)
                  printf("%c", codeUsed[index2]);
               else
                  printf("error");
               index2 = 0;
         }
      }
      else if (number == DECODE)
      {
         printf("Enter your encoded sentence.\n");

         gets(sentence);
         gets(sentence);

         printf("Your decoded message is > ");

         for (index1 = 0; (y = sentence[index1]) != '\0'; index1++)
         {
            while (( x = codeUsed[index2]) != y)
               index2++;
            if (x == y)
               printf("%c", charactersUsed[index2]);
            else
               printf("error");
               index2 = 0;
         }
      }
      printf("\nEnter 1 to encode and 2 to decode or 3 to return to main menu.\n");
      scanf("%d", &number);
   }

   return;
}
+3  A: 

In my opinion, the whole program is questionable. The choices of how things are divided into functions is kind of dopey, and it's documented like a class project, not like real system. The documentation kind of obscures the overall mediocre organization. The entire program shows a bias towards verbosity over clarity.

The construct '''\ is frequently seen. The point of the triple quote construct is that carriage returns and newlines work just fine. There is no need to \ escape the newline.

I will tackle functions I see specific problems in as examples:

Whether or not get_message should exist in that form is debatable. The entire function can be replaced by one line.

def get_message():
    "Gets and returns text entered by the user (until EOF)."
    return sys.stdin.read()

The way main, run_interface_loop, get_character and FUNC interact is confusing and not the best way to handle the problem. In particular, catching SystemExit isn't really a great way to signal your program's end in this case.

def main():
    encode_map, decode_map = load_key(KEY_FILE)
    run_interface_loop(encode_map, decode_map)
    save_key(KEY_FILE, encode_map)

def run_interface_loop():
    exit_picked = False
    while not exit_picked:
        print '''
MENU
====
(1) Encode
(2) Decode
(3) Custom
(4) Finish'''
        choice = sys.stdin.readline()
        if choice == '':
            break  # EOF was reached, no more asking the user for stuff
        choice = choice.strip()
        if choice == '1':
            encode(encode_map, decode_map) # This reads until EOF
            exit_picked = True # and so we might as well exit
        elif choice == '2':
            decode(encode_map, decode_map)
            exit_picked = True # Same here
        elif choice == '3':
            encode_map, decode_map = custom()
        elif choice == '4':
             exit_picked = True
        else:
             print "%s isn't a valid choice, try again." % (choice,)
Omnifarious
'''\ is perfectly valid. It suppresses an initial newline in the string, but lets all lines line up at the left edge.
Mark Tolonen
+6  A: 

don't use bare excepts;

try:
    with open(filename) as file:
        plain, cypher = file.read().split('\n')
        return plain, cypher
**except:**
    with open(filename, 'w') as file:
        file.write(BACKUP)

it is almost always wrong to use bare excepts, and in this case, it is wrong. Catch the exceptions that you want to catch only, in this case most likely is IOException and the error you might get if the file does not contain a '\n' and your tuple unpacking fails (hint: catch them in separate except clause). Also, you might be interested to check what happens if the user doesn't has write permission to filename.

Is there any reason why you're not using print and raw_input() here?

def get_character(prompt, choices):
    "Gets a valid menu option and returns it."
    while True:
        sys.stdout.write(prompt)
        sys.stdout.flush()
        line = sys.stdin.readline()[:-1]
        if not line:
            sys.exit()
        if line in choices:
            return line
        print(repr(line), 'is not a valid choice.')

you might want to use str.translate() for this:

    sys.stdout.write(encode_map[char] if char in encode_map else char)
Lie Ryan
If anything, print() and input() would have been used from Python 3. However if memory serves correctly, print() was not flushing correctly; and input() could have raised an exception. Exception handling was mostly avoided in the program since my friend is still new to programming and does not know very much yet. The idea behind the exception loop is that if it fails for any reason, the file will be replaced with something valid that will succeed. If an error was raised for some other reason, the second part of the try/except clause will probably fail for the same reason and crash the program.
Noctis Skytower
@Noctis Skytower: Re handling SystemExit, then handle it with a finally clause. Say what you mean. There are other exceptions besides SystemExit. The file save is currently skipped for any exception besides SystemExit.
Omnifarious
+7  A: 

Since you asked about formatting and style, I'm surprised that nobody else has mentioned PEP 8 yet. It's nominally a guide for modules that want to be included in the standard library, but I find most of its guidance to be applicable pretty much everywhere.

Forest
+1. Also this example is two things. A module of functions that do encoding and decoding and a Character mode User Interface (CLI). Please learn about Python modules and break this into two pieces.
S.Lott
A: 

Never call sys.exit() or raise SystemExit inside a function. Instead, raise an exception.

Longpoke
+1  A: 

Also look here for Google's Python coding guidelines. They're mostly in line with the PEP mentioned above but are a bit more restrictive in some areas.

Google Python Style Guide

Khorkrak