【python 让繁琐工作自动化】第8章 输入验证


Automate the Boring Stuff with Python: Practical Programming for Total Beginners (2nd Edition)
Copyright © 2020 by Al Sweigart.


输入验证(input validation)代码检查用户输入的值(如 input() 函数中的文本)是否正确格式化。例如,如果希望用户输入他们的年龄,那么代码不应该接受诸如负数(在可接受的整数范围之外)或单词(错误的数据类型)之类的无意义答案。
输入验证可以防止出错或安全漏洞。

通常情况下,我们通过反复请求用户输入直到他们输入有效文本,来执行输入验证,如下面的例子所示:

while True:
    print('Enter your age:')
    age = input()
    try:
        age = int(age)
    except:
        print('Please use numeric digits.')
        continue
    if age < 1:
        print('Please enter a positive number.')
        continue
    break

print(f'Your age is {age}.')

8.1 PyInputPlus 模块

PyInputPlus 包含与 input() 类似的函数,用于处理几种数据:数字、日期、电子邮件地址等。如果用户输入了无效的数据,比如格式糟糕的日期或超出预期范围的数字,PyInputPlus 将提示用户重新输入。
PyInputPlus 还有其他一些有用的特性,比如限制提示用户的次数,如果用户需要在一定的时间内响应,则设置超时。
PyInputPlus 不是 Python 标准库的一部分,因此必须使用 Pip 单独安装它。在命令行运行 pip install --user pyinputplus 安装此模块。要检查是否安装正确,请在交互式环境中导入它:

>>> import pyinputplus

PyInputPlus 有几个函数,针对不同类型的输入:

  • inputStr() 类似于内置的 input() 函数,但是具有一般的 PyInputPlus 特性。还可以向它传递自定义验证函数。
  • inputNum() 确保用户输入一个数字,返回一个整型数或浮点数,取决于该数字中是否有小数点。
  • inputChoice() 确保用户输入所提供的选择之一。
  • inputMenu() 类似于 inputChoice(),但它提供一个带有编号或字母选项的菜单。
  • inputDatetime() 确保用户输入日期和时间。
  • inputYesNo() 确保用户输入“yes”或“no”响应。
  • inputBool() 类似于 inputYesNo(),但是接受一个“True”或“False”响应,并返回一个布尔值。
  • inputEmail() 确保用户输入一个有效的电子邮件地址。
  • inputFilepath() 确保用户输入有效的文件路径和文件名,并且可以检查该名称的文件是否存在(可选)。
  • inputPassword() 类似于内置的 input(),但在用户键入时显示 * 字符,这样密码或其他敏感信息就不会显示在屏幕上。

只要输入无效的数据,这些函数会自动提示用户:

>>> import pyinputplus as pyip
>>> response = pyip.inputNum()
five
'five' is not a number.
42
>>> response
42

正如可以将字符串传递给 input() 以提供提示,也可以将字符串传递给 PyInputPlus 函数的 prompt 关键字参数以显示提示:

>>> response = input('Enter a number: ')
Enter a number: 42
>>> response
'42'
>>> import pyinputplus as pyip
>>> response = pyip.inputInt(prompt='Enter a number: ')
Enter a number: cat
'cat' is not an integer.
Enter a number: 42
>>> response
42

完整的文件可以查看 https://pyinputplus.readthedocs.io/

min、max、greaterThan 和 lessThan 关键字参数

接受整数和浮点数的 inputNum()inputInt()inputFloat() 函数有 minmaxgreaterThanlessThan 关键字参数,用于指定一个有效值范围。

>>> import pyinputplus as pyip
>>> response = pyip.inputNum('Enter num: ', min=4)
Enter num:3
Input must be at minimum 4.
Enter num:4
>>> response
4
>>> response = pyip.inputNum('Enter num: ', greaterThan=4)
Enter num: 4
Input must be greater than 4.
Enter num: 5
>>> response
5
>>> response = pyip.inputNum('Enter num: ', min=4, lessThan=6)
Enter num: 6
Input must be less than 6.
Enter num: 3
Input must be at minimum 4.
Enter num: 4
>>> response
4

这些关键字参数是可选的。如果传入了这些参数,输入应该大于等于 min 参数,小于等于 max 参数。同样,输入必须大于 greaterThan 参数,小于 lessThan 参数。

blank 关键字参数

默认情况下,空白输入是不允许的,除非 blank 关键字参数设置为 True

