The efficiency of a specific while loop - python

I'm making a program for a school project and within the program, the user makes a decision. to make sure the user inputs a valid input. i want to know if using a while loop like this is efficient. it is be contained within a function that would be used multiple times. i did this as this function will be called and the returned value will be stored in a variable. this is for a command line programme:
def user_choice():
while True:
choice = input("choose \'a\' or '\b\':")
if choice == "a" or choice == "b":
return choice
else:
print("not a valid input")

In terms of raw machine efficiency, your algorithm is perfectly acceptable. The bottleneck is the user's input. It only goes through the loop once per input, so the actual execution of that code is basically instantaneous. Some programs will use a loop to continuously check some condition as fast as the computer can manage, and that is indeed suboptimal (eats up a CPU core).
In terms of good coding style, your algorithm is perfectly acceptable as well. Using a while True (or while 1) with a return on a valid input is very common and easily understood to any reader.
I would suggest some slight modifications like if choice.lower().strip() in ('a', 'b'): instead of if choice == "a" or choice == "b": as noted in a comment, but that's just to make it a bit more robust in the face of messy user input. Also, you don't need to escape quotes unless they're of the type that encloses the string, so you can do input("choose 'a' or 'b':") instead of input("choose \'a\' or '\b\':").

Related

How do I use the if / then command in python

