What is going on with scope in this beginners Python program? - python-3.x

I'm a fairly experienced C# programmer taking a look at Python. I'm working from a book called Invent Your Own Computer Games with Python. Chapter two features this game...
# This is a Guess the Number game.
import random
# guessesTaken = 0
print ("Hello! What is your name?")
myName = input()
number = random.randint(1,10)
print("Well, " + myName + ", I am thinking of a number between 1 and 10.")
for guessesTaken in range(6):
print ("Take a guess.") # Four spaces in front of "print"
guess = input()
guess = int (guess)
testNumber = 5
if guess < number:
print("Your guess is too low.") # Eight spaces in front of "print"
if guess > number:
print ("Your guess is too high.")
if guess == number:
break
if guess == number:
guessesTaken = str(guessesTaken + 1)
print ("Good job, " + myName + "! You guessed my number in " + guessesTaken + " guesses!")
if guess != number:
number = str(number)
print ("Nope. The number I was thinking of was " + number + ".")
testNumber = str(testNumber)
print (testNumber)
I was a little confused about the guessesTaken variable, so I commented out it's instantiation line - and was surprised to find the code still behaved as normal.
In C#, I would expect guessesTaken in the For declaration to be out of scope for the rest of the program. However, later uses of the variable have no problem accessing the number in question.
To test this, I added the variable 'testNumber' within the For block and assigned it '5'. Sure enough, attempting to print the number outside of it's scope just works...!
I'm a little rusty with programming, but this is still a bit confusing to me - is the For loop not considered a different scope to the block surrounding it? This page seems to suggest otherwise, but I guess it could be that For loops in Python are considered in scope with their surroundings.
Just seems a bit weird. Can anybody put me right here? I'll get back into C# to test this in that language later, but any responses now would be much appreciated. Thanks!

The for guessesTaken in range(6): creates the variable guessesTaken without you needing to explicitly do it, that is why the following code would also work (results in 9):
for x in range(0, 10):
pass
print(x)
However, this would result in a NameError:
print(x)
for x in range(0, 10):
pass
This is likely hidden away somewhere in the documentation.
The reason you can access testNumber is that it's assigned local scope by default; unless it's explicitly set, which in your code, happens to be local to the entire script. The for loop is not considered to be in a different scope.

Related

Implement a while loop in my hangman type/puzzle game

I'm new to python programming. I would like to display a win message after every correct letter input and no message if an incorrect letter is input.
I've written my code such that it will only accept one letter at a time and reduce an attempt by 1, regardless of if it is wrong or right.
How would I be able to implement a while loop into this so that I don't keep getting this error:
builtins.TypeError: 'str' object does not support item assignment
word="banana"
word_list=list(word)
length=len(word_list)
word_list= set(word_list)
word_list=list(word_list)
answer=["_"]*length
answer=list(answer)
guess=[]
count = 4
win=False # boolean so we do not use an identifier in our if statements
user_guess=window.input_string("Guess a letter: ", x, y)
y = y + font_height
guess.append(user_guess)
while count > 0:
# Removes guesses if they are not in the word so that the blanks do not fill in with incorrect letters
for letter in guess:
if letter not in word_list:
guess.remove(letter)
else:
win=True
# Replaces blanks in empty list with the letter guess
for place,letter in enumerate(list(word)):
for i in range(len(guess)):
if letter == guess[i]:
answer[place]=guess[i]
answer=" ".join(answer)
update_message = 'The answer so far is: '
window.draw_string(update_message + answer,x,y)
y = y + font_height
#End Game
win_message = 'Good job! You got the word.'
lose_message = 'Not quite, the correct word was: '+word +' Better luck next time'
if win:
window.draw_string(win_message,x,y)
y = y + font_height
count -=1
else:
window.draw_string(lose_message,x,y)
y = y + font_height
count -=1
Please notice this assignment: answer=" ".join(answer). Before the assignment, answer is a list of string. After the assignment, answer becomes a string.
So, in the next iteration of the while loop, answer[place]=guess[i] turns invalid, because python does not allow modifying a string by assigning a "character" to some place of the string.
It really takes some time to find the fault. You'd better provide the information, like, "which line in the program targeted the error message", when asking questions in future.

My randomNumber generator play again acts weird