>>> import pyinputplus as pyip
>>> response = pyip.inputNum('Enter num: ')
Enter num:(blank input entered here)
Blank values are not allowed.
Enter num: 42
>>> response
42
>>> response = pyip.inputNum(blank=True)
(blank input entered here)
>>> response
''

如果想让输入成为可选的,这样用户就不需要输入任何内容,那么可以使用 blank=True

limit、timeout 和 default 关键字参数

默认情况下,PyInputPlus 函数将永远(或在程序运行时)继续询问用户有效的输入。如果希望某个函数在经过一定次数的尝试或一定的时间后停止要求用户输入,则可以使用 limittimeout 关键字参数。
limit 关键字参数传入一个整数,确定 PyInputPlus 函数可以在多少次内接收有效输入。
timeout 关键字参数传入一个整数,确定 PyInputPlus 函数必须在多少秒钟内,用户输入有效数据。

如果用户未能输入有效的数据,这些关键字参数将导致函数分别引发 RetryLimitExceptionTimeoutException

>>> import pyinputplus as pyip
>>> response = pyip.inputNum(limit=2)
blah
'blah' is not a number.
Enter num: number
'number' is not a number.
Traceback (most recent call last):
    --snip--
pyinputplus.RetryLimitException
>>> response = pyip.inputNum(timeout=10)
42 (entered after 10 seconds of waiting)
Traceback (most recent call last):
    --snip--
pyinputplus.TimeoutException

当使用这些关键字参数并传递 default 关键字参数时,函数将返回默认值,而不是引发异常。

>>> response = pyip.inputNum(limit=2, default='N/A')
hello
'hello' is not a number.
world
'world' is not a number.
>>> response
'N/A'
allowRegexes 和 blockRegexes 关键字参数

可以使用正则表达式,来指定输入是否允许。allowRegexesblockRegexes 关键字参数接受一个正则表达式字符串列表,来确定 PyInputPlus 函数将接受或拒绝哪些内容作为有效输入。

>>> import pyinputplus as pyip
>>> response = pyip.inputNum(allowRegexes=[r'(I|V|X|L|C|D|M)+', r'zero'])
XLII
>>> response
'XLII'
>>> response = pyip.inputNum(allowRegexes=[r'(i|v|x|l|c|d|m)+', r'zero'])
xlii
>>> response
'xlii'

上面的代码中,inputNum() 除了接受普通的数字外,还接受罗马数字。

使用 blockRegexes 关键字参数,指定一个正则表达式字符串列表,说明 PyInputPlus 函数不接受的内容。

>>> import pyinputplus as pyip
>>> response = pyip.inputNum(blockRegexes=[r'[02468]$'])
42
This response is invalid.
44
This response is invalid.
43
>>> response
43

上面的代码中,inputNum() 不接受偶数。

如果同时指定 allowRegexesblockRegexes 参数,则“允许列表”将覆盖“阻止列表”。

>>> import pyinputplus as pyip
>>> response = pyip.inputStr(allowRegexes=[r'caterpillar', 'category'],
blockRegexes=[r'cat'])
cat
This response is invalid.
catastrophe
This response is invalid.
category
>>> response
'category'

上面的代码中,允许使用“ caterpillar”和“ category”,但会阻止其它包含“cat”一词的任何其他内容。

查看 PyInputPlus 模块的完整文档可以访问:https://pyinputplus.readthedocs.io/

向 inputCustom() 传递一个自定义验证函数

可以通过编写一个函数,并将该函数传递给 inputCustom(),来执行自己的自定义验证逻辑。例如,假设希望用户输入一系列数字,这些数字的总和为 10。没有 pyinputplus.inputAddsUpToTen() 函数,但是可以创建自己的函数:
① 接受用户输入内容的单个字符串参数
② 如果字符串验证失败,则引发异常
③ 如果 inputCustom() 应该返回不变的字符串,则返回 None(或没有 return 语句)
④ 如果 inputCustom() 应该返回与用户输入不同的字符串,则返回非 None 值
⑤ 作为第一个参数传递给 inputCustom()

