I'm learning how to use Python. I have a function with a conditional inside of it, if an invalid input is provided, it should restart the loop until a valid input is provided.
Unfortunately, this "restarting" behavior is causing an infinite loop within my tests (it circularly provides the wrong input). How can I pause, or break, or limit the output to one instance so I can test the returned string?
function:
def confirm_user_choice(choice: str):
while True:
user_response = input(f"\nYou chose '{choice}', is this correct? y/n ")
if user_response == "y":
return True
elif user_response == "n":
return False
else:
print("\nSelect either 'y' (yes) or 'n' (no)")
test:
import unittest
from unittest import mock
from src.utils.utils import addValues, confirm_user_choice
class TestConfirmUserChoice(unittest.TestCase):
def test_yes(self):
with mock.patch("builtins.input", return_value="y"):
result = confirm_user_choice("y")
self.assertEqual(result, True)
def test_no(self):
with mock.patch("builtins.input", return_value="n"):
result = confirm_user_choice("n")
self.assertEqual(result, False)
def test_invalid_input(self):
with mock.patch("builtins.input", return_value="apple"): <-- triggers func else case
result = confirm_user_choice("apple")
self.assertEqual(result, False)
You have a partial function: on a proper input, it will return a Boolean value, but it may not return at all, and you can't test that an infinite loop is indeed infinite.
To make it more testable, allow the function to take an optional iterable value that defaults to sys.stdin, allowing you to control what the function reads (and how long it will attempt to do so.)
def confirm_user_choice(choice: str, responses: Optional[Iterable[str]] = None):
if responses is None:
# An infinite stream of calls to input()
responses = iter(lambda: input(f"\nYou chose '{choice}', is this correct? y/n "), None)
for user_response in responses:
if user_response == "y":
return True
elif user_response == "n":
return False
else:
print("\nSelect either 'y' (yes) or 'n' (no)")
else:
# Note: cannot be raised from the default value of responses
raise ValueError("Unexpected end of responses")
Now your test can simply pass canned lists of responses, and either catch the expected ValueError, or look at the returned Boolean value.
import unittest
from src.utils.utils import addValues, confirm_user_choice
class TestConfirmUserChoice(unittest.TestCase):
def test_yes(self):
result = confirm_user_choice("y", ["y"])
self.assertTrue(result)
def test_eventual_yes(self):
result = confirm_user_choice("y", ["apple", "pear", "y"])
self.assertTrue(result)
def test_no(self):
result = confirm_user_choice("y", ["n"])
self.assertFalse(result)
def test_no_valid_input(self):
with self.assertRaises(ValueError):
result = confirm_user_choice(["apple"])
continue does nothing in your code
continue alows you to ignore a part of the code for some instance of the loop.
For example :
for i in range(2):
if i < 1:
continue
print(i)
Output :
1
For what you want to do, don't forget while is suppose to end when a condition is meet. Hence bypassing the condition using while True: and then using a if to exit your loop is a bit counter productive.
Just use the while condition :
user_response = ""
while user_response not in ["y", "n"]:
user_response = input("y/n ? ")
print(user_response)
Happy programming
I'm new to Python myself but in my understanding, unit tests investigate how function handle different inputs based on the function's return value or exceptions raised (if any).
Your function only exits when the user inputs either "y" or "n" or when an error is raised (for instance, if the user provides Crtl-Z). Your while loop does not break when a user inputs 'apple.' There is no return value for pytest (or the like) to inspect.
If you really want to test this, you'd have to rewrite your function so that's a little more modular. It would have to feature at least three different return values, including one that implies that the input was invalid.
Related
The program asked is:
"besides testing if the length of the given string is more than ten characters, it also tests if there is the character "X" (capital X) in the given string. If the string is longer than 10 characters and it has X in it, the tester subfunction returns a value True to the main function, otherwise False.
If the subfunction returns True to the main function, the program prints "X spotted!". As earlier, if the user inputs "quit", the program terminates."
This is what I tried, but the part of checking the x character does not work at all
def check(st,res="Too short"):
if len(st)>=10:
if checkX(st):
st=st+"\nX spotted!"
return st
else:
return res
def checkX(st):
for i in st:
if i=="X":
return True
return False
def main():
while True:
st=input("Write something (quit ends): ")
if st=="quit":
break
print(check(st))
It only checks if introduced string length is equal or higher than 10 characters.
The code works.
What should you change in your code:
You can use the in operator:
if "blah" not in somestring:
continue
Does Python have a string 'contains' substring method?
Do a real "main":
if __name__ == '__main__':
What you can do if you want to keep the main() function:
A module can discover whether or not it is running in the main scope by checking its own name, which allows a common idiom for conditionally executing code in a module when it is run as a script or with python -m but not when it is imported:
if __name__ == "__main__":
# execute only if run as a script
main()
main — Top-level script environment
Result (this code works, it is simplier, more pythonic and respect pep8):
def check_input(tested_sentence: str, result: str = "Too short"):
if len(tested_sentence) >= 10:
if "X" in tested_sentence:
result = tested_sentence + "\nX spotted!"
else:
result = tested_sentence
return result
def main():
while True:
sentence = input("Write something (quit ends): ")
if sentence == "quit":
break
print(check_input(sentence))
if __name__ == '__main__':
main()
Code Style — The Hitchhiker's Guide to Python
The aim of this code is to go through the users input check if check if any of the words match the words on the dictionary then give one response related to the first word that matches and if not reply with "I am curious tell me more". My problem is that I can't iterate over the list and print a single response.
def main():
bank = {"crashed":"Are the drivers up to date?","blue":"Ah, the blue screen of death. And then what happened?","hacked":"You should consider installing anti-virus software.","bluetooth":"Have you tried mouthwash?", "windows":"Ah, I think I see your problem. What version?","apple":"You do mean the computer kind?","spam":"You should see if your mail client can filter messages.","connection":"Contact Telkom."}
def welcome():
print('Welcome to the automated technical support system.')
print('Please describe your problem:')
def get_input():
return input().lower().split()
def mainly():
welcome()
query = get_input()
while (not query=='quit'):
for word in query:
pass
if word in bank:
print(bank[word])
elif not(word=="quit"):
print("Curious, tell me more.")
query = get_input()
mainly()
if __name__=="__main__":
main()
In your code there is few mistakes. First one, when you start the script you run main which loading a local disctionnary 'bank' which does't exist out of the function. When the function end, it runs 'mainly' but doesn't remember the dictionary.
Second one, as you use a dictionnary structure you don't need to loop thru and check all elements 1 by 1. You can instead use the function dict.get
I can propose you this solution :
def welcome():
print('Welcome to the automated technical support system.')
print('Please describe your problem:')
def get_input():
return input().lower().split()
def main():
bank = {"crashed": "Are the drivers up to date?", ...}
welcome()
query = get_input()
while query != 'quit':
if bank.get(query, None) is not None:
print(bank[query])
else:
print("doesn't exist")
query = get_input()
print("Curious, tell me more.") # will be triggered only when you are out of the loop
if __name__=="__main__":
main()
In that case bank.get(query, None) will return the sentence if the word exist, else it returns None.
You can also simplify it as :
while query != 'quit':
sentence = bank.get(query, "doesn't exist")
print(bank[query])
query = get_input()
this is because if it exist, sentence = what you want to display, if it doesn't, it displays the error message you want
I hope it helps,
I'm often tasked with asking users for input. I've always just written my prompts "as-needed" in my main execution scripts. This is kind of ugly, and because I often ask for the same types of input across multiple scripts, a ton of my code is just copy/pasted prompt loops. Here's what I've done in the past:
while True:
username = input("Enter New Username: ")
if ldap.search(username):
print " [!] Username already taken."
if not validator.validate_username(username):
print " [!] Invalid Username."
else:
break
I'd like to create something that can be called like:
username = prompt(prompt="Enter New Username: ",
default=None,
rules=["user_does_not_exist",
"valid_username"])
Then the prompt function looks like:
def prompt(prompt, default, rules):
while True:
retval = input(prompt)
if default and retval == "":
break
return default
if not rule_match(retval, rules):
continue
break
return retval
def rule_match(value, rules):
if "user_does_not_exist" in rules:
if not user.user_exists(value):
return False
if "valid_username" in rules:
if not validator.username(value):
return False
if "y_n_or_yes_no" in rules:
if "ignore_case" in rules:
if value.lower() not in ["y", "yes", "n", "no"]:
return False
else:
if value not in ["y", "yes", "n", "no"]:
return False
return True
An alternative I'm considering is to make a Prompt class, which would allow for more flexibility with the results. For example, if I want to convert the "y" or "n" to True or False, the above doesn't really work.
create_another = Prompt(prompt="Create another user? (y/n): ,"
default=False,
rules=["y_n_or_yes_no",
"ignore_case"]).prompt().convert_to_bool()
The other alternative I'm considering is just making individualized prompts and naming them, with each one written similar to my very original code. This doesn't actually change anything. It just serves to get these loops out of my main execution code which makes the main execution code easier to browse:
username = prompt("get_new_username")
def prompt(prompt_name):
if prompt_name == "get_new_username":
while True:
username = input("Enter New Username: ")
if ldap.search(username):
print " [!] Username already taken."
if not validator.validate_username(username):
print " [!] Invalid Username."
else:
break
return username
if prompt_name == "y_n_yes_no_ignore_case":
# do prompt
if prompt_name == "y_n_yes_no":
# do prompt
if prompt_name == "y_n":
# do prompt
if prompt_name == "y_n_ignore_case":
# do prompt
if prompt_name == "yes_no":
# do prompt
if prompt_name == "yes_no_ignore_case":
# do prompt
I realize that it's probably just a good idea to settle on one accepted "y/n" format for all of my programs, and I will. This is just for the sake of showing that, in cases where I would need a very similar but slightly different prompt, it would result in a lot of copy/pasted code (no flexibility with this method at all).
What is a good approach to writing clean, flexible, and easy-to-maintain user prompts?
(I've seen this: Asking the user for input until they give a valid response and some other responses. My question is not about how to get input and validate it, it's about how to make a flexible input system that can be reused with across multiple programs).
I once wrote a function for something similar. The explanation is in the doc-string:
def xory(question = "", setx = ["yes"], sety = ["no"], setz = [], strict = False):
"""xory([question][, setx][, sety][, setz][, strict]) -> string
Asks question. If the answer is equal to one of the elements in setx,
returns True. If the answer is equal to one of the elements in sety,
returns False. If the answer is equal to one of the elements in setz,
returns the element in setz that answer is equal to. If the answer is
not in any of the sets, reasks the question. Strict controls whether
the answer is case-sensitive. If show is True, an indication of the
acceptable answers will be displayed next to the prompt."""
if isinstance(setx, str):
setx = [setx]
if isinstance(sety, str):
sety = [sety]
if isinstance(setz, str):
setz = [setz]
if (setx[0])[0] != (sety[0])[0]:
setx = [(setx[0])[0]] + setx
sety = [(sety[0])[0]] + sety
question = question.strip(" ") + " "
while True:
if show:
shows = "[%s/%s] " % (setx[0], sety[0])
else:
shows = ""
user_input = raw_input(question + shows)
for y in [setx, sety, setz]:
for x in y:
if (user_input == x) or ((not strict) and (user_input.lower() == x.lower())):
if y is setx:
return True
elif y is sety:
return False
else:
return x
question = ""
show = True
Examples:
>>> response = xory("1 or 0?", ["1", "one", "uno"], ["0", "zero", "null"], ["quit", "exit"])
1 or 0? x
[1/0] eante
[1/0] uno
>>> print(response)
True
>>> response = xory("Is that so?")
Is that so? Who knows?
[y/n] no
>>> print(response)
False
>>> response = xory("Will you do it?", setz=["quit", "exit", "restart"])
Will you do it? hm
[y/n] quit
>>> print(response)
quit
I'd advise to write a library that contains a number of very clearly defined building blocks, each one as small and light-weight as possible, without too many assumptions baked in about how you're going to put the pieces together.
That is, I'd include one function that does the loop, but instead of passing in a set of rules, I'd allow for exactly one function to be passed in that either returns a value (if a valid input was given and after converting it in any way necessary) or raises a ValueError if the input wasn't usable. Other building blocks would implement certain checks or transformations (like resolution of 'y' and 'n' into boolean values).
This way, you would leave it completely up to the user to assemble the stuff in a way suitable for the use case.
# library:
def prompt(prompt, default, postprocess):
value = input('{} ({}): '.format(prompt, default)) or default
try:
return postprocess(value)
except ValueError:
continue
def check_lower(value):
if not value.islower():
raise ValueError()
def to_bool(value):
return value in 'yes'
# using the library:
def postprocess(value):
check_lower(value)
return to_bool(value)
prompt('Really?', 'n', postprocess)
I would create a prompt function as such:
def prompt(prompt, default=None, rules=[]):
while True:
response = input(prompt)
if response:
valid = [rule(response) for rule in rules]
if not(False in valid):
return response
else:
print('Invalid input')
else:
return default
You could then create different validation functions such as
def filterValidEmail(string):
if '#' in string:
if '.' in string.split('#')[1]:
return True
else:
return False
else:
return False
And call these functions like so:
prompt('What is your email? ', rules=[filterValidEmail])
You could also tweak this so that you can tell the user what verification they failed or disallow blank inputs.
On Code Academy there is this course where in the example they show
def speak(message):
return message
if happy():
speak("I'm happy!")
elif sad():
speak("I'm sad.")
else:
speak("I don't know what I'm feeling.")
The above example will NOT be related to the rest of the code I show. That was just an example for the if statement. Now I was under the impression that when ever writing an if statement it had to end in an ():like the above example.
However when doing the assignments this does not work:
def shut_down(s):
if s == "yes"():
return "Shutting down"
elif s == "no"():
return "Shutdown aborted"
else:
return "Sorry"
However this works:
def shut_down(s):
if s == "yes":
return "Shutting down"
elif s == "no":
return "Shutdown aborted"
else:
return "Sorry"
My question is how come the () is not needed next to the "yes" and "no" but :is still needed. I thought whenever writing an if statement it will automatically have to end with ():. In that very first example, that's how it is shown. Do you understand my confusion.
In the example given, happy() and sad() are functions, and as such require parentheses. The if itself does not need parentheses at the end (and it shouldn't have them)
No, if has nothing to do with ()
happy is a function. happy() is a call to that function. So, if happy(): tests if the happy function returns true when called.
In other words, if happy(): speak("I'm happy!") is equivalent to
result_of_happy = happy()
if result_of_happy:
speak("I'm happy!")
As has been mentioned happy() / sad() are functions so they require (). In example two of your question you are comparing your value to the string "yes" because it is a string it does not require ().
Within an if statement you can use parentheses to make the code more readable and ensure certain operations are evaluated before others.
if (1+1)*2 == 4:
print 'here'
else:
print 'there'
Differs from:
if 1+1*2 == 4:
print 'here'
else:
print 'there'
Because string objects are not callable so what are you expecting then:
Then use lambda not that efficient tho:
def shut_down(s):
if (lambda: s == "yes")():
return "Shutting down"
elif (lambda: s == "no")():
return "Shutdown aborted"
else:
return "Sorry"
I am a beginer python learner. I am trying to create a basic dictionary where random meaning of words will come and user have to input the correct word. I used the following method, but random doesn't work. I always get the first word first and when the last word finishes, I get infinite 'none' until I kill it. Using python 3.2
from random import choice
print("Welcome , let's get started")
input()
def word():
print('Humiliate')
a = input(':')
while a == 'abasement':
break
else:
word()
# --------------------------------------------------------- #
def word1():
print('Swelling')
a = input(':')
while a == 'billowing':
break
else:
word()
# ------------------------------------------------------------ #
wooo = [word(),word1()]
while 1==1:
print(choice(wooo))
is there any faster way of doing this and get real random? I tried classes but it seems harder than this. Also, is there any way I can make python not care about weather the input is capital letter or not?
To answer one part of your question ("is there any way I can make python not care about weather the input is capital letter or not?"): use some_string.lower():
>>> "foo".lower() == "foo"
True
>>> "FOO".lower() == "foo"
True
An this is to help you how you could improve the structure of your code:
import sys
from random import choice
WORDPAIRS = [('Humiliate', 'abasement'), ('Swelling', 'billowing')]
def ask():
pair = choice(WORDPAIRS)
while True:
answer = raw_input("%s: " % pair[0]).lower()
if answer == pair[1]:
print "well done!"
return
def main():
try:
while True:
ask()
except KeyboardInterrupt:
sys.exit(0)
if __name__ == "__main__":
main()
It works like that:
$ python lulu.py
Swelling: lol
Swelling: rofl
Swelling: billowing
well done!
Humiliate: rofl
Humiliate: Abasement
well done!
Swelling: BILLOWING
well done!
Humiliate: ^C
$
wooo = [word, word1]
while 1:
print(choice(wooo)())
But in any case it will print you None, cause both of your functions return nothing (None).