Now this is pretty beginner version of randomNumber guessing quiz code in Python. Many of you can look at it and make it 4 times shorter and I get it. However the point of this question is that I am unsure of the logic behind this problem.
If you execute this code, it will work just fine - till you get to Play again part.
When you type No - the program as intended quits. Yes starts again.
However if you type Yes first and the game goes one more time and you decide that it is for now I want to quit - this time you have to enter No twice.
And then it quits. I have no idea why. Ideas?
import random
def game(x,y):
con = True
count = 0
ranNum = random.randint(x,y)
while con:
userInput = int(input("Take a guess between these numbers {} and {}: ".format(x,y)))
count +=1
if userInput == ranNum:
print("You got it!")
print("Number of tries: {}.".format(count))
print("Play again?")
while True:
again = input("> ")
if again.lower() == "yes":
game(x,y)
elif again.lower() == "no":
con = False
break
elif userInput < ranNum:
print("Wrong! Try higher!")
elif userInput > ranNum:
print("Wrong! Try lower!")
game(5,10)
The simplest solution: You need a break or return after calling game() when the user answers 'yes'.
This has an unfortunate side effect where each successive game adds another call to the stack. A better design would move the 'play again?' question out of the game function.
The problem is you are running game(x, y) inside itself. Therefore, con is a local variable. Think of it this way: when you type no the first time, it exits out of the game(x, y) that is running inside game(x, y) Therefore, the number of times you have played is the number of times you need to type no. An easier way to do this would be, like #xulfir says, put the "Play again?" loop outside of the function, or you could put exit() instead of using break to just kill the program.

Do Not Display Message While in IF statement

I am going through "Python Programming for the absolute beginner - Third Edition" and inside of that there is a word jumble game. One of the exercises is to offer a hint to the user when they have guessed incorrectly. I have it working however when the hint statement is executed it also prints out the incorrect guess message, and I am stumped as to how to make it not print the incorrect guess message while in the hint.
Here is the code:
guess = input("\nYour guess: ")
wrong_guess_msg = "Sorry that was not correct. Try again."
while guess != correct and guess != "":
print(wrong_guess_msg)
guess = input("Your guess: ")
guess_count += 1
# Ask the user if they would like a hint after 4 wrong guesses
if guess_count == 4:
hint_response = input("Would you like a hint?: ")
if hint_response.lower() == "yes":
print("Here is your hint: {}".format(hint))
else:
guess = input("Your guess: ")
I have tried pulling the if guess_count statement out of the while loop, however it never executes. I tried change the while loop to include a condition where the guess_count != 4 and that sort of worked, meaning I didn't get the incorrect guess when I got the hint, however it exited the program.
Just not really sure what I am missing here.
You could structure the whole loop differently, thereby also eliminating the first prompt before the start of the loop:
wrong_guess_msg = '...'
while True:
guess = input('Your guess: ')
guess_count += 1
if guess == correct:
break
if guess_count == 4:
...
You may argue that this is leaving the while condition unused, but by that, you gain the chance to write your code straight and clean, exactly because you're not forced to put the correctness check right in front of each iteration. Always (if possible) let yourself be guided by how you would spell out an algorithm if explaining it to the next person, rather than by loop mechanics or other technical constraints.

using a looop to add user input to the set and dict in order discovered

So I'm trying to write a small program that does a few things. First is:
Write a while loop that repeatedly creates a list of words from a line of input from the user. So I did this:
s = input("Please enter a sentence: ")
while True:
pos = 0
for c in s:
if c == " ":
print(s[:pos])
s = s [pos+1:]
break
pos += 1
else:
print(s)
break
I need to add the user inputted words to the set and dict and then display their value in the order in which the program discovered them. I believe I need another loop but I'm not sure. I'm pretty lost at this point and the above is as far as I can seem to come on this program. Any help is appreciated as I am (obviously)new at python.

using .find for multiple letters in a string