>>> import pyinputplus as pyip
>>> def addsUpToTen(numbers):
...   numbersList = list(numbers)
...   for i, digit in enumerate(numbersList):
...     numbersList[i] = int(digit)
...   if sum(numbersList) != 10:
...     raise Exception('The digits must add up to 10, not %s.' % (sum(numbersList)))
...   return int(numbers) # Return an int form of numbers.
...
>>> response = pyip.inputCustom(addsUpToTen) # No parentheses after
addsUpToTen here.
123
The digits must add up to 10, not 6.
1235
The digits must add up to 10, not 11.
1234
>>> response # inputStr() returned an int, not a string.
1234
>>> response = pyip.inputCustom(addsUpToTen)
hello
invalid literal for int() with base 10: 'h'
55
>>> response

上面代码中,函数调用是 inputCustom(addsUpToTen),而不是 inputCustom(addsUpToTen()),因为将 addsUpToTen() 函数本身传递给 inputCustom(),而不是调用 addsUpToTen(),传递它的返回值。

inputCustom() 函数也支持常规的 PyInputPlus 功能,比如 blank,limit,timeout,default,allowRegexes 和 blockRegexes 关键字参数。

8.2 项目:如何让傻瓜忙几个小时

使用 PyInputPlus 创建一个简单的程序,
① 询问用户,他们是否想知道如何让傻瓜忙几个小时。
② 如果用户回答不,退出。
③ 如果用户回答是,进入步骤 ①。
当运行程序时,结果像下面这样:

Want to know how to keep an idiot busy for hours?
sure
'sure' is not a valid yes/no response.
Want to know how to keep an idiot busy for hours?
yes
Want to know how to keep an idiot busy for hours?
y
Want to know how to keep an idiot busy for hours?
Yes
Want to know how to keep an idiot busy for hours?
YES
Want to know how to keep an idiot busy for hours?
YES!!!!!!
'YES!!!!!!' is not a valid yes/no response.
Want to know how to keep an idiot busy for hours?
TELL ME HOW TO KEEP AN IDIOT BUSY FOR HOURS.
'TELL ME HOW TO KEEP AN IDIOT BUSY FOR HOURS.' is not a valid yes/no response.
Want to know how to keep an idiot busy for hours?
no
Thank you. Have a nice day.
#! python3
# idiot.py - How to Keep an Idiot Busy for Hours

import pyinputplus as pyip
while True:
    prompt = 'Want to know how to keep an idiot busy for hours?\n'
    response = pyip.inputYesNo(prompt) # 保证只返回字符串 yes 或 no
    if response == 'no':
        break
print('Thank you. Have a nice day.')

还可以通过传递 yesValnoVal 关键字参数,使用非英语语言作为 inputYesNo() 函数提示。 例如,该程序的西班牙语版本将包含以下几行:

	prompt = '¿Quieres saber cómo mantener ocupado a un idiota durante horas?\n'
    response = pyip.inputYesNo(prompt, yesVal='sí', noVal='no')
    if response == 'sí':

现在,用户可以输入 sí 或 s(大小写都可以)来替代 yes 或 y,作为肯定的回答。

8.3 项目:乘法测验

PyInputPlus 的功能可用于创建定时乘法测验。 通过设置 pyip.inputStr()allowRegexesblockRegexestimeoutlimit 关键字参数,可以将大部分实现留给 PyInputPlus。需要编写的代码越少,编写程序的速度就越快。创建一个程序,向用户提出 10 个乘法问题,其中有效的输入是问题的正确答案。

# multiplicationQuiz.py - Creates a timed multiplication quiz.

import pyinputplus as pyip
import random, time

numberOfQuestions = 10
correctAnswers = 0
for questionNumber in range(numberOfQuestions):
	# Pick two random numbers:
    num1 = random.randint(0, 9)
    num2 = random.randint(0, 9)

    prompt = '#%s: %s x %s = ' % (questionNumber, num1, num2)
    try:
        # Right answers are handled by allowRegexes.
        # Wrong answers are handled by blockRegexes, with a custom message.
        pyip.inputStr(prompt, allowRegexes=['^%s$' % (num1 * num2)],
                              blockRegexes=[('.*', 'Incorrect!')],
                              timeout=8, limit=3)
	except pyip.TimeoutException:
        print('Out of time!')
    except pyip.RetryLimitException:
        print('Out of tries!')
	else:
        # This block runs if no exceptions were raised in the try block.
        print('Correct!')
        correctAnswers += 1
	time.sleep(1) # Brief pause to let user see the result.

print('Score: %s / %s' % (correctAnswers, numberOfQuestions))

8.4 实践项目