So very recently, I have started learning python, and I have come up with a very basic script that should ask the user a question, and move the cursor to a spot on the screen based on the answer that the program receives. But when I run the program it runs the first part of the code, then closes the interpreter as if the program was finished.
import pyautogui
import time
choice = 0
choice = pyautogui.prompt("Which option do you choose? ")
# The code stops working here
if choice == 1:
pyautogui.moveTo(670, 440)
elif choice == 2:
pyautogui.moveTo(690, 440)
elif choice == 3:
pyautogui.moveTo(670, 500)
elif choice == 4:
pyautogui.moveTo(690, 500)
I believe that the issue is with the if / then command but is could be something as simple as an indention error.
I apologise in advance for any formatting mistakes that I made when typing up this question as I am quite new to stack overflow.
I'd like to elaborate on top of #zerecees's already excellent answer to account for possible edge cases that might break down your program.
import time
import pyautogui
while True:
try:
choice = int(pyautogui.prompt("Which option do you choose? "))
break
except ValueError:
print("Please type an integer value.")
if choice == 1:
pyautogui.moveTo(670, 440)
elif choice == 2:
pyautogui.moveTo(690, 440)
elif choice == 3:
pyautogui.moveTo(670, 500)
elif choice == 4:
pyautogui.moveTo(690, 500)
else:
# Some default fallback code
The try and except statement accounts for cases in which the user inputs something that cannot be casted into int. For example, imagine a situation where the user inputs one instead of 1; in such cases, type conversion will not work. Therefore, we use a while loop to prompt the user to input a valid input until a valid input is entered.
Then, since we have converted the input from a string to an integer, the conditionals will work as expected.
pyautogui.prompt() returns a string, and you are checking for int. Try putting quotes around if .. "1", elif .. "2" to make the int's string's.
Or, try:
int(pyautogui.prompt("...") to convert string to int.
The problem here is pyautogui.prompt() is returning a string and you are checking for integer. You can check the return type using,
print(type(choice))
So change the type. If you still get stuck(if you didn't get the prompt window) then there may be some security issues, so you will need to explicitly allow application to use mouse/keyboard. Just have a look at accessibility in security preference and allow the appropriate action. Hope this will help :)

Python game; Why can't I re-call my input and if/else function?

I'm still kind of learning Python, but my friend who has programmed in Python before says this should work fine, but it wont?
All code before this was the beginning story for this basic "escape the room" game I'm making. The code up until here works, (basic print functions describing the game).
I give the player the scenario that they're in a room and they can do one of two things:
def intro_room_input():
intro_action = input("What would you like to do? (Please enter either: 1 or 2) ")
return intro_action;
These two functions are for when they choose 1 or 2, the next if/elif function runs these functions
If they choose 1:
def intro_room_result1():
print(
"""
(Story stuff for the result of option 1. Not important to the code)
""")
return;
This function will play out if they choose 2
def intro_room_result2():
print(
"""
(Story stuff for the result of option 2. Not important to the code)
""")
return;
This will be the function for taking the player's input and continuing the story from there.
def intro_action_if(string):
if string == "1":
intro_room_result1()
elif string == "2":
intro_room_result2()
else:
print("I'm sorry, that wasn't one of the options that was available..."+'\n'+
"For this action, the options must be either '1' or '2'"+'\n'+
"Let me ask again...")
intro_room_input()
intro_action_if(string)
return;
that last intro_room_input runs fine, it re-runs the previous input, but when you actually enter 1 or 2, it doesn't do anything with them. It doesn't want to re-run the if/elif/else function to give the results.
finally I have a main that runs everything:
def main():
string = intro_room_input()
intro_action_if(string)
return;
main()
Please help, I have no idea what's wrong with this code!?
The problem is in your intro_action_if(). When you are calling the function to get values again, you forgot to change the string value.
ie,
#intro_room_input() #wrong
string = intro_room_input() #right
intro_action_if(string)
As you can see, even though in your code you asked for the user input and returned it, you forgot to reassign string with the returned value. Hence it kept the same input you had given previously and passed that old value to intro_action_if().

Trying to call an function with a variable imbedded in the middle of it in Python

I'm creating a program with a menu that asks the user via raw_input to choose a specific chapter of narrative in the program they want to read. That portion of the code is:
mainma = raw_input ("Which chapter do you want? (1-10): ")
Here's where my problem is: I'm trying to code the call so that it will go to the requested chapter that is defined as:
Chap(mainma)menu()
as I was working on the assumption that if mainma = 1, it would invoke Chap1menu() where that particular chapter data is stored, the same if mainma = 3 it would invoke Chap3menu(). The syntax error I'm getting disagrees with me. What am I doing wrong?
You don't really want to try to turn user input directly into variable names; it's possible but it's messy, brittle, and insecure. What you want to do instead is create a list or dictionary that maps the input to the chapter functions; something like:
chapters = {'1': Chap1Menu,
'2': Chap2Menu,
#etc.
}
and then use it like so:
chapters[mainma]()
Just use m as a parameter to the function Chapmenu. For example:
def Chapmenu(m):
if m == 1:
# code for Chap1menu goes here
elif m == 2:
# code for Chap2menu goes here
elif m == 3:
# code for Chap3menu goes here
etc.
Then just call Chapmenu(mainma)
You have an opportunity for efficient code design here -- presumably the chapter menu functions share a lot of behavior between them. You shouldn't every copy-and-paste code in a program like that, because it's harder to read and a risk to update (too easy to get them out of whack with each other).
Whatever you were putting into Chapter#Menu, write a sliiiightly more general function that accepts the chapter number as an argument. This helps reuse code that's similar between chapters. The differences get stored in some list or dictionary (here insides, minimal):
insides = list('qwertywuiop')
def ChapterMenus(chapternum, **kwargs):
print('Chapter %d: About the Letter %s'%(chapternum, insides[chapternum]))
print(kwargs)
if __name__=='__main__':
mainma = raw_input("Which chapter do you want? (1-10): ")
mainma = int(mainma) #Be more helpful validating this
if mainma in range(1,11):
ChapterMenus(mainma, text='Ants')
else:
print('Tsk! A real chapter number, please!')

Problems simulating a dice in Python

I'm new to python and I'm trying to create a simple program to allow the user to choose to use an 8, 12 or 24 sided dice and then display the result from rolling it.
This is my code but I am unsure why when I enter whether I wish to roll again it produces an error.
import random
def dice4():
min=1
max=4
print random.randint(min, max);
return;
def dice6():
min=1
max=6
print random.randint(min, max);
return;
def dice12():
min=1
max=12
print random.randint(min, max);
return;
roll = "yes"
y = 1
while roll == "yes" or roll == "y":
x = input("What dice do you want to use? 4/6/12?");
if x ==8:
dice4();
elif x==12:
dice6();
elif x==16:
dice12();
else:
print "You have not entered a valid dice number";
roll = input("Do you want to roll again? y/n");
print "Thanks for rolling!";
input("Press <Enter> to quit");
Thanks for any help, I realise it is probably a trivial error.
I am unsure why when I enter whether I wish to roll again it produces an error.
So the problem is in that part of the code. Let's look at it:
roll = input("Do you want to roll again? y/n");
You're using the input function. That will try to evaluate whatever you type as a Python expression.
So, if you type yes, it will try to find the value of yes, and raise a NameError. The same for n or no.
If you type y, the same thing should happen… except because you happen to have an extraneous variable named y lying around, it will actually find a value, 1. So, when you check that later, in while roll == "yes" or roll == "y":, obviously 1 is not equal to either of those strings, so it will just exit.
The only thing you could type that would work is "y" or "yes" (or the same with single quotes). Which obviously you don't want your users to have to type.
The solution is to use raw_input instead of input, which just gives you the input as a string, instead of trying to evaluate it.
roll = raw_input("Do you want to roll again? y/n");
This is one reason using input is usually a bad idea. Even in the earlier case, where you want an integer, the errors for typos are going to be ugly.
For an even better reason, see what happens when you type __import__('os').system('dir C:\\') (substitute ls / if you're on Unix instead of Windows), and imagine how much mischief your user could cause with other inputs.
So, I would recommend always using raw_input. If you want to convert the input to an integer, pass it to int. If you want to treat it as a literal value of any type, use ast.literal_eval. If you really want to evaluate it as arbitrary Python code, use eval (which is no safer than input, but at least it's more explicit).
Let me just add other problems with your code. You overwrite the builtins minand max in your functions and you use semicolons which are not needed. You use empty return statements.
Then you repeat too much code with dice4, dice6 and dice12. You should use one function with a parameter. Now you will be able to throw a lot of different dice.
def dice(value):
print random.randint(1, value)
dice(4)
dice(6)
dice(8)

sys.exit(): is there a less extreme alternative?

I was hoping to get some kind of advice regarding the use of sys.exit(). I have used sys.exit() to stop a script from running any further:
My code:
if x != 20:
query = raw_input("X does not equal 20. Do you want to continue? (Y/N)")
if query in ['y', 'Y']:
pass
else:
sys.exit()
I have done some searching (and am still searching) but I was hoping to gain some clarity regarding the best practices use of sys.exit(). Is there something a little less extreme than killing the script? I'm considering the combination of extra loop and a more inquisitive question.
Since this is used at the beginning of your script (as mentioned by you). Use the coding pattern written below. And use return instead of sys.exit (actually since you are exiting from the script itself thereby terminating the process altogether sys.exit is not a bad practice).
Whether you use return or sys.exit return appropriate integer. its a good practice.
def do_something():
//process something
return 1
if __name__ == '__main__':
query = raw_input("Do you want to continue? (Y/N)")
if query.lower() == 'y':
//do something
do_something()
else:
print 'ERROR: Cant Understand Input. It has to be (Y/N). Exiting...'
return 0
Place your code in the main() function:
def main():
// your code
if __name__ == '__main__':
main()
Then you can exit from your script just by return
As other commenters have noted, a function provides you the option of using return to bail out from anywhere in the script and that's probably the best choice - I just thought I'd mention one piece of trivia:
Technically sys.exit raises a SystemExit exception. You can catch a SystemExit with an ordinary try-catch, effectively 'canceling' the exit if you want to. This might come in handy if you have more complex code in which a low level function might not have enough context to make a good decision but higher level code may want to step in and clean up.
People from C/C++ and lots of other languages get hives from this tactic but it's not uncommon in Pythonland. It's not a good idea to use exceptions for flow control in general but if you are using other people's functions, or combining smaller scripts into a larger application, it may be useful.
Don't forget, btw, that if control falls through to the bottom of your script it will just return 'None' anyway, so in your example:
if x != 20:
query = raw_input("X does not equal 20. Do you want to continue? (Y/N)")
if query in ['y', 'Y']:
// do something
is the same as
if x != 20:
query = raw_input("X does not equal 20. Do you want to continue? (Y/N)")
if query in ['y', 'Y']:
// do something
else:
sys.exit()
EXCEPT for the fact that you could try/catch around the second but not the first... though I assume your example is snipped from a larger sample where return is the better escape route

Categories