I'm working on a hobby project. I'm attempting to make a hangman game in Python. So far everything works nice. There's just one problem. If I type a letter that appears in the word two times, I can't get the second letter to appear. I've been toying around with string.find and string.count methods but to no avail. Does anyone have an idea how I would go about doing this? I'm stumped.
#!bin/bash/python
import os
import time
word = 'megalopolis'
l = len(word)
list = []
n=0
while n!=l:
list.append('-')
n+=1
os.system('cls' if os.name=='nt' else 'clear')
print list
i=3
while i!=0:
x = raw_input('Enter a letter: ')
if x in word and x!='':
print 'Good choice!'
count=word.count(x)
loc=word.find(x)
print count
print loc
list[loc]=x
os.system('cls' if os.name=='nt' else 'clear')
if '-' not in list:
break
print list
else:
print 'Sorry...'
i-=1
if i==2:
print 'You have '+`i`+' more chances.'
if i==1:
print 'You have '+`i`+' more chance!'
time.sleep(1)
os.system('cls' if os.name=='nt' else 'clear')
print list
if '-' not in list:
print 'YOU WIN!!'
else:
print 'GAME OVER!!'
x = raw_input('press enter')
If you just need the index of every character occurence:
indexes = [idx for idx, ch in enumerate(word) if ch == x]
Perhaps you should use Unidecode to keep the accents in words, it might be useful depending on the language (if not English). Also, you can use str.lower() or str.upper() methods to ensure every word and trial is in the same case.
The string module has useful constants for you (e.g. ascii_uppercase).
However, in this game you don't need to worry about any index. I've made another version for you:
#!/usr/bin/env python
from string import ascii_uppercase
word = "megalopolis".upper() # Top-secret!
trial = 3 # Total trials available (number of mistakes to lose the game)
typed = set() # Typed characters
word_letters = set(word)
while trial:
print
print "".join(ch if ch in typed else "-" for ch in word)
# Winning condition
if typed.issuperset(word_letters):
break
# Data input
x = raw_input("Enter a letter: ").upper()
# Error cases
if len(x) != 1:
print "Invalid input."
continue
if x in typed:
print "Already typed."
continue
if x not in ascii_uppercase:
print "What you typed isn't a letter."
continue
# Valid data cases
typed.add(x)
if x in word:
print "Good choice!"
else:
print "{} not found!".format(x),
trial -= 1
if trial == 1:
print "You have one more chance!"
elif trial > 1:
print "You have {} more chances.".format(trial)
else:
print 'Sorry...'
# Ending message
print
if trial:
print "YOU WIN!!"
else:
print "GAME OVER!!"
Hashbang: Your shebang should usually start with "#!/". You're probably using Windows, so the "bin" as a relative directory wasn't used by you.
"l" / l as a variable name should be avoided! It might be seen as one or lower "L" (PEP8), or even a pipe "|". PS: At the beginning of this item, I typed the same letter here twice.
There's no need to use "list" as a variable name here, and you shouldn't do, as that's a built-in name.
Multiplication like "txt" * 3 returns "txttxttxt" (it repeats the data) for both strings and lists
Neither "cls" nor "clear" worked here, showing
"TERM environment variable not set."
instead of clearing the console screen. I replaced these with an empty
"print", and removed the time sleep. Look for subprocess if you want to call something from console (although I'd also look for curses if there's a need to do some CLI visualization).
Suppose x is a string. When x == "", bool(x) is False, else bool(x) is True.
Suppose x is an integer. When x == 0, bool(x) is False, else bool(x) is True.
Avoid backticks (`). No one uses them today in Python, they doesn't exist in Python 3 and you can use the repr built-in instead. However, you probably wanted something like str(trial), "%d" % trial or "{}".format(trial).
The last "press enter" probably has to do with an operating system "auto-close-after-finish" behaviour, but you [at least] didn't need to store it in x.
I've used a generator expression. You should read here about list comprehensions if the "for" in the middle of one line is confusing for you. Python developers use generator expressions and list comprehensions all the time, you shouldn't avoid learning about them.
I replaced the original winning evaluation to a comparison between the set of characters the word originally has and the set of typed characters (both uppercase).
If there's something here you didn't understand, please ask a new question.
This SO question ought to cover it for you:
Finding multiple occurrences of a string within a string in Python
It should work just as well for individual characters as strings, considering how easy it is to form the second from the first.
So in the end, I wound up doing it this way:
if x in word and x!='':
count=word.count(x)
loc=0
while count==1 or count>1:
loc=word.find(x,loc)
list[loc]=x
loc+=1
count-=1
print 'Good choice!'
Thanks for your help everyone. I definitely learned something.

Resources