07-自定义类型的布尔值
约 2873 字大约 10 分钟
2026-09-28
在 Python 中,任何对象都可以被放进需要真假判断的位置:
if obj:
...
while obj:
...
not obj
bool(obj)内置容器遵循直观规则:空列表、空字符串和空字典为假,包含内容的对象为真。自定义类型也可以定义自己的真假语义。
本节要解释:
bool(obj)如何决定结果;__bool__和__len__谁优先;- 两者都没有时为什么对象默认为真;
__bool__和__len__的返回值要求;and、or为什么返回操作数而不一定返回布尔值;- 真假判断什么时候应该简单、快速且没有副作用。
1. 先看内置对象的规则
下面这些对象在布尔上下文中为假:
print(bool(False))
print(bool(None))
print(bool(0))
print(bool(""))
print(bool([]))
print(bool({}))输出为:
False
False
False
False
False
False相应地,非空容器和非零数值通常为真:
print(bool(1))
print(bool("ready"))
print(bool(["item"]))True
True
Truebool() 的作用不是把字符串解析成某种文本意义上的“真”或“假”。例如:
print(bool("False"))输出是:
True因为字符串非空,而不是因为它的内容代表真值。
2. 自定义 __bool__
一个订单对象可以根据自身状态定义真假:只有已经支付的订单为真。
class Order:
def __init__(self, order_id, paid):
self.order_id = order_id
self.paid = paid
def __bool__(self):
return self.paid
pending = Order("A-100", paid=False)
completed = Order("A-101", paid=True)
print(bool(pending))
print(bool(completed))
if completed:
print("order can be shipped")输出为:
False
True
order can be shipped调用链是:
bool(order)
-> Order.__bool__(order)
-> 返回 True 或 Falseif completed: 使用同一套协议,不需要手动写成 if bool(completed):。后者虽然结果相同,但显式调用没有增加信息,通常只保留在需要把结果保存或传递给其他函数时:
is_ready = bool(completed)__bool__ 必须返回真正的布尔值
下面的实现不符合协议:
class BrokenOrder:
def __bool__(self):
return 1 # 错误:返回了整数虽然整数 1 在普通布尔上下文中为真,但 bool(BrokenOrder()) 不会替你把返回值转换成 True,而会抛出 TypeError。
正确做法是显式转换:
class Order:
def __init__(self, items):
self.items = list(items)
def __bool__(self):
return bool(self.items)__bool__ 的返回值必须是 True 或 False,不能是任意“可以被转换成布尔值”的对象。
3. 没有 __bool__ 时回退到 __len__
如果类没有定义 __bool__,Python 会尝试使用 __len__:
class Batch:
def __init__(self, records):
self.records = list(records)
def __len__(self):
return len(self.records)
empty_batch = Batch([])
loaded_batch = Batch(["a", "b"])
print(bool(empty_batch))
print(bool(loaded_batch))输出为:
False
True调用顺序可以表示为:
bool(obj)
-> 如果有 __bool__,调用 __bool__
-> 否则如果有 __len__,调用 __len__
-> 长度为 0:False
-> 长度非 0:True这就是为什么一个只实现 __len__ 的容器自然地具有真假语义。空列表为假,并不是列表额外实现了一套与长度无关的规则,而是长度为 0 的回退结果。
__len__ 仍然必须返回合法长度
真假判断不会放宽 __len__ 的接口要求:
class InvalidBatch:
def __len__(self):
return -1
bool(InvalidBatch())会抛出 ValueError,因为长度不能为负数。返回字符串或浮点数则会抛出 TypeError。
如果类的主要语义不是容器,却希望拥有真假判断,不要为了复用回退机制而让 __len__ 返回一个与对象无关的数字。直接实现 __bool__ 更清晰。
4. __bool__ 优先于 __len__
一个类可以同时实现两个方法:
class Workspace:
def __init__(self, files, active):
self.files = list(files)
self.active = active
def __len__(self):
print("checking number of files")
return len(self.files)
def __bool__(self):
print("checking active state")
return self.active
workspace = Workspace([], active=True)
print(bool(workspace))输出为:
checking active state
True即使工作区没有任何文件,只要它处于 active 状态,bool(workspace) 仍然为真。因为 Python 优先选择 __bool__,不会再调用 __len__。
这意味着两个方法表达的概念可以不同:
__len__回答“有多少项”;__bool__回答“是否满足真假语义”。
如果这两个概念实际上相同,只实现 __len__ 往往更简单。如果它们不同,则应明确实现 __bool__,不要让调用者猜测长度是否代表有效性。
5. 两者都没有时默认为什么是真
普通自定义对象即使没有实现 __bool__ 或 __len__,也默认为真:
class Token:
pass
token = Token()
print(bool(token))True默认真值的含义是:对象存在本身不代表失败、空或无效。只有类型作者明确提供真假协议,Python 才会根据对象状态返回假。
这也解释了一个常见误解:
class Response:
def __init__(self, payload):
self.payload = payload
response = Response(None)
print(bool(response))输出仍然是:
True因为 Response 没有定义“payload 为空时对象为假”的规则。若这确实是业务语义,需要显式实现:
class Response:
def __init__(self, payload):
self.payload = payload
def __bool__(self):
return self.payload is not None不要把“某个属性为空”误认为“整个对象自动为空”。
6. if、while、assert 都使用真假协议
下面几种写法都会触发对象的真假判断:
if obj:
...
while obj:
...
assert obj例如,一个有待处理任务的队列可以这样写:
class TaskQueue:
def __init__(self, tasks):
self.tasks = list(tasks)
def __bool__(self):
return bool(self.tasks)
def pop(self):
return self.tasks.pop(0)
queue = TaskQueue(["compile", "test"])
while queue:
print(queue.pop())输出为:
compile
test每次进入 while 前,Python 都会重新调用 queue.__bool__()。当任务列表变空时,循环结束。
assert 也会在条件为假时抛出 AssertionError:
class Configuration:
def __init__(self, endpoint):
self.endpoint = endpoint
def __bool__(self):
return bool(self.endpoint)
config = Configuration("")
try:
assert config
except AssertionError:
print("configuration is empty")输出为:
configuration is emptyassert 适合检查开发阶段的不变量,不应作为生产输入验证的唯一机制,因为运行 Python 时可以通过优化选项关闭断言。
7. not 会把对象转换成布尔值
not obj 的结果始终是布尔值:
class User:
def __init__(self, enabled):
self.enabled = enabled
def __bool__(self):
return self.enabled
user = User(enabled=False)
print(not user)
print(type(not user))输出为:
True
<class 'bool'>调用过程是:
not obj
-> 先按照 bool(obj) 判断
-> 对结果取反
-> 返回 True 或 False与 and 和 or 不同,not 不会返回原始操作数。
8. and 与 or 返回操作数,而不只是布尔值
Python 的 and、or 具有短路行为,而且返回参与运算的对象:
print("left" and "right")
print("" and "right")
print("left" or "right")
print("" or "right")输出为:
right
left
right规则可以写成:
a and b:如果a为假,返回a;否则返回b;a or b:如果a为真,返回a;否则返回b。
对象版本同样如此:
class Marker:
def __init__(self, name, truth):
self.name = name
self.truth = truth
def __bool__(self):
print(f"checking {self.name}")
return self.truth
def __repr__(self):
return f"Marker({self.name!r})"
first = Marker("first", truth=False)
second = Marker("second", truth=True)
print(first and second)
print(first or second)输出为:
checking first
Marker('first')
checking first
Marker('second')first and second 没有检查 second,因为第一个操作数已经为假;它返回 first 本身。
first or second 发现第一个操作数为假,于是返回第二个对象;第二个对象是否为真不需要再次检查,因为 or 已经确定了要返回它。
因此下面这种常见写法会返回原始对象或默认值,而不一定返回布尔值:
name = user_name or "anonymous"如果只需要真假结果,应使用 bool(...) 或直接放进条件位置,不要误以为 and、or 会自动返回 True 或 False。
9. 真假判断应该表达什么
一个好的 __bool__ 应该回答一个稳定、直观的问题。例如:
- 容器是否有元素;
- 任务队列是否还有待处理任务;
- 向量是否为零向量;
- 配置对象是否已满足最小有效条件。
不适合把含义不明确的业务状态全部压缩成真假:
class Account:
def __bool__(self):
return self.balance > 0这样会把“账户存在”“账户已激活”“余额为正”等不同概念混成一个结果。调用者看到 if account: 时,无法知道究竟检查了哪一层含义。
更清晰的设计是提供有名字的查询方法:
class Account:
def __init__(self, balance, active):
self.balance = balance
self.active = active
def is_active(self):
return self.active
def has_positive_balance(self):
return self.balance > 0只有当某个真假含义足够自然、频繁且不会引发歧义时,才把它放进 __bool__。
10. 不要让 __bool__ 有明显副作用
真假判断可能在多个地方被隐式触发:if、while、and、or、not、断言和标准库函数都可能调用它。
因此,不建议在 __bool__ 中:
- 发起网络请求;
- 写入数据库;
- 修改对象状态;
- 打印大量日志;
- 执行不可预测的长时间计算。
错误示例:
class RemoteJob:
def __bool__(self):
return check_remote_service(self.id)调用 if job: 可能突然产生网络延迟和外部副作用,阅读代码的人很难预料。
如果状态查询确实昂贵,应使用命名方法明确表示成本:
if job.refresh_status_and_check():
...或者在对象中缓存状态,再让 __bool__ 只读取已经可用的数据。
11. 浮点数和特殊数值的真假
Python 中 0.0 为假,非零浮点数为真:
print(bool(0.0))
print(bool(float("nan")))
print(bool(float("inf")))输出为:
False
True
Truenan 虽然不等于自身,但它不是数值 0,所以布尔值为真。实现数值对象时,不能把“不是一个普通有限数”自动等同于假;真假语义应由对象领域定义。
12. 向量真假语义的完整示例
前一节的 Vector 使用长度判断真假。下面给出一个独立版本:
import math
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x!r}, {self.y!r})"
def __abs__(self):
return math.hypot(self.x, self.y)
def __bool__(self):
return bool(abs(self))
zero = Vector(0, 0)
nonzero = Vector(3, 4)
print(bool(zero))
print(bool(nonzero))
if nonzero:
print(f"length={abs(nonzero)}")输出为:
False
True
length=5.0这里 __bool__ 直接表达领域语义:零向量没有方向和长度,可以被视为假;其他向量为真。
如果将向量坐标扩展到高精度或符号数值,是否把“非常接近零”视为假就会变成更复杂的业务决定。不要在通用 __bool__ 中悄悄加入近似比较,除非对象的公开文档明确规定了这种语义。
13. 本节小结
Python 对象的真假判断遵循一条清晰的协议顺序:
- 有
__bool__时,优先调用它; - 没有
__bool__时,尝试__len__; __len__返回 0 时为假,非 0 时为真;- 两者都没有时,对象默认是真;
__bool__必须返回真正的bool;__len__仍必须遵守非负整数的长度协议;not返回布尔值;and和or短路并返回操作数本身;__bool__应该表达稳定、自然、低成本且没有明显副作用的语义。
下一节将从单个对象的真假协议扩展到容器协议,说明 Iterable、Sized、Container 和 Collection 等接口如何组织这些基础能力。
拓展阅读与查阅
以下资料用于进一步研究或日后查阅。理解本节内容不依赖这些链接。
