命令行脚本、交互式工具、运维诊断程序——只要需要人在终端里敲点什么,键盘输入就是绕不开的环节。Python 提供了从最简单的 input() 到第三方校验库的一整套方案,但真实场景里坑不少:用户敲了空字符串、密码明文回显、类型转换直接炸掉。这篇文章把这些常见问题逐一拆开,给出可直接复用的代码片段。
input() 的基本用法与隐含陷阱
input(prompt) 会暂停程序、等待用户敲回车,返回值永远是 字符串:
name = input("请输入你的名字:")
print(f"你好,{name}")
最常见的错误发生在类型转换上。新手往往这样写:
age = int(input("请输入年龄:"))
用户敲了 abc 或者直接按回车,int() 立刻抛 ValueError。更稳妥的做法是把转换和校验包在一起,下面会展开讲。
另一个容易忽略的点:input() 在不同环境行为不同。在 IDE 的输出面板里,它可能不走标准输入;在管道调用时(echo 123 | python script.py),input() 会直接读到管道内容而不等待键盘。如果你的脚本既要交互又要支持管道,需要先检测 sys.stdin.isatty()。
把错误处理做成可复用的模式
与其在每一处 int(input(...)) 外面套 try/except,不如写一个通用转换函数:
def read_input(prompt, converter=str, retries=3, default=None):
"""读取键盘输入并安全转换类型,失败时重试或返回默认值"""
for attempt in range(retries):
raw = input(prompt)
try:
return converter(raw)
except (ValueError, TypeError) as e:
print(f" 输入无效({e}),请重新尝试")
if default is not None:
print(f" 已使用默认值:{default}")
return default
raise ValueError(f"超过 {retries} 次重试,放弃输入")
# 使用示例
age = read_input("请输入年龄(0-150):", converter=int, default=0)
print(f"年龄已记录为 {age}")
运行效果:
请输入年龄(0-150):abc
输入无效(invalid literal for int() with base 10: 'abc'),请重新尝试
请输入年龄(0-150):
输入无效(invalid literal for int() with base 10: ''),请重新尝试
请输入年龄(0-150):27
年龄已记录为 27
这个模式的好处:转换逻辑、重试次数、兜底默认值全部参数化,一处定义多处调用。
密码输入:别让终端回显出卖你
input() 会把用户敲的每一个字符显示在屏幕上。输入密码时这是不可接受的。标准库 getpass 专门解决这个问题:
import getpass
username = input("用户名:")
password = getpass.getpass("密码:") # 输入时屏幕不显示任何字符
print(f"登录凭据已获取(密码长度 {len(password)})")
getpass 在 Unix 终端下会直接关闭回显;在部分 IDE 或 Windows 旧版 cmd 里可能回退到 input(),此时会有警告。如果你的程序必须保证不回显,可以加一层检测:
import getpass, sys
if not sys.stdin.isatty():
# 管道模式下 getpass 无法隐藏输入,需要额外处理
sys.stderr.write("警告:非交互终端,密码输入可能被回显\n")
password = getpass.getpass("密码:")
实际项目中,密码拿到后应尽快用掉并从内存清除,不要长期存变量或写日志。
用 PyInputPlus 把校验逻辑从业务代码里剥离
手动写"必须是正整数""只能选 y/n""邮箱格式要对"这类校验,代码又长又重复。PyInputPlus 把常见校验封装成声明式参数,一行搞定:
pip install pyinputplus
import pyinputplus as pyip
# 限定范围的整数
age = pyip.inputInt("请输入年龄:", min=0, max=150)
# 只接受 yes/no
confirm = pyip.inputYesNo("确认删除?(yes/no):")
# 正则匹配邮箱
email = pyip.inputEmail("请输入邮箱:")
# 自定义选项菜单
role = pyip.inputMenu(["admin", "editor", "viewer"], numbered=True)
# 限定超时——5 秒不输入就走默认值
timeout_val = pyip.inputInt("端口(默认 8080):", default=8080, timeout=5)
print(f"\n年龄={age}, 确认={confirm}, 链箱={email}, 角色={role}, 端口={timeout_val}")
几个值得注意的参数:
blank=True允许空输入,blank=False(默认)拒绝空字符串。limit=3限制最多尝试次数,超限抛ValidationException。timeout=10设置总超时秒数,超时抛TimeoutException。applyFunc=str.upper对输入做后处理再返回。
如果你的校验规则不在内置列表里,可以传 validateFunc 自定义:
import pyinputplus as pyip
def is_chinese_name(value):
if not all('\u4e00' <= ch <= '\u9fff' for ch in value):
raise ValueError("请输入纯中文姓名")
return value
name = pyip.inputStr("姓名:", validateFunc=is_chinese_name)
实战组合:一个带校验的交互式部署脚本
把上面几个工具串起来,写一个真实可用的交互脚本:
#!/usr/bin/env python3
"""交互式部署配置收集脚本"""
import getpass
import pyinputplus as pyip
import sys
def collect_deploy_config():
print("=== 部署配置收集 ===\n")
env = pyip.inputMenu(
["production", "staging", "development"],
prompt="选择部署环境:\n",
numbered=True
)
port = pyip.inputInt("服务端口(1024-65535):", min=1024, max=65535, default=8000)
replicas = pyip.inputInt("副本数(1-20):", min=1, max=20, default=2)
db_host = pyip.inputStr("数据库主机(默认 localhost):", default="localhost", blank=True)
db_user = pyip.inputStr("数据库用户名:", blank=False)
db_pass = getpass.getpass("数据库密码:")
confirm = pyip.inputYesNo("\n确认以上配置?(yes/no):")
if not confirm:
print("已取消。")
sys.exit(0)
return {
"env": env,
"port": port,
"replicas": replicas,
"db_host": db_host,
"db_user": db_user,
# 注意:实际项目中不要把密码打印或写入无加密的文件
}
config = collect_deploy_config()
print(f"\n配置已确认:{config}")
选用建议与注意事项
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 简单一次性输入、类型可控 | input() + 手动转换 |
无额外依赖,够用 |
| 需要重试、默认值、类型安全 | 自定义 read_input 函数 |
轻量、可控、不引入第三方 |
| 密码 / token 输入 | getpass.getpass() |
标准库、关闭回显 |
| 多种校验规则、菜单选择、超时 | PyInputPlus |
声明式、代码量少、内置规则丰富 |
最后几个容易踩的坑列成清单,写脚本前过一遍:
- 永远记住
input()返回字符串——需要数字就显式转换,并包异常处理。 - 管道调用时
input()不等键盘——用sys.stdin.isatty()检测,或改用文件参数。 - 密码不要存变量太久——用完即清,不要写日志、不要序列化。
- PyInputPlus 的
limit和timeout会抛异常——在自动化脚本里要try/except兜住,否则进程直接退出。 - IDE 里
getpass可能回退到回显模式——生产环境优先在真实终端运行。
掌握这几层工具,从最简单的问答脚本到带校验、带密码的部署交互程序,都能写得干净且安全。