本文翻译自:Asking the user for input until they give a valid response
I am writing a program that accepts an input from the user. 我正在编写一个接受用户输入的程序。
#note: Python 2.7 users should use `raw_input`, the equivalent of 3.X's `input`
age = int(input("Please enter your age: "))
if age >= 18:
print("You are able to vote in the United States!")
else:
print("You are not able to vote in the United States.")
The program works as expected as long as the the user enters meaningful data. 只要用户输入有意义的数据,该程序就会按预期运行。
C:\Python\Projects> canyouvote.py
Please enter your age: 23
You are able to vote in the United States!
But it fails if the user enters invalid data: 但是如果用户输入无效数据,它将失败:
C:\Python\Projects> canyouvote.py
Please enter your age: dickety six
Traceback (most recent call last):
File "canyouvote.py", line 1, in <module>
age = int(input("Please enter your age: "))
ValueError: invalid literal for int() with base 10: 'dickety six'
Instead of crashing, I would like the program to ask for the input again. 除了崩溃,我希望程序再次请求输入。 Like this: 像这样:
C:\Python\Projects> canyouvote.py
Please enter your age: dickety six
Sorry, I didn't understand that.
Please enter your age: 26
You are able to vote in the United States!
How can I make the program ask for valid inputs instead of crashing when non-sensical data is entered? 输入非意义的数据时,如何使程序要求有效的输入而不是崩溃?
How can I reject values like -1
, which is a valid int
, but nonsensical in this context? 如何拒绝像-1
这样的值,它是一个有效的int
,但在这种情况下毫无意义?
#1楼
参考:https://stackoom.com/question/1Zk0I/要求用户提供输入-直到他们给出有效的答复
#2楼
The simplest way to accomplish this would be to put the input
method in a while loop. 完成此操作的最简单方法是将input
方法放入while循环中。 Use continue
when you get bad input, and break
out of the loop when you're satisfied. 使用continue
当你错误的输入,并break
循环了,当你满足。
When Your Input Might Raise an Exception 当您的输入可能引发异常时
Use try
and except
to detect when the user enters data that can't be parsed. 使用try
和except
来检测用户何时输入了无法解析的数据。
while True:
try:
# Note: Python 2.x users should use raw_input, the equivalent of 3.x's input
age = int(input("Please enter your age: "))
except ValueError:
print("Sorry, I didn't understand that.")
#better try again... Return to the start of the loop
continue
else:
#age was successfully parsed!
#we're ready to exit the loop.
break
if age >= 18:
print("You are able to vote in the United States!")
else:
print("You are not able to vote in the United States.")
Implementing Your Own Validation Rules 实施您自己的验证规则
If you want to reject values that Python can successfully parse, you can add your own validation logic. 如果要拒绝Python可以成功解析的值,则可以添加自己的验证逻辑。
while True:
data = input("Please enter a loud message (must be all caps): ")
if not data.isupper():
print("Sorry, your response was not loud enough.")
continue
else:
#we're happy with the value given.
#we're ready to exit the loop.
break
while True:
data = input("Pick an answer from A to D:")
if data.lower() not in ('a', 'b', 'c', 'd'):
print("Not an appropriate choice.")
else:
break
Combining Exception Handling and Custom Validation 结合异常处理和自定义验证
Both of the above techniques can be combined into one loop. 以上两种技术都可以组合成一个循环。
while True:
try:
age = int(input("Please enter your age: "))
except ValueError:
print("Sorry, I didn't understand that.")
continue
if age < 0:
print("Sorry, your response must not be negative.")
continue
else:
#age was successfully parsed, and we're happy with its value.
#we're ready to exit the loop.
break
if age >= 18:
print("You are able to vote in the United States!")
else:
print("You are not able to vote in the United States.")
Encapsulating it All in a Function 将其全部封装在一个函数中
If you need to ask your user for a lot of different values, it might be useful to put this code in a function, so you don't have to retype it every time. 如果您需要询问用户许多不同的值,则将此代码放在函数中可能很有用,因此您不必每次都重新键入。
def get_non_negative_int(prompt):
while True:
try:
value = int(input(prompt))
except ValueError:
print("Sorry, I didn't understand that.")
continue
if value < 0:
print("Sorry, your response must not be negative.")
continue
else:
break
return value
age = get_non_negative_int("Please enter your age: ")
kids = get_non_negative_int("Please enter the number of children you have: ")
salary = get_non_negative_int("Please enter your yearly earnings, in dollars: ")
Putting It All Together 全部放在一起
You can extend this idea to make a very generic input function: 您可以扩展此思想,以创建非常通用的输入函数:
def sanitised_input(prompt, type_=None, min_=None, max_=None, range_=None):
if min_ is not None and max_ is not None and max_ < min_:
raise ValueError("min_ must be less than or equal to max_.")
while True:
ui = input(prompt)
if type_ is not None:
try:
ui = type_(ui)
except ValueError:
print("Input type must be {0}.".format(type_.__name__))
continue
if max_ is not None and ui > max_:
print("Input must be less than or equal to {0}.".format(max_))
elif min_ is not None and ui < min_:
print("Input must be greater than or equal to {0}.".format(min_))
elif range_ is not None and ui not in range_:
if isinstance(range_, range):
template = "Input must be between {0.start} and {0.stop}."
print(template.format(range_))
else:
template = "Input must be {0}."
if len(range_) == 1:
print(template.format(*range_))
else:
print(template.format(" or ".join((", ".join(map(str,
range_[:-1])),
str(range_[-1])))))
else:
return ui
With usage such as: 用法如下:
age = sanitised_input("Enter your age: ", int, 1, 101)
answer = sanitised_input("Enter your answer: ", str.lower, range_=('a', 'b', 'c', 'd'))
Common Pitfalls, and Why you Should Avoid Them 常见的陷阱以及为什么要避免它们
The Redundant Use of Redundant input
Statements 冗余使用input
语句
This method works but is generally considered poor style: 此方法有效,但通常被认为是较差的样式:
data = input("Please enter a loud message (must be all caps): ")
while not data.isupper():
print("Sorry, your response was not loud enough.")
data = input("Please enter a loud message (must be all caps): ")
It might look attractive initially because it's shorter than the while True
method, but it violates the Don't Repeat Yourself principle of software development. 最初它看起来很有吸引力,因为它比while True
方法短,但它违反了软件开发的“ 不要重复自己”的原理。 This increases the likelihood of bugs in your system. 这增加了系统中错误的可能性。 What if you want to backport to 2.7 by changing input
to raw_input
, but accidentally change only the first input
above? 如果你想向移植到2.7通过改变input
到raw_input
,却意外地只改变第一input
上面? It's a SyntaxError
just waiting to happen. 这是一个SyntaxError
正等待发生。
Recursion Will Blow Your Stack 递归会毁了你的栈
If you've just learned about recursion, you might be tempted to use it in get_non_negative_int
so you can dispose of the while loop. 如果您刚刚了解了递归,则可能会想在get_non_negative_int
使用它,以便可以处理while循环。
def get_non_negative_int(prompt):
try:
value = int(input(prompt))
except ValueError:
print("Sorry, I didn't understand that.")
return get_non_negative_int(prompt)
if value < 0:
print("Sorry, your response must not be negative.")
return get_non_negative_int(prompt)
else:
return value
This appears to work fine most of the time, but if the user enters invalid data enough times, the script will terminate with a RuntimeError: maximum recursion depth exceeded
. 这似乎在大多数情况下都可以正常工作,但是如果用户输入无效数据的次数足够多,该脚本将以RuntimeError: maximum recursion depth exceeded
终止RuntimeError: maximum recursion depth exceeded
。 You may think "no fool would make 1000 mistakes in a row", but you're underestimating the ingenuity of fools! 您可能会认为“没有傻瓜会连续犯1000个错误”,但是您却低估了傻瓜的创造力!
#3楼
Though the accepted answer is amazing. 尽管公认的答案是惊人的。 I would also like to share a quick hack for this problem. 我也想分享一个快速解决此问题的方法。 (This takes care of the negative age problem as well.) (这也解决了负面的年龄问题。)
f=lambda age: (age.isdigit() and ((int(age)>=18 and "Can vote" ) or "Cannot vote")) or \
f(input("invalid input. Try again\nPlease enter your age: "))
print(f(input("Please enter your age: ")))
PS This code is for python 3.x. PS此代码适用于python3.x。
#4楼
Why would you do a while True
and then break out of this loop while you can also just put your requirements in the while statement since all you want is to stop once you have the age? 您为什么要做while True
,然后中断这个循环,而您也可以只将需求放在while语句中,因为您想要的只是一旦年龄就停止了?
age = None
while age is None:
input_value = input("Please enter your age: ")
try:
# try and convert the string input to a number
age = int(input_value)
except ValueError:
# tell the user off
print("{input} is not a number, please enter a number only".format(input=input_value))
if age >= 18:
print("You are able to vote in the United States!")
else:
print("You are not able to vote in the United States.")
This would result in the following: 这将导致以下结果:
Please enter your age: *potato*
potato is not a number, please enter a number only
Please enter your age: *5*
You are not able to vote in the United States.
this will work since age will never have a value that will not make sense and the code follows the logic of your "business process" 这是可行的,因为年龄永远不会有没有意义的值,并且代码遵循“业务流程”的逻辑
#5楼
So, I was messing around with something similar to this recently, and I came up with the following solution, which uses a way of getting input that rejects junk, before it's even checked in any logical way. 因此,我最近在搞些类似的事情,于是我想到了以下解决方案,该解决方案使用了一种获取垃圾输入的方式,甚至可以以任何逻辑方式对其进行检查。
read_single_keypress()
courtesy https://stackoverflow.com/a/6599441/4532996 read_single_keypress()
由https://stackoverflow.com/a/6599441/4532996提供
def read_single_keypress() -> str:
"""Waits for a single keypress on stdin.
-- from :: https://stackoverflow.com/a/6599441/4532996
"""
import termios, fcntl, sys, os
fd = sys.stdin.fileno()
# save old state
flags_save = fcntl.fcntl(fd, fcntl.F_GETFL)
attrs_save = termios.tcgetattr(fd)
# make raw - the way to do this comes from the termios(3) man page.
attrs = list(attrs_save) # copy the stored version to update
# iflag
attrs[0] &= ~(termios.IGNBRK | termios.BRKINT | termios.PARMRK
| termios.ISTRIP | termios.INLCR | termios. IGNCR
| termios.ICRNL | termios.IXON )
# oflag
attrs[1] &= ~termios.OPOST
# cflag
attrs[2] &= ~(termios.CSIZE | termios. PARENB)
attrs[2] |= termios.CS8
# lflag
attrs[3] &= ~(termios.ECHONL | termios.ECHO | termios.ICANON
| termios.ISIG | termios.IEXTEN)
termios.tcsetattr(fd, termios.TCSANOW, attrs)
# turn off non-blocking
fcntl.fcntl(fd, fcntl.F_SETFL, flags_save & ~os.O_NONBLOCK)
# read a single keystroke
try:
ret = sys.stdin.read(1) # returns a single character
except KeyboardInterrupt:
ret = 0
finally:
# restore old state
termios.tcsetattr(fd, termios.TCSAFLUSH, attrs_save)
fcntl.fcntl(fd, fcntl.F_SETFL, flags_save)
return ret
def until_not_multi(chars) -> str:
"""read stdin until !(chars)"""
import sys
chars = list(chars)
y = ""
sys.stdout.flush()
while True:
i = read_single_keypress()
_ = sys.stdout.write(i)
sys.stdout.flush()
if i not in chars:
break
y += i
return y
def _can_you_vote() -> str:
"""a practical example:
test if a user can vote based purely on keypresses"""
print("can you vote? age : ", end="")
x = int("0" + until_not_multi("0123456789"))
if not x:
print("\nsorry, age can only consist of digits.")
return
print("your age is", x, "\nYou can vote!" if x >= 18 else "Sorry! you can't vote")
_can_you_vote()
You can find the complete module here . 您可以在此处找到完整的模块。
Example: 例:
$ ./input_constrain.py
can you vote? age : a
sorry, age can only consist of digits.
$ ./input_constrain.py
can you vote? age : 23<RETURN>
your age is 23
You can vote!
$ _
Note that the nature of this implementation is it closes stdin as soon as something that isn't a digit is read. 请注意,此实现的性质是,一旦读取到的不是数字,它就会关闭stdin。 I didn't hit enter after a
, but I needed to after the numbers. 我没有在后面a
回车,但我需要在数字后面。
You could merge this with the thismany()
function in the same module to only allow, say, three digits. 您可以将其与thismany()
函数合并到同一模块中,以仅允许输入三位数。
#6楼
While a try
/ except
block will work, a much faster and cleaner way to accomplish this task would be to use str.isdigit()
. 虽然try
/ except
块将起作用,但是完成此任务的更快,更干净的方法是使用str.isdigit()
。
while True:
age = input("Please enter your age: ")
if age.isdigit():
age = int(age)
break
else:
print("Invalid number '{age}'. Try again.".format(age=age))
if age >= 18:
print("You are able to vote in the United States!")
else:
print("You are not able to vote in the United States.")