Python isdigit 返回 True,int 转换上标数字却报 ValueError
isdigit 判断 Unicode 数字属性,不能保证文本能解析为整数。按业务语法处理 int 异常或限制 ASCII 字符,并保留输入长度约束。
Python 中,"²".isdigit() 返回 True,接着调用 int("²") 却抛出 ValueError: invalid literal for int() with base 10。先判断 isdigit() 再转换,并不能保证这一步成功。
isdigit() 判断 Unicode 字符属性,包含上标数字等字符;int() 按整数文本语法解析。这里针对 Python 3 的字符串输入,后面的示例按 Python 3.6 或更新版本编写。
看清字符,而不是只看显示形状
在不含敏感数据的受控样本中,可以比较三种判断:
text = "²"
print(repr(text))
print([hex(ord(ch)) for ch in text])
print(text.isdecimal(), text.isdigit(), text.isnumeric())
预期三项判断分别是 False、True、True。isdecimal() 要求字符属于 Unicode 十进制数字类别;isdigit() 还包含其他数字字符;isnumeric() 范围更宽,例如还可能接受分数字符。把 isdigit() 换成 isnumeric() 会放宽检查,不能解决整数解析失败。
这不是把文本重新编码成 UTF-8 就能修好的问题。此时对象已经是 str,需要确定允许什么字符和语法。
接受 int 的语法,就直接处理解析结果
如果字段允许有符号十进制整数,可让解析器完成判断:
def parse_integer(text):
if not isinstance(text, str):
raise TypeError("expected text")
if len(text) > 128:
raise ValueError("input is too long")
try:
return int(text, 10)
except ValueError as error:
raise ValueError("expected a decimal integer") from error
这里的 128 是示例业务长度上限,需要按字段协议确定,不是 Python 的固定上限。函数失败时保留异常,不把非法输入静默改成零。
这条路径允许 int 支持的格式:例如首尾空白、正负号和 Unicode 十进制数字;Python 3.6 起还支持数字之间合法位置的单个下划线。因此," -12 " 可以被接受,"1_000" 可以表示 1000,但上标 "²" 仍应被拒绝。
如果业务只允许 ASCII 数字,这种解析范围就太宽,不能仅因 int() 成功就宣布协议校验通过。
协议只收 ASCII 无符号整数时,明确限制字符
def parse_ascii_unsigned(text):
if not isinstance(text, str):
raise TypeError("expected text")
if not 1 <= len(text) <= 128:
raise ValueError("invalid length")
if any(ch < "0" or ch > "9" for ch in text):
raise ValueError("expected ASCII digits")
return int(text, 10)
这个版本拒绝空白、正负号、下划线和非 ASCII 数字,接受前导零;这些都是明确的输入约定。若编号必须保留前导零,应保存字符串,不要为了校验就永久转换成整数。
单用 isdecimal() 也不等于 ASCII 检查,因为它允许其他文字系统的十进制数字。此外,字符检查不负责输入长度。CPython 的十进制整数文本转换存在可配置的长度限制,Python 3.11 文档列出了这项机制,部分旧版本也通过安全补丁加入了它;超长输入可能抛出 ValueError。不要为让输入通过而全局关闭限制。
用协议边界验收
在开发环境分别检查 "2"、"²"、"-2"、"1_000"、空字符串、非 ASCII 十进制数字和超出业务长度的文本。两套函数对后三类格式的接受范围不同,应选定一套约定后固定测试预期。
不要用 int(float(text)) 兜底,否则带小数的输入可能被截断,改变原有“只允许整数”的要求。上线前检查调用方是否依赖旧的字符范围;回退代码只能恢复旧校验,已经丢失的前导零或被错误转换的输入需要依靠原始数据恢复。


