Hangman program w/ Python and JSON
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.