三明治制造机

写一个程序,询问他们对于三明治的偏好。程序应该使用 PyInputPlus,确保输入有效,例如:

  • 使用 inputMenu() 选择面包类型:全麦面包、白面包或酸面包。
  • 使用 inputMenu() 选择蛋白质类型:鸡肉、火鸡、火腿或豆腐。
  • 使用 inputYesNo() 询问他们是否想要奶酪。
  • 如果是,使用 inputMenu() 选择奶酪类型:切达奶酪、瑞士奶酪或马苏里拉奶酪。
  • 使用 inputYesNo() 询问他们是否想要蛋黄酱、芥末、生菜或番茄片。
  • 使用 inputInt() 询问他们多少个三明治。确保这个数字是 1 或更大。

提供这些选项的价格,并让程序在用户输入他们的选择后显示总成本。

#! python3
# sandwichMaker.py - Asks users for their sandwich preferences.

import pyinputplus as pyip

breadPrice = {'wheat':7, 'white':5, 'sourdough':8}
proteinPrice = {'chicken':4, 'turkey':5, 'ham':3, 'tofu':2}
cheesePrice = {'cheddar':3, 'Swiss':4, 'mozzarella':3}
otherPrice = {'mayo':1, 'mustard':1, 'lettuce':1, 'tomato':1}

paySum = 0

breadType = pyip.inputMenu(['wheat', 'white', 'sourdough'], numbered=True, prompt='Please select the bread type:')
paySum += breadPrice[breadType]

proteinType = pyip.inputMenu(['chicken', 'turkey', 'ham', 'tofu'], numbered=True, prompt='Please select the protein type:')
paySum += proteinPrice[proteinType]

cheeseRes = pyip.inputYesNo('Want cheese?')
if cheeseRes == 'yes':
	cheeseType = pyip.inputMenu(['cheddar', 'Swiss', 'mozzarella'], numbered=True, prompt='Please select the cheese type:')
	paySum += cheesePrice[cheeseType]

mayoRes = pyip.inputYesNo('Want mayo?')
if mayoRes == 'yes':
	paySum += otherPrice['mayo']

mustardRes = pyip.inputYesNo('Want mustard?')
if mustardRes == 'yes':
	paySum += otherPrice['mustard']

lettuceRes = pyip.inputYesNo('Want lettuce?')
if lettuceRes == 'yes':
	paySum += otherPrice['lettuce']

tomatoRes = pyip.inputYesNo('Want tomato?')
if tomatoRes == 'yes':
	paySum += otherPrice['tomato']

sandwichNum = pyip.inputInt('How many sandwiches you want? ', min=1)
paySum *= int(sandwichNum)

print('Please pay %s $!' % paySum)
编写自己的乘法测验

请尝试自己重新创建乘法测验项目,而不导入 PyInputPlus。这个程序向用户提出 10 个乘法问题,范围为0×0 到 9×9。需要实现以下功能:
① 如果用户输入正确的答案,程序显示“Correct!” 1 秒钟,然后继续下一个问题。
② 在程序继续下一个问题之前,用户有 3 次机会输入正确答案。
③ 在第一次显示问题 8 秒后,即使用户在 8 秒后输入正确答案,问题也会被标记为不正确。
将代码与“项目:乘法测试”中使用 PyInputPlus 的代码进行比较。

# multiplicationQuiz2.py - Creates a timed multiplication quiz.

import random, time

numberOfQuestions = 10
correctAnswers = 0
for questionNumber in range(numberOfQuestions):
	# Pick two random numbers:
	num1 = random.randint(0, 9)
	num2 = random.randint(0, 9)

	limits = 3
	timeout = 10
	startTime = time.time()
	while limits > 0:
		prompt = '#%s: %s x %s = ' % (questionNumber, num1, num2)
		try:
			answer = input(prompt)
			ans = int(answer)
		except:
			print('Please input an integer!')
		else:
			if startTime + timeout < time.time():
				print('Out of time!')
				break
			if ans == num1 * num2:
				correctAnswers += 1
				print('Correct!')
				break
			else:
				print('Incorrect!')
		limits -= 1
    
	if limits == 0:
		print('Out of tries!')
    
	time.sleep(1) # Brief pause to let user see the result.

print('Score: %s / %s' % (correctAnswers, numberOfQuestions))

学习网站:
https://automatetheboringstuff.com/2e/chapter8/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值