I'm looking to use functions within my Python program to make it cleaner and more efficient, Within my function I either return true or false depending on a user's selection. Though in the case they enter an incorrect / invalid response, I'd like to return a return that way asking more questions don't be asked.
Edit:
To be a bit more descriptive; I'd like to re-create this:
def askquestion(question):
response = input(question, "Enter T or F")
if response == "T":
return True
elif response == "F":
return False
else:
return None
def askmultiple():
questionOne = askquestion("Do you fruits?")
if questionOne == None:
return # Exit the function, not asking more questions
questionTwo = askquestion("Do you Apples?")
if questionTwo == None:
return # Exit the function, not asking more questions
I want to cut out checking afterwards if it is None and just return return.
When you don't create a return statement at the end of a function that is equal to a sole return and those two are equal to return None call.
So you may organize your code like:
if returned_value is None:
# do something a
elif returned_value is False:
# do something else
else: # value is True
# do something b
You can try using a while loop to make sure that the user inputs a correct input.
For example:
while not response.isdigit():
response = input("That was not a number try again")
In this case, while the user input, "response" is not a number the python console will keep asking for a response. For a basic template,
while not (what you want):
(ask for input again)
I hope this helps you. :)
Use an exception flow.
def ask_question(prompt):
"""Asks a question, translating 'T' to True and 'F' to False"""
response = input(prompt)
table = {'T': True, 'F': False}
return table[response.upper()] # this allows `t` and `f` as valid answers, too.
def ask_multiple():
questions = [
"Do you fruits?",
"Do you apples?",
# and etc....
]
try:
for prompt in questions:
result = ask_question(prompt)
except KeyError as e:
pass # this is what happens when the user enters an incorrect response
Since table[response.upper()] will raise a KeyError if response.upper() is neither 'T' nor 'F', you can catch it down below and use that flow to move you out of the loop.
Another option is to write a validator that forces the user to answer correctly.
def ask_question(prompt):
while True:
response = input(prompt)
if response.upper() in ['T', 'F']:
break
return True if response.upper() == 'T' else False
Related
I'm tired of trying to make a menu that let's me choose from a dictionary keys and in every value I have the choice. I found that I can use dictionary and get() method, it's working fine, but I should use if else statement after get() to execute a function that answers the user choice. Can I do it better? Maybe using a lambda inside the key value?
def menu():
print("Welcome to Our Website")
choises={
1:"Login" ,
2:"Register",
}
for i in choises.keys(): # Loop to print all Choises and key of choise !
print(f"{i} - {choises[i]}")
arg=int(input("Pleasse Chose : "))
R=choises.get(arg,-1)
while R==-1:
print("\n Wrong Choise ! Try again ....\n")
menu()
else:
print(f"You Chosed {R}")
if R==1:
login()
if R==2:
register()
def register():
print("Registration Section")
def login():
print("Login Section")
menu()
you can simulate a switch statement using the following function definition:
def switch(v): yield lambda *c: v in c
You can use it in C-style:
x = 3
for case in switch(x):
if case(1):
# do something
break
if case(2,4):
# do some other thing
break
if case(3):
# do something else
break
else:
# deal with other values of x
Or you can use if/elif/else patterns without the breaks:
x = 3
for case in switch(x):
if case(1):
# do something
elif case(2,4):
# do some other thing
elif case(3):
# do something else
else:
# deal with other values of x
It can be particularly expressive for function dispatch
functionKey = 'f2'
for case in switch(functionKey):
if case('f1'): return findPerson()
if case('f2'): return editAccount()
if case('f3'): return saveChanges()
I am comparing key elements in a list with dictionary keys
event_test = {
"EventType": "ShipmentDueDate",
"Endpoint": "https://example.net"
}
events_list = ['EventType','Endpoint']
body = "https"
I wrote a custom function as follows
def validate_input(event_keys,body):
count = 0
for list_item in events_list:
if list_item in event_keys:
count+= 1
if count != len(events_list):
print("One/All of: 'EventType','Endpoint' parameters are missing.")
if not "https" in body:
print("Only https endpoints are accepted")
return bool
I want to execute the other part of my code, only if the function executes without any errors. I dont understand how to specify a return value for the function and execute my code based on the return value
I am trying this : calling my function first
validate_response = validate_input(list(event_test.keys()),body)
if validate_response == False:
print("error in your input")
try:
print("execute my rest of the code")
Is this the right way to do?
First things first, I see a couple of issues
1. There is an issue with your eval condition (you are missing an else)
if validate_response == False:
print("error in your input")
else:
print("execute my rest of the code")
2. Your function is not returning True or False AND the not in operator isn't used correctly when checking for 'https'
def validate_input(event_keys,body):
count = 0
for list_item in events_list:
if list_item in event_keys:
count+= 1
if count != len(events_list):
print("One/All of: 'EventType','Endpoint' parameters are missing.")
return False
if "https" not in body:
print("Only https endpoints are accepted")
return False
return True
Moving on...
I am a bit unclear as to what you're referring to as the "right way"
I'll take a stab at it and assume you are referring to reducing the complexity and/or lines of code in your sample...
You may try to use the following "optimizations"
Changing your function to use list comprehensions
NOTE: I did change the name of the "body" function param to "body_str" as it shadowed the body variable from the outer scope. Please avoid this as a rule of thumb
def validate_input(event_keys, body_str):
count = len([x for x in events_list if x in event_keys])
# print count to debug
print(f"Count is {count}")
if count != len(events_list):
print("One/All of: 'EventType','Endpoint' parameters are missing.")
return False
if "https" not in body_str:
print("Only https endpoints are accepted")
return False
return True
The following line essentially returns a new list of elements that match your if condition, it then uses the len operation to count the number of elements that matched the said condition
count = len([x for x in events_list if x in event_keys])
Changing your evaluation
One possibility (I would personally use this)
if not validate_response:
print("error in your input")
else:
print("execute my rest of the code")
Another possibility is to get rid of temporary variable assignment altogether - reduces readability though
# validate_response = validate_input(list(event_test.keys()),body)
if not validate_input(list(event_test.keys()), body_str):
print("error in your input")
else:
print("execute my rest of the code")
First of all, you never defined the value of bool in your code sample. bool is a builtin function, so validate_input will actually always return the function bool, which will cause the program not to work as intended.
A better way to implement this would be to return False if either of the error conditions are met, and return True otherwise, like so:
def validate_input(event_keys,body):
count = 0
for list_item in events_list:
if list_item in event_keys:
count+= 1
if count != len(events_list):
print("One/All of: 'EventType','Endpoint' parameters are missing.")
return False
if not "https" in body:
print("Only https endpoints are accepted")
return False
return True
Additionally, validate_input isn't actually raising any exceptions, it's simply returning True or False based on whether the function parameters are valid. There's no need for a try-except statement; you can simply use an if-else statement, like so:
if validate_response:
print("execute my rest of the code")
else:
print("error in your input")
With the above changes, "error in your input" will be printed if validate_response is False. validate_input will return True if all items found in events_list are also present in parameter event_keys, and "https" is in body.
Is there a keyword that I can use to iterate a for loop without stepping the iterator? I know that it's possible to do this without such a command, by using a while loop and iterating manually, but it would greatly simplify things, in this instance, if I could just use a for loop, since continuing without iteration is the exception, not the rule. (there will significantly more conditions added to this when it is complete, all of which will require iteration). Here's my code (or, what I've written so far):
for line in file_as_list:
response = input(line)
if response.lower() == 'help':
self.show_command_list()
response = input(line)
if response.lower() == 'loc':
self.show_location(file_as_list, location)
response = input(line)
if response.lower() == 'exit':
save_changes = input('Would you like to save the changes you have made? (Y/N) ')
while (save_changes.upper() != 'Y') & (save_changes.upper() != 'N'):
save_changes = input('That is not a valid response. Try again. ')
if save_changes.upper() == 'N':
temp = file_as_list
print('Changes were not saved.')
else:
for line in file_as_list[location:]:
temp.append(line)
print('Changes were saved.')
break
if response.lower() == 'inline':
line += ' //' + input(line + ' //')
print('Line generated: ' + line)
location += 1
temp.append(line)
I think you want two nested loops. Try something like this:
for line in file_as_list: # outer loop
while True: # inner loop
response = input(line).lower()
if response == 'help': # these two options don't exit the inner loop
...
elif response == 'loc': # so they won't consume a line from the for loop
...
else:
break
if response == 'exit': # these other two conditions are outside the while loop
...
elif response == 'inline': # so a new line will be fetched after they run
...
If either of the first two conditions are met, the inner loop will keep on running without changing line. Only if the break gets hit will the inner loop end, and the other conditions get tested. After they do their thing, a new value will be assigned to line, as the for loop continues iterating.
Unrelated to your main question, I also changed the input line to call lower on the input immediately before saving it to response. That means the conditions don't need to keep calling it repeatedly. Your code isn't wrong there, but if you never care about the user's capitalization, throwing it away right off the bat can simplify things.
You can use an explicit iterator like
it = iter(file_as_list)
for line in it:
input(line)
...
input(next(it))
and so on. Just be sure to properly handle the case where you run out of lines!
You have two types of commands: ones that advance the iterator, and ones that don't. You could also call it action vs descriptive commands. Your best bet conceptually is to have a while loop that will continue to seek input until you get an action command. This while loop will live inside the existing for loop.
The advantage of this is that currently, your descriptive commands like "help" and "loc" can't be repeated, but you probably want them to be.
Another decision I would recommend is to use distinct functions to implement each command. By giving the commands a consistent interface, you make the code easier to maintain and understand. By registering the commands in a dictionary, you can make your lookup faster and more flexible.
The following concept has a bunch of functions that return a tri-state boolean value and an update. The boolean is True if the command wants to stay on the current line, False to continue. None to exit. The line update is usually just the input.
# in __init__
...
self.command_map = {
'help': self.help,
'loc': , self.loc,
'exit': self.exit,
'inline': self.inline,
}
self.temp = []
...
def help(self, file_as_list, location, line):
self.show_command_list()
return True, line
def loc(self, file_as_list, location, line):
self.show_location(file_as_list, location)
return True, line
def exit(self, file_as_list, location, line):
save_changes = ''
while len(save_changes) != 1 or save_changes.upper() not in 'YN':
save_changes = input('Would you like to save the changes you have made? (Y/N) ')
if save_changes.upper() == 'N':
self.temp = file_as_list
print('Changes were not saved.')
else:
self.temp.extend(file_as_list[location:])
print('Changes were saved.')
return None, line
def inline(self, file_as_list, location, line):
line += ' //' + input(line + ' //')
print('Line generated: ' + line)
return True, line
def process(self):
for location, line in enumerate(file_as_list):
stay = True
while stay:
response = input(line)
command = command_map.get(response.casefold())
if command is None:
print(f'Command "{response}" not found. Try again')
else:
stay, line = command(file_as_list, location, line)
if stay is None:
break
self.temp.append(line)
Given command_map, you can do lots of things easier: for example, you can reimplement show_command_list to do something with sorted(command_map.keys()). I'm sure you can see how relatively easy it is to add commands to your list. You don't have to repeat boilerplate code, just be careful with the inputs and return values.
This construction is also much easier to iterate manually if you don't like the idea of having nested loops:
def process(self):
stay = False
iterator = enumerate(file_as_list)
while True:
if not stay:
try:
location, line = next(iterator)
except StopIteration:
break
response = input(line)
command = command_map.get(response.casefold())
if command is None:
print(f'Command "{response}" not found. Try again')
stay = True
else:
stay, line = command(file_as_list, location, line)
if stay is None:
break
if not stay:
self.temp.append(line)
As you can see, this method requires quite a bit more special handling for the various conditions.
Is there any pythonic way to deal with wrong user input? I'm creating a module to help people work with files, and I have functions like rename, move, basepath, etc. Example:
def move(file_path):
# do something and return
I would like to know how to handle exceptions (i.e. if I should wrap my code in a try-except block);
def move(file_path):
try:
# do something and return
except Exception as error:
# return error
If I should use the try-except, I would like to know how I should return from it. I have a background in functional programming, so I was thinking like this:
def move(file_path):
try:
# do something
return (True, something...)
except Exception as error:
return (False, error)
Other example:
def execute_query(database_cursor, query, fetch):
if type(database_cursor) != "":
return (1, "database_cursor isn't a valid database cursor")
cursor.execute(query)
if fetch == "*":
return self.cursor.fetchall()
yield self.cursor.fetchone()
In this case, I'm worried about the user sending input that is not a database.
Is there any convention for this functionality?
Thanks!
Update
How i'm doing:
from sys import exit
def testing_something(a, b, c):
try:
return 0, a + b + c
except Exception, error:
return 1, error
error, result = testing_something(1, 2, 3)
if error:
print error # raise error or treat.
sys.exit(error)
I think is very clever to do like this, now i can decide to raise it or to treat it.
In the past, I've used something like the following to ensure that a certain user input was "valid" in the sense that it was contained within a list or something. This was useful for validating manual file input for loading data into memory.
def validate_choice(selection, choices):
while selection not in choices:
selection = input("'%s' is not a valid entry. Try again: " % selection)
print("'%s' works! Returning..." % selection)
return selection
result1 = validate_choice('foo', ['foo', 'bar', 'baz'])
result2 = validate_choice('boogers', ['foo', 'bar', 'baz'])
If you're trying to coerce something of one type to another type, here's another example which coerces the user to enter either A) an integer or B) a number that can be coerced to an integer:
def str_to_int_validation(num):
parsed = False
while not parsed:
try:
num = input("Enter an integer: ")
num = int(num)
parsed = True
except ValueError:
print("'%s' is not an integer. Try again.")
return num
Hope that helps!
This you may consider Pythonic if you want, but it is nothing really but a hacky bit of code:
import os
import shutil
class MoveError(Exception): pass
def move (patha, pathb):
# Some checks
if not os.path.exists(patha):
return MoveError("'%s' does not exist!" % patha)
if os.path.exists(pathb):
return MoveError("'%s' already exists! I will not overwrite it!" % pathb)
print "Moving '%s' to '%s'..." % (patha, pathb)
try: shutil.move(patha, pathb)
except Exception, e:
return MoveError("Whoops, something nasty happened! Error is:\n%s" % str(e))
return "%i bytes moved" % os.path.getsize(pathb)
def help ():
print "This is some help!"
def quit ():
global running
print "Quitting!"
running = 0
commands = {"mv": move, "move": move, "help": help, "?": help, "q": quit, "quit": quit}
running = 1
while running:
inp = raw_input("--> ").split()
if not inp: continue
try: cmd = commands[inp[0]]
except:
print "Invalid command '%s'" % inp[0]
continue
result = cmd(*inp[1:])
if isinstance(result, Exception):
print "Error occurred!"
else: print "Done!"
if result is not None:
print result
Getting the command from commands dictionary could have also been:
cmd = commands.get(inp[0], None)
if not cmd: print "Command doesn't exist!"
or unefficient way:
if inp[0]not in commands:
print "No such command"
else: cmd = commands[inp[0]]
Now, we can start arguing over which of the three is more Pythonic. And that's just for this part of code.
But, it is dead true that returning exceptions, although it may be tempting is something to be done rarely. Usually only when you have to push something into some library's object to force it to catch the exception. (depends on design of the lib - and is very rarely needed). Your original design, starts well, with flag indicating error, but the second thing should be the error message then. Or go on like this, if you feel that you really need it for some reason. But you can always do:
def move (pa, pb):
raise MoveError, "I will not move anything!"
and then:
try:
commands["move"]("somefile", "somewhereelse") # To simulate call from user input
print "Done!"
except Exception, e:
print "An error occurred.\n%s" % str(e)
I made a simple script which finds the Square root of a number. The user inputs a number and it finds the square root and shows the result. I want it to check whether the input was a number or not. If it was a number it'll continue else it'll show a message, and reset.
I tried using:
while num != int(x):
print "That is not a valid number"
return self.page()
But that only shows an error.
Can someone help me out on this?
Here is the code:
import math
import sys
class SqRoot(object):
def __init__(self):
super(SqRoot, self).__init__()
self.page()
def page(self):
z = 'Enter a number to find its square root: '
num = int(raw_input(z))
sqroot = math.sqrt(num)
print 'The square root of \'%s\' is \'%s\'' % (num, sqroot)
choose = raw_input('To use again press Y, to quit Press N: ')
if choose == 'Y' or choose == 'y':
return self.page()
elif choose == 'N' or choose == 'n':
sys.exit(0)
print "SqRoot Finder v1.0"
print "Copyright(c) 2013 - Ahnaf Tahmid"
print "For non-commercial uses only."
print "--------------------------------"
def main():
app = SqRoot()
app()
if __name__ == '__main__':
main()
One of the python principles is EAFP:
Easier to ask for forgiveness than permission. This common Python coding style assumes the existence of valid keys or attributes and catches exceptions if the assumption proves false.
x = raw_input('Number?')
try:
x = float(x)
except ValueError:
print "This doesn't look like a number!"
If you don't want to use the ask forgiveness method, here is a simple function which should work on any valid float number.
def isnumber(s):
numberchars = ['0','1','2','3','4','5','6','7','8','9']
dotcount=0
for i in range(len(s)):
if (i==0 and s[i]=='-'):
pass
elif s[i]=='.' and dotcount==0:
dotcount+=1
elif s[i] not in numberchars:
return False
return True
Note: You can add base 16 easy by changing numberchars to:
numberchars = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']