HOWTO · Python
在 Python 中将字符串转换为布尔值
在 Python 中使用 bool()、显式真/假规则、列表推导式、map()、strtobool() 和 json.loads() 将字符串转换为布尔值,并了解为什么 bool("False") 返回 True 以及 eval() 不安全。
本页内容
来自文件、配置、环境变量或用户输入的文本,会把布尔值写成 "true" 和 "false" 这样的字符串,而把这些文本转换回真正的 True 或 False,取决于你所处的情形。有时你只需要真值性(truthiness)——即是否存在任何文本。有时文本来自一组可以预先列出的固定标记。有时文本是带有自身严格写法的 JSON 字面量。选错转换方法是经典的 bug 来源,因为最顺手的工具 bool() 从不读取文本:bool("False") 返回 True。
本教程按这个顺序介绍各种转换方法:用于真值性的 bool()、用于已知标记的显式规则、用推导式或 map() 把同样的规则应用于列表、带有确切可用范围和返回值界定的旧有 strtobool()、用于 JSON 文本的 json.loads(),以及最后关于 eval() 的警告。
使用 bool() 将字符串转换为布尔值
bool() 函数将 Python 的真值性规则应用于字符串。每个非空字符串都是 True,空字符串是 False。字符串的内容不起任何作用:
print(bool("False"))
print(bool("True"))
print(bool(""))
print(bool("hello"))
输出:
True
True
False
True
只有当问题是“是否存在”时才使用 bool()——例如某个可选设置是否已提供。一旦文本本身承载含义,如上面的 "False",bool() 就会悄悄给出错误答案,此时你需要改用显式规则。
使用显式规则转换已知的文本值
当你知道输入可能包含哪些标记时,就把文本与恰好这些标记进行比较。先统一大小写并去除两端空白,对任何无法识别的内容抛出错误而不是猜测,这样配置文件中的拼写错误会立即暴露,而不是悄悄变成 False:
def parse_bool(value):
text = value.strip().lower()
if text in ("true", "yes", "on", "1"):
return True
if text in ("false", "no", "off", "0"):
return False
raise ValueError(f"unrecognized boolean string: {value!r}")
print(parse_bool("True"))
print(parse_bool(" off "))
print(parse_bool("YES"))
输出:
True
False
True
超出接受集合的输入会抛出错误,调用方可以捕获并报告它:
try:
parse_bool("maybe")
except ValueError as error:
print(error)
输出:
unrecognized boolean string: 'maybe'
按你的数据源调整这两个标记集合:CSV 导出可能只有 "true" 和 "false",而命令行标志还可能允许 "on" 和 "off"。集合是显式的,这正是这种方法比 bool() 更安全的原因。
使用列表推导式转换字符串列表
比较本身已经产生布尔值,因此列表推导式可以对每个元素应用比较来转换整个列表。这是本教程原始版本示例的直接延续,使用相同的数据:
words = ["False", "True", "False", "False", "True"]
flags = [word == "True" for word in words]
print(flags)
输出:
[False, True, False, False, True]
当列表可能包含多种标记写法时,复用上一节的显式转换函数,而不是单个比较:
flags = [parse_bool(word) for word in ["true", "False", "yes", "off"]]
print(flags)
输出:
[True, False, True, False]
使用 parse_bool 时,无法识别的元素会抛出 ValueError 并指出错误的值,而不是悄悄以 False 落入结果。
使用 map() 作为列表转换的替代方案
map() 将函数应用于可迭代对象的每个元素,因此当你传入一个单参数函数时,它执行相同的转换。使用 lambda 时,它与推导式互为镜像:
words = ["False", "True", "False", "False", "True"]
flags = list(map(lambda word: word == "True", words))
print(flags)
输出:
[False, True, False, False, True]
结果与列表推导式完全相同。当转换像这里一样内联书写时,优先使用推导式;当转换函数已经作为命名函数存在时,map() 更易读,例如 list(map(parse_bool, words)),它无需 lambda 即可把显式规则应用于每个元素。
将 strtobool() 作为旧有或依赖特定的选项处理
旧代码经常使用标准库 distutils.util 模块中的 strtobool()。它的界定是精确的,在依赖它之前很重要。该函数在 Python 3.11 及之前属于标准库;distutils 在 Python 3.10 中根据 PEP 632 被弃用,并在 Python 3.12 中被移除,因此在当前的解释器上,除非 setuptools 提供它自己的副本(见下文),否则导入会失败。Python 3.11 文档精确地定义了其行为:输入会被转为小写,真值为 'y'、'yes'、't'、'true'、'on' 和 '1',假值为 'n'、'no'、'f'、'false'、'off' 和 '0',任何其他值都会抛出 ValueError。标准库版本返回整数 1 和 0,而不是布尔值,因此当你需要真正的布尔值时,用 bool() 包裹结果:
from distutils.util import strtobool
print(strtobool("Yes"))
print(bool(strtobool("Yes")))
print(strtobool("off"))
输出:
1
True
0
该函数还以 vendored 副本的形式存留在 setuptools 中,许多项目已经依赖它。它接受相同的值并抛出相同的 ValueError(例如 invalid truth value 'abc'),但当前的 setuptools 版本直接返回 True 和 False——已使用 setuptools 84.2.1 验证:
from setuptools._distutils.util import strtobool
print(strtobool("Yes"))
输出:
True
由于返回类型、甚至模块的来源在标准库副本、setuptools 副本,以及 setuptools 在其存在时安装的 distutils 垫片之间各不相同,应将 strtobool() 视为旧有或依赖特定的选项:在有界的运行时上维护已经使用它的代码时可以接受,但对于新代码,上面的显式规则函数没有任何依赖,并且在每个 Python 版本上行为一致。
使用 json.loads() 解析 JSON 布尔字面量
当字符串来自 JSON 数据时,让 JSON 解析器来转换它。JSON 只用小写书写它的布尔字面量,而 json.loads() 会为它们返回真正的布尔值:
import json
print(json.loads("true"))
print(json.loads("false"))
输出:
True
False
Python 风格的大写写法不是有效的 JSON,因此对 "True" 的相同调用会失败而不是转换:
import json
json.loads("True")
输出:
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
请注意,json.loads() 解析任何 JSON 值,而不仅仅是布尔值;如果输入不可信,在将其用作布尔值之前,检查结果是否确实是布尔值。
为什么 eval() 对字符串转换不安全
eval() 看起来能转换字符串,因为它将字符串作为 Python 表达式求值,而 True 和 False 是有效的表达式:
print(eval("False"))
输出:
False
这正是问题所在:eval() 不是转换字符串,而是执行它。像 "__import__('os').getcwd()" 这样的值不会被拒绝——它会以你的程序的权限被执行,而恶意值可以执行任何代码。切勿将来自文件、网络或用户的文本传递给 eval()。如果你的字符串保证是 Python 字面量 "True" 和 "False",ast.literal_eval() 会在不执行任何内容的情况下解析它们,并拒绝表达式:
import ast
print(ast.literal_eval("True"))
print(ast.literal_eval("False"))
try:
ast.literal_eval("__import__('os').getcwd()")
except ValueError:
print("ValueError")
输出:
True
False
ValueError
在实践中,对标记文本使用显式规则,对 JSON 文本使用 json.loads(),仅对保证是 Python 字面量的文本使用 ast.literal_eval()——并将 eval() 完全排除在转换代码之外。