# Password Trainer v1.0 - Helping you learn a new password

There are two reasons why many people choose weak passwords over stronger, more complicated ones:

1. They don't know that their passwords are insecure
2. They don't want to change to a better password because they are generally more complicated and harder to remember

So, I came up with this program: a password trainer! It helps you learn a new password, which should hopefully make point no. 2 more appealing.

While I am not new to the Python language, this my first attempt at correct styling according to PEP 0008. I have been focousing on the correct use of docstrings and commenting.

### Code:

"""
Copyright (c) 2016 angussidney
Released under the MIT license

This program helps you remember long, complicated, (and most importantly) secure passwords.
"""

import os

MAXIMUM_TRIES = 3 # The maximum number of attempts before the program stops annoying the user.

def main():
"""Prompts the user for their password and provides feedback."""
tries = 0
while tries < MAXIMUM_TRIES:
print
break # Skips directly to end of session
else:
print "Your attempt was incorrect.\n"
raw_input("\nPress any key to continue...")
tries += 1
clear_screen()
print "\nPassword training is complete for this session."
raw_input("Press any key to continue...")

def clear_screen():
"""Clears the screen for a new password attempt."""
os.system("cls" if os.name=="nt" else "clear")

def print_differences(attempt):
"""Prints the differences between the password attempt and the real password.
Raises an error if the attempt is of the wrong length.

Keyword arguments:
attempt -- the password attempt

Example output:

Errors:                 x   x
Corrections:            r   3
"""
if len(attempt) != len(PASSWORD):
print "Your password attempt was of the wrong length."
print "Your attempt should have been %s characters long." % str(len(PASSWORD))
print "No password accuracy breakdown available."
else:
print "Your attempt:     %s" % attempt
print "Errors:           ",
for letter in range(len(PASSWORD)):
if attempt[letter] == PASSWORD[letter]:
print "\b ", # \b backspace character must be used to remove automatically added space
else:
print "\bx",
print
print "Corrections:      ",
for letter in range(len(PASSWORD)):
if attempt[letter] == PASSWORD[letter]:
print "\b ",
else:
print "\b%s" % PASSWORD[letter],
print

if __name__ == "__main__":
main()


### Notes:

• Currently, the program simply returns an error if the length of the password attempt is different to that of the actual password. This will be changed in v2.0.
• The program only works when run in the console. It does not work in IDLE (because the clear screen function and the backspace characters won't work)

### What needs reviewing:

I would be greatful if you could review the following items:

• Is my code following the PEP 0008 standard? Especially the comments and docstrings?
• Is there a better way to print the corrections and errors, without using backspace characters (\b)?

Style : PEP Compliance

Your code looks nice and is well documented. You can use tools to check your code compliance to PEP 8 and PEP 257 : pep8 and pydocstyle (formerly pep257).

PEP 8 warnings are about spacing and line length. PEP 257 warnings are about verbal form used (should be imperative and not declarative).

Style : a step further

A few details in your code are not very pythonic. The main one if the way you write loops. At a rule of thumb, any time you write range(len in Python code, there might be a better way. Ned Batchelder's talk about loops might interest you. In your case, you could use zip or itertools.izip is what you need.

Your code becomes :

    for p, a in zip(PASSWORD, attempt):
if a == p:
print "\b ", # \b backspace character must be used to remove automatically added space
else:
print "\bx",
print
print "Corrections:      ",
for p, a in zip(PASSWORD, attempt):
if a == p:
print "\b ",
else:
print "\b%s" % p,


On a pretty similar topic, your while loop could be written with range or xrange.

for try_index in range(MAXIMUM_TRIES):
print
break # Skips directly to end of session
else:
print "Your attempt was incorrect.\n"
raw_input("\nPress any key to continue...")


(I've used try_index as a loop variable to make intent clearer but the usage/convention is to use _ as a variable name if you do not use its value).

Making things more simple

You call print multiple times which adds whitespace that you are trying to remove later on. It would be easier to build the string you want to print and then print it in one go.

Using multiple Python techniques :

• list comprehension

• join

• conditional expression

You can write :

    print "Correct password: %s\n" % PASSWORD
print "Your attempt:     %s" % attempt
errors = "".join(" " if a == p else "x" for p, a in zip(PASSWORD, attempt))
print "Errors:           %s" % errors
correction = "".join(" " if a == p else p for p, a in zip(PASSWORD, attempt))
print "Corrections:      %s" % correction
print


If you are learning Python, it would be a good idea to give Python 3 a try. There is not much to change in your case but the earlier you take the good habits, the easier it will be.

Regarding your length comparison, you could try to use izip_longest to handle scenarios with strings of different lengths.

• Thanks for your input. I do know how to code in Python 3 (or I at least know the main differences), and I was debating which version to use, however I decided to use Python 2 because the py2exe module works with it. – angussidney Feb 7 '16 at 7:20
password_attempt = raw_input(...)


When dealing with passwords, you want to try and minimize the amount of information any bypasser can see on the screen.

Python offers the getpass module to ease such inputs:

import getpass