在 Python 里,None 承担了其他语言中 null 或 nil 的角色:它表示“没有值”,但并不等同于 0、False、空字符串或空容器。理解 None 的身份、比较方式和默认参数行为,是读懂 Python 代码与 traceback 的基础。
先做一个小测验
下面哪些表达式的结果为 True?
value = None
print(value is None)
print(value == None)
print(value is False)
print(value == 0)
print(type(value).__name__)
正确结果是:
True
True
False
False
NoneType
None 是一个单例对象,类型是 NoneType。因此,type(None) 的结果是 <class 'NoneType'>,而不是字符串或特殊的“空类型”。
判断 None 时使用 is None
推荐使用身份比较:
result = None
if result is None:
print("没有返回结果")
else:
print(f"结果是:{result}")
is 判断两个变量是否指向同一个对象,== 判断值是否相等。对于 None,Python 社区约定使用 is None 和 is not None。这样表达意图更清晰,也避免自定义对象重载 __eq__ 后产生意外行为。
不要把所有“假值”都当成 None:
values = [None, False, 0, "", [], {}]
for value in values:
print(repr(value), value is None, bool(value))
这里除了 None 以外,其他值也可能在条件判断中表现为假,但它们表达的含义不同。比如 0 是数值零,[] 是空列表,而不是“没有这个值”。
None 适合做默认参数吗?
当参数需要区分“调用者没有传值”和“调用者明确传入空容器”时,None 是很实用的默认值:
def collect_tags(tags=None):
if tags is None:
tags = []
tags.append("python")
return tags
print(collect_tags())
print(collect_tags([]))
输出:
['python']
['python']
不要直接把可变对象写成默认参数:
def unsafe_collect_tags(tags=[]):
tags.append("python")
return tags
print(unsafe_collect_tags())
print(unsafe_collect_tags())
两次调用可能得到同一个持续增长的列表,因为默认参数对象会在函数定义时创建,并在多次调用之间复用。使用 None 作为哨兵值,再在函数体中创建新列表,可以避免这种状态泄漏。
None 如何出现在 traceback 中
函数没有显式写 return 时,会隐式返回 None:
def find_user(user_id):
if user_id == 1:
return {"id": 1, "name": "Ada"}
# 没有匹配时隐式返回 None
user = find_user(2)
print(user) # None
如果后续代码把这个返回值当成字典使用,就可能出现类似错误:
user = find_user(2)
print(user["name"])
典型 traceback 会提示:TypeError: 'NoneType' object is not subscriptable。这句话的关键不是“NoneType 很特殊”,而是变量当前的值为 None,代码却尝试执行只有字典、列表等对象才支持的下标操作。
可以在使用前显式处理:
user = find_user(2)
if user is None:
print("用户不存在")
else:
print(user["name"])
实践检查清单
- 用
None表示缺失、尚未计算或没有结果。 - 判断空值时优先写
value is None或value is not None。 - 不要把
None、False、0、""和空容器混为一谈。 - 对列表、字典等可变默认参数使用
None,并在函数体内初始化。 - 看到
NoneTypetraceback 时,沿调用链检查哪个函数返回了None,以及调用者是否正确处理了它。
把 None 看成一个明确的状态,而不是一个可以随意替换所有空值的占位符,代码的接口和错误处理都会更可靠。