010、布尔值判断的暗坑:truthy、falsy、短路逻辑与 None 的正确判法
010、布尔值判断的暗坑:truthy、falsy、短路逻辑与 None 的正确判法
上周帮同事排查一个线上bug,场景很简单:从Redis里取一个用户配置,如果取不到就返回默认值。他写的代码长这样:
ifnotredis.get('user_config'):returndefault_configreturnredis.get('user_config')看着没毛病对吧?结果线上炸了——用户配置明明存在,但值是0,结果not 0为True,直接返回了默认配置。这个bug让我想起自己刚入行时踩过的坑,今天就把这些暗坑一次性说清楚。
truthy和falsy:Python的“隐式布尔转换”
Python里每个对象都能被当作布尔值使用,这就是truthy和falsy的概念。falsy值包括:None、False、0、0.0、''(空字符串)、[](空列表)、{}(空字典)、set()(空集合)、range(0)。其他所有值都是truthy。
这里有个容易翻车的地方:0和False是falsy,但"False"和"0"是truthy——因为它们是非空字符串。别笑,我真见过有人写if "False":然后困惑为什么条件成立。
# 这里踩过坑:以为空列表是False,但判断逻辑写反了items=[]ifitems:# 正确写法,空列表为falsyprint("有数据")else:print("空列表")短路逻辑:and和or的“偷懒”机制
短路逻辑是Python优化布尔表达式的手段,但用不好就是坑。and遇到第一个falsy就停,or遇到第一个truthy就停。
# 别这样写:依赖短路逻辑做条件赋值,可读性极差result=aorborc# 返回第一个truthy值,如果全是falsy返回最后一个# 更清晰的写法result=aifaelsebifbelsec# 虽然也不推荐,但至少意图明确短路逻辑最常见的坑是:or返回的是第一个truthy值,不一定是布尔值。比如:
name=user_inputor"默认用户"# 如果user_input是空字符串,返回"默认用户"# 但如果user_input是0,也返回"默认用户"——这可能不是你要的None的正确判法:别用not,用is
回到开头的bug,正确的做法是显式判断None:
# 正确写法:用is None判断config=redis.get('user_config')ifconfigisNone:returndefault_configreturnconfig为什么不用not config?因为not会把所有falsy值都当成“不存在”,包括0、False、空字符串等。如果你明确要判断“值为None”,就用is None。
# 这里踩过坑:用not判断None,结果0被误判defget_user_score(user_id):score=cache.get(f"score:{user_id}")ifscoreisNone:# 正确:只判断Nonereturn0returnscore实战中的“三态”判断
实际开发中经常遇到“三态”场景:值存在且为真、值存在但为假、值不存在。这时候if value和if value is None都不够用。
# 别这样写:无法区分"值为False"和"值不存在"ifconfig:# 处理配置else:# 这里可能是config为False,也可能是config为None# 正确做法:先判断存在性,再判断值ifconfigisnotNone:ifconfig:# 配置为真else:# 配置为假(0、False、空字符串等)else:# 配置不存在函数返回值判断的陷阱
很多内置函数和库函数在“没找到”时返回None,但有些返回-1或空列表。比如str.find()找不到返回-1,而-1是truthy。
# 这里踩过坑:用if判断str.find()的结果text="hello world"iftext.find("python"):# 返回-1,-1是truthy,条件成立!print("找到了")# 实际上没找到# 正确写法iftext.find("python")!=-1:print("找到了")个人经验建议
判断None永远用
is:is None和is not None,别用==,更别用not。is比==快,而且语义更清晰。判断空容器用
if not:比如if not items:比if len(items) == 0:更Pythonic,前提是你确定items是列表且不会为None。避免链式
or赋值:a or b or c这种写法虽然简洁,但可读性差,而且容易误判falsy值。用三元表达式或显式if-else。写单元测试覆盖边界值:至少测试
None、0、False、空字符串、空列表这五种情况。我见过太多bug都是因为没测边界值。类型注解+静态检查:用
Optional[int]明确表示可能为None,配合mypy能在编译期发现很多问题。
最后说一句:Python的隐式布尔转换是双刃剑,用好了代码简洁,用不好就是线上事故。记住一个原则——显式优于隐式,当你不确定时,就写清楚判断条件。
