为什么不建议你用去 “! = null” 做判空?
x != null 这个判空写法,在 Python 里不只是别扭,很多时候还是错的。
刚接触 Python 的人,尤其是从 Java、C# 这类语言切过来的,特别容易手一抖写出这种代码:
if x != null: process(x)这段代码先别谈好不好,先说结果:直接报错。
NameError: name 'null' is not definedPython 里根本没有 null,对应的是 None。这事看着像语法问题,其实后面还有一层更容易踩坑的:Python 判空这件事,很多人不是写错了一个词,而是整个判断思路都带着别的语言的惯性。 参考风格见:
先把最容易写错的地方摆出来
很多人会这么改:
if x != None: process(x)这次倒是能跑了,但我平时不太建议这么写。
不是说它一定错,而是它在 Python 里不够地道,而且碰到一些“像空又不完全是空”的值时,代码很容易越写越歪。
举个很常见的清洗字段的脚本。
我以前处理导出数据时,经常会写这种逻辑:
row = { "user_id": 1001, "email": "", "age": 0, "remark": None }有的人为了过滤掉“没值”的字段,会这样写:
for k, v in row.items(): if v != None: print(k, v)输出是:
user_id 1001 email age 0这就有意思了。
如果你的目标只是过滤掉 None,这段没问题。 但如果你的目标其实是“过滤空值”,那空字符串 ""、数字 0、空列表 [] 都还在。
很多线上脚本最后写成一坨 != None、!= ''、!= [],其实不是 Python 难用,是一开始判空就没想明白:你到底是在判“是不是 None”,还是在判“有没有内容”。
这两个不是一回事。
Python 里别拿 null,要拿 None
先把基础结论说掉。
Python 只有 None,没有 null:
x = Noneif x is None: print("空值")
这里我一般直接用 is None,而不是 == None,更不会写 != null。
原因不复杂,None 在 Python 里是个单例对象,判它最稳的方式就是身份判断,不是值比较。
x = Noneprint(x == None) # True print(x is None) # True
这俩表面看起来一样,但真到自定义对象、重载比较运算的时候,差别就出来了。
为什么我不建议写 != None
因为 != 走的是值比较,背后会调对象自己的比较逻辑。这个地方有坑,而且坑不算小。
看个很短的例子:
class DirtyValue: defeq(self, other): return Truex = DirtyValue()print(x == None) # True print(x is None) # False
这代码在真实业务里不一定长这样,但意思已经够明显了: == None 和 != None 依赖对象怎么实现比较;is None 只看它到底是不是那个 None 对象。
你清洗接口返回值、处理 ORM 对象、接第三方库结果时,这种差异是可能真撞上的。
所以我自己写代码,一般会这么分:
如果我要判断“这个值是不是 None”,我写:
if x is not None: process(x)如果我要判断“这个值是不是空的、假的、没内容”,我写:
if x: process(x)这两句长得像,语义完全不一样。
is not None 和 if x: 不是一个意思
这个地方特别容易混。
比如接口字段清洗,经常会遇到这几种值:
values = [None, "", 0, False, [], {}, "ok", 12]你跑一下:
for v in values: print(v, v is not None, bool(v))大概就是这个效果:
None False False "" True False 0 True False False True False [] True False {} True False "ok" True True 12 True True差别就很明显了。
1)你要的是“字段存在,但允许 0”
比如订单金额、库存、重试次数这种字段,0 是有效值,不是空值。
那你不能写:
if retry_count: save(retry_count)因为 0 会被当成 False 干掉。
这时候应该写:
if retry_count is not None: save(retry_count) 2)你要的是“字符串非空”
那 None 和 "" 都不想要。
这时候写:
if name: save(name)就比 name != None 靠谱得多。 因为你本来就不只是想排除 None,你是想排除“没内容”。
我见过最常见的一种脏写法
很多脚本到后面会长成这样:
if val != None and val != '' and val != [] and val != {}: handle(val)这类代码我一般看见就想改。
不是它不能跑,是它越补越没头。
今天加个 '',明天补个 [],后天再补个 'null'、'None'、'N/A'。最后你会发现,自己不是在判空,是在跟脏数据互殴。
这种时候,最好先把规则掰直:
只是判断是不是 None if val is not None: handle(val) 判断是不是“有内容的字符串” if isinstance(val, str) and val.strip(): handle(val) 判断容器里是不是有元素 if val: handle(val) 字段清洗时统一归一
这个我平时更常用一点,先收口,再用。
def normalize(v): if v is None: return None if isinstance(v, str): v = v.strip() return v or None return vcleaned = {k: normalize(v) for k, v in payload.items()}
后面再判断就简单很多:
if cleaned["nickname"] is not None: ... 再说回 null 这件事,问题不只是写错单词
很多人是从 JSON 里把习惯带进来的。
JSON 长这样:
{ "name": null }然后脑子里就默认代码里也该写 null。
但 JSON 是 JSON,Python 是 Python。 JSON 反序列化到 Python 之后,那个 null 会变成 None:
import jsondata = json.loads('{"name": null}') print(data)# {'name': None} print(data["name"] is None) # True
所以你在处理接口返回、消息队列消息、配置文件的时候,别拿 JSON 的字面量习惯直接往 Python 代码里套。
这个坑不大,但特别烦,尤其是在临时脚本、日志分析脚本里,复制一段示例数据过来,人顺手就写成了 null。
实战里怎么写,我一般这么分 场景一:接口字段,只想排除 None timeout = config.get("timeout")
if timeout is not None: client.timeout = timeout这里不能写 if timeout:。 因为 timeout = 0 可能是明确配置。
场景二:清洗用户输入,空串也不要 nickname = form.get("nickname")
if nickname and nickname.strip(): user.nickname = nickname.strip()这里用 is not None 不够,因为 " " 这种值你大概率也不想要。
场景三:批量导入,只保留真正有值的字段 def has_value(v): if v is None:
return False if isinstance(v, str) and not v.strip(): return False return Truerow = { "name": "东哥", "mobile": "", "age": 0, "remark": None }clean_row = {k: v for k, v in row.items() if has_value(v)} print(clean_row)
输出:
{'name': '东哥', 'age': 0}这种写法就比到处散着 != None 顺手得多。
x != null 在 Python 里,第一层就是错,null 根本不存在。 真改成 x != None 之后,也不算好写法,因为它把“判 None”和“判空”这两件事混在一起了,而且还依赖对象的比较行为。
这类判断我平时基本只留两种:
if x is not None:和
if x:前者判断“是不是 None”,后者判断“有没有内容”。
别混着用。这个地方一混,后面处理 0、空串、空列表、False 的时候,代码就开始拧巴了。 Python 判空本来不复杂,复杂的是拿别的语言习惯硬往里塞。