Hangman program w/ Python and JSON What improvements can I make? Should I organize my functions based on A-Z? For example, find_letter function comes before guessing function. 

import json
import random
import re


def find_letter(user_input, vocab, i=None):
    return [i.start() for i in re.finditer(user_input, vocab)]


def guessing(tries,vocab,blank_line):
    while tries &gt; 0:
        user_input = input("Guess:")

        if user_input in vocab:
            if not(user_input in blank_line):
                index = find_letter(user_input=user_input,vocab=vocab)
                i = 0
                while i &lt; len(index):
                    blank_line[index[i]] = user_input
                    i += 1

                if not('_' in blank_line):
                    return "you won!!!"
            else:
                print("YOU ALREADY TRIED THAT LETTER!")
        else:
            tries -= 1

        print(str(blank_line) + "  Number of Tries : " + str(tries))

    return "you lost"


def hangman_game(vocab):
    'Preliminary Conditions'
    print(introductory(vocab=vocab))
    tries = num_of_tries(vocab=vocab)
    blank_line = produce_blank_lines(vocab=vocab)
    print(blank_line)

    print(guessing(tries=tries,vocab=vocab,blank_line=blank_line))


def introductory(vocab):
    return "HANGMAN GAME. So...what you gotta do is guess and infer what the word might be. " \
           "WORD COUNT : " + str(len(vocab))


def num_of_tries(vocab):
    if len(vocab) &lt; 7:
        return 8
    elif 7 &lt;= len(vocab) &lt; 10:
        return 10
    else:
        return 14


def produce_blank_lines(vocab):
    blank_line = []
    for i in vocab:
        if i.isalpha():
            blank_line += "_"
        elif i == " ":
            blank_line += " "
        else:
            blank_line += i + " "

    return blank_line

if __name__ == '__main__':
    data = json.load(open("vocabulary.json"))
    vocab = random.choice(list(data.keys())).lower()
    print(vocab)
    print(hangman_game(vocab=vocab))


Snippet of the JSON File

{"abandoned industrial site": ["Site that cannot be used for any purpose, being contaminated by pollutants."], "abandoned vehicle": ["A vehicle that has been discarded in the environment, urban or otherwise, often found wrecked, destroyed, damaged or with a major component part stolen or missing."], "abiotic factor": ["Physical, chemical and other non-living environmental factor."], "access road": ["Any street or narrow stretch of paved surface that leads to a specific destination, such as a main highway."], "access to the sea": ["The ability to bring goods to and from a port that is able to harbor sea faring vessels."], "accident": ["An unexpected, unfortunate mishap, failure or loss with the potential for harming human life, property or the environment.", "An event that happens suddenly or by chance without an apparent cause."], "accumulator": ["A rechargeable device for storing electrical energy in the form of chemical energy, consisting of one or more separate secondary cells.\\n(Source: CED)"], "acidification": ["Addition of an acid to a solution until the pH falls below 7."], "acidity": ["The state of being acid that is of being capable of transferring a hydrogen ion in solution."], "acidity degree": ["The amount of acid present in a solution, often expressed in terms of pH."], "acid rain": ["Rain having a pH less than 5.6."]
data = json.load(open("vocabulary.json"))


Unless Python 3 has changed this, I think you're leaking the open file handle here. What you want is more like

with open("vocabulary.json") as f:
    data = json.load(f)


which IMHO is even a bit easier to read because it breaks up the mass of nested parentheses.

vocab = random.choice(list(data.keys())).lower()


Same deal with the nested parentheses here. We can save one pair by eliminating the unnecessary materialization into a list. [EDIT: Graipher points out that my suggestion works only in Python 2. Oops!]

Also, vocab (short for vocabulary, a set of words) isn't really what this variable represents. data.keys() is our vocabulary; what we're computing on this particular line is a single vocabulary word. So:

word = random.choice(data.keys()).lower()
print(word)
print(hangman_game(word))


I can tell you're using Python 3 because of the ugly parentheses on these prints. ;) But what's this? Function hangman_game doesn't contain any return statements! So it basically returns None. Why would we want to print None to the screen? We shouldn't be printing the result of hangman_game; we should just call it and discard the None result.

word = random.choice(data.keys()).lower()
print(word)
hangman_game(word)




def hangman_game(vocab):


Same comment about vocab versus word here.

'Preliminary Conditions'


This looks like you maybe forgot a print? Or maybe you're trying to make a docstring? But single quotes don't make a docstring AFAIK, and even if they did, "Preliminary Conditions" is not a useful comment. I'd just delete this useless line.

print(introductory(vocab=vocab))


In all of these function calls, you don't need to write the argument's name twice. It's a very common convention that if you're calling a function with only one argument, the argument you pass it is precisely the argument it needs. ;) Also, even if the function takes multiple parameters, the reader will generally expect that you pass it the right number of arguments in the right order. You don't have to keyword-argument-ize each one of them unless you're doing something tricky that needs calling out. So:

