I work for a service desk and change A LOT of passwords. Most the time the end user is a complete fool that doesn't understand the requirements for the password they want to use. So I created a program that will generate random passwords based on a randomness level (1 - 3).Here's how the levels work:Reads the passwords from a file that you made, these are the most readable passwordsRuns through a gibberish function and returns somewhat word like passwords complete with uppercase, lowercase, integer, and special characterCompletely random string, no pattern to it, has uppercase, lowercase, integers, and special characters.What I would like to know is if there is anything I can do better inside of this program, along with a way to make more readable strings output from the gibberish function.import optparseimport stringimport randomimport itertoolsimport osfrom random import randintinit_cons = (set(string.ascii_lowercase) - set(aeiou)             - set(qxc)             | {bl, br, cl, cr, dr, fl, fr, gl, gr, pl, pr, sk, sl, sm, sn, sp, st,                str, sw, tr}             )final_cons = (set(string.ascii_uppercase) - set(aeiou)              - set(qxcsj)              | {ct, ft, mp, nd, ng, nk, nt,                 pt, sk, sp, ss, st}              )vowels = aeiouSYLLABLES = map(''.join, itertools.product(init_cons,                                           vowels,                                           final_cons))opts = optparse.OptionParser()opts.add_option(-l, --level, metavar=RANDOMNESS LEVEL,                dest=level, help=How random do you want the passwords [1-3])opts.add_option(-a, --add, metavar=PASS TO ADD,                dest=add, help=Add a password to the password file)opts.add_option(--test-demo, action=store_true, dest=test,                help=Run the program 5 times in each level)(options, args) = opts.parse_args()def define_randomness_level(level):     Return how random you the strings are going to be    :type level: String the level you're on with your randomness    >>> from gen import define_randomness_level    >>>    >>> print(define_randomness_level(2))    >>> 2        if level == 1:        return 1    elif level == 2:        return 2    elif level == 3:        return 3    else:        return Nonedef random_strings(size=randint(8, 15), spec_chars=random.choice(?!@*^), chars=string.ascii_lowercase + string.digits                   + string.ascii_uppercase):     Return a random string between 8 and 15 characters long.    :type size: Integers, the length of the string returned    :type spec_chars: String of special characters that is randomly grabbed from    :type chars: String of random characters    >>> from gen import random_strings    >>>    >>> print(random_strings())    >>> hfYpvO4YCzHuCW^        return ''.join(random.choice(chars) for _ in xrange(size)) + spec_charsdef read_pass_from_file(filename):     Read from the file if you want actual readable password strings    :type filename: I shouldn't have to tell you what this is..    >>> from gen import read_pass_from_file    >>>    >>> print(read_pass_from_file(pass_list.txt))    >>> Cocoa!55        with open(filename, r) as data:        arr = data.read().splitlines()        return random.choice(arr)def gibberish(count):     Somewhat readable stuff that almost makes sense.    :type count: Integer, how many somewhat words you want smooshed together    >>> from gen import gibberish    >>>    >>> print(gibberish(2))    >>> druJdiE!9        return ''.join(random.sample(SYLLABLES, count)) + random.choice(?!@*^) + random.choice(string.digits)def add_to_file(word):     Add a password to the password file    :type word: String, your cool password you want added to the file    >>> from gen import add_to_file    >>>    >>> add_to_file(this1SAt3st!)    >>> NOTHING! BECAUSE IT'S IN THE FILE!        with open(pass_list.txt, r) as words:        data = words.read()        open(pass_list.txt, w).write(data + \n{}.format(word))def test_levels(amount):     Test each level of randomness because @boardrider thought it was confusing    :type amount: Integer, run each level this many times    >>> from gen import test_levels    >>>    >>> test_levels(5)    >>> Running with level: 1    >>> Scha3fer!07    >>> ...        command_list = [python gen.py -l 1, python gen.py -l 2, python gen.py -l 3]    while amount != 0:        amount -= 1        for command in command_list:            arr = command.split( )            print(Running with level: {}.format(arr[3]))            os.system(command)def console_main(opt):     Main section of the program, decide where to send what command    :type opt: Dict, given as an option        if define_randomness_level(opt) == 1:        return read_pass_from_file(pass_list.txt)    elif define_randomness_level(opt) == 2:        return gibberish(2)    elif define_randomness_level(opt) == 3:        return random_strings()    else:        raise NotImplementedError(Randomness level of {} is not implemented yet.format(options.level))if __name__ == '__main__':    # To test run with the --test-demo flag    if options.level:        print(console_main(options.level))    elif options.add:        add_to_file(options.add)    elif options.test:        test_levels(5)Example of usage:C:\bin\python\gen_pass.py>python gen.py --test-demoRunning with level: 1MSHa!899Running with level: 2proIclamp@0Running with level: 3DkrYnuADyI!Running with level: 1Scha3fer!07Running with level: 2hespproE@4Running with level: 3wm4m2rVfjh!Running with level: 1American!Dad!07Running with level: 2taSreL!8Running with level: 3TcR5ozXofmCUlY8^Running with level: 1American!Dad!07Running with level: 2peVcloO@6Running with level: 39ZktisBY?Running with level: 1grAnd1k!Running with level: 2floskneD!7Running with level: 33jbHmth4j0P@None
How random can you get?
python;strings;python 2.7;random
CLIDid you read the deprecation notice on the optparse module? Use argparse instead, it will help you simplify the code.Convert to integers and limit available choices to 1, 2 and 3:parser = argparse.ArgumentParser(description='TODO')parser.add_argument(-l, --level, metavar=RANDOMNESS LEVEL, type=int, choices=range(1,4),                    dest=level, help=How random do you want the passwords [1-3])parser.add_argument(-a, --add, metavar=PASS TO ADD,                    dest=add, help=Add a password to the password file)parser.add_argument(--test-demo, action=store_true, dest=test,                    help=Run the program 5 times in each level)args = parser.parse_args()Disallow specifying two options at once, using groups:parser = argparse.ArgumentParser(description='TODO')group = parser.add_mutually_exclusive_group(required=True)group.add_argument(-l, --level, metavar=RANDOMNESS LEVEL, type=int, choices=range(1,4),                   dest=level, help=How random do you want the passwords [1-3])group.add_argument(-a, --add, metavar=PASS TO ADD,                   dest=add, help=Add a password to the password file)group.add_argument(--test-demo, action=store_true, dest=test,                   help=Run the program 5 times in each level)args = parser.parse_args()or subcommands:parser = argparse.ArgumentParser(description='TODO')commands = parser.add_subparser(metavar='command', dest='command')generate = commands.add_parser('generate', help='create a new password')generate.add_argument('level', type=int, choices=[1, 2, 3],                      help=How random do you want the passwords [1-3])store = commands.add_parser('store', help='Add a password to the password file')store.add_argument('password', help='A password to store in the file')commands.add_parser('demo', help=Run the program 5 times in each level)args = parser.parse_args()if args.command is None:    parser.error('command is required')I like the last version much as it helps separate the logic and the associated values:import argparsedef command_line_parser():    parser = argparse.ArgumentParser(description='TODO')    commands = parser.add_subparser(metavar='command', dest='command')    generate = commands.add_parser('generate', help='create a new password')    generate.add_argument('level', type=int, choices=[1, 2, 3],                          help=How random do you want the passwords [1-3])    store = commands.add_parser('store', help='Add a password to the password file')    store.add_argument('password', help='A password to store in the file')    commands.add_parser('demo', help=Run the program 5 times in each level)    args = parser.parse_args()    if args.command is None:        parser.error('command is required')    return args...if __name__ == '__main__':    options = command_line_parser()    if options.command == 'generate':        print(console_main(options.level))    elif options.command == 'store':        add_to_file(options.password)    elif options.command == 'demo':        test_levels(5)FilesYour add_to_file function append the provided word into the file. There is a special mode to the open function exactly for that purpose:def add_to_file(word):    with open(pass_list.txt, a) as words:        print(word, file=words)You also happen to use the plain filename in this function and take it as a parameter in read_pass_from_file. I would rather define the default filename as a constant that will be used in each function:PASSWORD_FILE = 'pass_list.txt'def add_to_file(word):    with open(PASSWORD_FILE, a) as words:        print(word, file=words)def read_pass_from_file():    with open(PASSWORD_FILE, r) as password:        data = password.readlines()    return random.choice(data).rstrip('\n')You may also note the use of .readlines() instead of .read().splitlines() that required twice as much as memory. The drawback being that, contrary to splitlines, readlines will keep the newline at the end of each string, so we need to clean it.Lastly, I would use an absolute path for the password file rather than a file in the current working directory. It let you easily launch it from an other folder or even make it executable and put it in your PATH:import os.pathPASSWORD_FILE = os.path.join(    os.path.dirname(os.path.abspath(__file__)),    'pass_list.txt')This way, pass_list.txt will always be a file in the same folder than your script.Default valuesAs you don't allow access to these through the command line, you may want to define default values for each of your main functions. You already had them for random_strings, we just modified the logic for read_pass_from_file, so there is only gibberish missing. Doing so will allow you to simplify console_main since it now receive integers directly:def gibberish(count=2):    ....def console_main(randomness_level):    function = [        read_pass_from_file,        gibberish,        random_strings,    ][randomness_level-1]    return function()Even without doing so, you should have called define_randomness_level(opt) only once and stored the result in a variable, as there is no reason to recompute it at each if.DemoThere is absolutely no need to reload the file for each demo as you know that, in the end, console_main will be called. So call it yourself, it will save some resources:def test_levels(amount):    for _ in xrange(amount):        for command in xrange(1, 4):            print(Running with level: {}.format(command))            console_main(command)