print(introductory(word))


And then you can inline introductory since it's only ever used in this one place and it's a one-liner already:

print(
    "HANGMAN GAME. So...what you gotta do is guess and infer what the word might be. "
    "WORD COUNT : " + str(len(vocab))
)


Personally, I would avoid stringifying and string-concatenation in Python, and write simply

print(
    "HANGMAN GAME. So...what you gotta do is guess and infer what the word might be. "
    "WORD COUNT : %d" % len(vocab)
)


I know plenty of working programmers who would write

print(
    "HANGMAN GAME. So...what you gotta do is guess and infer what the word might be. "
    "WORD COUNT : {}".format(len(vocab))
)


instead. (I find that version needlessly verbose, but it's definitely a popular alternative.)

tries = num_of_tries(vocab=vocab)
blank_line = produce_blank_lines(vocab=vocab)
print(blank_line)
print(guessing(tries=tries,vocab=vocab,blank_line=blank_line))


It's odd that you abbreviated number_of_tries to num_..., but then left in the word of. I would expect either number_of_tries or num_tries (or ntries for the C programmers in the audience); num_of_tries is in a weird no-man's-land.

It's weird that you have a function named produce_blank_lines that produces actually a single blank_line. It's even weirder that the value of blank_line is not a constant "\n"! This suggests that everything here is misnamed. You probably meant underscores = produce_underscores(word) or even blanks = shape_of(word).

Finally, it is weird that blank_line is not even a string, but rather a list of characters.

I would replace this entire dance with something like

blanks = ''.join('_' if c.isalpha() else ch for ch in word)


or

blanks = re.sub('[A-Za-z]', '_', word)


This would lose your addition of a blank space after each non-space-non-alpha character, but IMHO that's actually an improvement over your version. (Your sample JSON doesn't show any examples of words containing non-space-non-alpha characters. My version certainly performs better for words like non-space or Route 66.)



if not(user_input in blank_line):


This would be idiomatically expressed (both in Python and in English) as

if user_input not in blank_line:




index = find_letter(user_input=user_input,vocab=vocab)
i = 0
while i &lt; len(index):
    blank_line[index[i]] = user_input
    i += 1


You have another singular/plural problem here (just like with blank_line[s]). It doesn't make sense to get the len of a singular index. Since the variable actually represents a list of indices, we should name it [list_of_]indices.

Also, this style of for-loop is very un-Pythonic. In Python we can say simply

for i in find_letter(user_input, word):
    blank_line[i] = user_input


And then we can inline the single-use find_letter:

for m in re.finditer(user_input, word):
    blank_line[m.start()] = user_input


(Using m as the conventional name for a match object.)
And then we can eliminate the regex and just loop over the word directly:

for i, ch in enumerate(word):
    if ch == user_input:
        blank_line[i] = user_input


or

for i in range(len(word)):
    if word[i] == user_input:
        blank_line[i] = user_input


In fact, by not using regexes, we fix a bug in your code: if the chosen vocabulary word contains . as one of its non-space-non-alpha characters, a user who enters . as their guess will win the game immediately! :D

Anyway, that's enough for one answer, I think.