Python 里 True + True 等于几?为什么 bool 是 int 的子类?
简化版
**True + True == 2,因为 Python 里 bool 是 int 的子类——True 本质就是整数 1、False 就是整数 0,所以布尔值能直接参与算术运算。**关键事实:① isinstance(True, int) 是 True(bool 继承自 int);② True == 1 且 False == 0(值上完全相等),甚至 True == 1.0、hash(True) == hash(1);③ 因此 True + True + True == 3、sum([True, False, True]) == 2(统计满足条件的元素个数的常用技巧)、True * 5 == 5。实用价值:sum(x > 0 for x in nums) 能一行统计正数个数(每个布尔当 0/1 累加);["偶", "奇"][n % 2] 用布尔/整数当下标。但要注意的坑:① 因为 True == 1、False == 0,字典里 {True: "a", 1: "b"} 会合并成一个键(1 和 True 哈希相同、相等),最终只剩一项;② 判断「是否是布尔类型」要用 isinstance(x, bool),而且因为 bool 是 int 子类,isinstance(True, int) 也为真(要「只接受 int 不接受 bool」需额外排除)。核心记忆:bool 是 int 子类、True=1/False=0,能算术运算和当下标,但字典键会和 1/0 合并。
详细版
bool 与 int 的关系:
| 事实 | 结果 |
|---|---|
issubclass(bool, int) | True(bool 继承 int) |
isinstance(True, int) | True |
True == 1, False == 0 | True(值相等) |
True + True | 2 |
sum([True, True, False]) | 2 |
hash(True) == hash(1) | True(能合并字典键) |
print(True + True) # 2
print(True + 1) # 2
print(False - 1) # -1
print(True * 5) # 5
print(sum([True, False, True, True])) # 3(统计 True 个数)
print(isinstance(True, int)) # True(bool 是 int 子类)
print(issubclass(bool, int)) # True
# 实用:一行统计满足条件的元素个数
nums = [3, -1, 5, -2, 0]
print(sum(n > 0 for n in nums)) # 2(正数个数)
# 布尔当下标
n = 7
print(["偶", "奇"][n % 2]) # 奇
# 坑:字典键合并(True==1, hash 相同)
d = {True: "a", 1: "b", 1.0: "c"}
print(d) # {True: 'c'} —— 三个键合并成一个!
print(len(d)) # 1
# 判断是否布尔类型
print(type(True) is bool) # True
print(isinstance(True, bool)) # True
print(isinstance(1, bool)) # False(1 是 int 不是 bool)
⚠️ 理解这个知识点的关键是:Python 在设计布尔类型时,让
bool继承int(True/False就是特殊的1/0),这是历史原因——Python 早期没有独立的布尔类型,一直用1/0表示真假,后来(2.3)加入bool时为了向后兼容(大量老代码用1/0做真假),就让它做 int 的子类。这带来了「布尔能当数字用」的便利(统计计数、当下标),但也埋了两个坑:① 字典/集合里True和1、False和0会被当同一个键(因为它们==相等且hash相同),{1: "x", True: "y"}只剩一项(键保留先出现的、值被后者覆盖);② 类型检查要小心——isinstance(True, int)为真,如果一个函数「只想接受真正的整数、不接受布尔」(比如避免func(True)被当func(1)),需要额外and not isinstance(x, bool)。知道这个继承关系,很多「反直觉」现象就都通了。
完整版教学
一、bool 是 int 的子类
先确立核心事实:
核心事实:bool 继承自 int
class bool(int): ... (概念上)
issubclass(bool, int) → True
isinstance(True, int) → True
True 和 False 本质:
True 就是整数 1(特殊的、显示为 True 的 1)
False 就是整数 0(特殊的、显示为 False 的 0)
值上完全相等:
True == 1 → True
False == 0 → True
True == 1.0 → True(跨类型数值比较)
True is 1 → False(是同一个对象吗?不是,值相等≠同对象)
所以布尔能参与一切 int 能做的运算:
算术:True + True = 2、True * 3 = 3
比较:True > False(1 > 0)
当下标:lst[True] = lst[1]
位运算:True | False = 1
bool 覆盖了 int 的什么:
主要是 __repr__/__str__(显示成 True/False 而非 1/0)
以及 &、|、^(对两个 bool 返回 bool 而非 int)
True & False → False(bool),但 True & 2 → 0(int)
所以 bool 是 int 子类,True=1/False=0,能做一切 int 运算
核心事实:bool 继承自 int(issubclass(bool, int) 为 True、isinstance(True, int) 为 True)。True/False 本质:True 就是整数 1(显示为 True 的特殊 1)、False 就是整数 0。值上完全相等:True == 1、False == 0、True == 1.0(跨类型数值比较),但 True is 1 为 False(值相等≠同对象)。所以布尔能参与一切 int 运算:算术(True+True=2)、比较(True>False)、当下标(lst[True]=lst[1])、位运算。bool 覆盖了 int 的 __repr__/__str__(显示成 True/False)以及 & | ^(对两个 bool 返回 bool)。理解「bool 是 int 子类(issubclass 为真);True=1/False=0 值上完全相等(True==1、==1.0,但 is 1 为假);能做一切 int 运算(算术/比较/下标/位运算);bool 覆盖了显示和 &|^」,就确立了核心事实。
二、True + True == 2 的原理
理解算术运算为什么成立:
True + True == 2 的原理:
True 是 int 1 的实例(bool 继承 int)
+ 运算调用 int 的 __add__
→ 1 + 1 = 2(结果是 int 2,不是 bool)
一系列推论:
True + True = 2 (1+1)
True + False = 1 (1+0)
True * 3 = 3 (1*3)
False - 5 = -5 (0-5)
True ** 2 = 1 (1**2)
2 * True + 3 * False = 2 (2*1 + 3*0)
结果类型是 int(不是 bool):
type(True + True) → int
→ 算术运算"退化"成普通整数(因为调的是 int 的方法)
sum 统计的妙用:
sum([True, False, True, True])
= 求和 = 1+0+1+1 = 3
→ 统计"True 的个数"= 统计"满足条件的元素个数"
# 一行统计满足条件的元素数:
count = sum(1 for x in data if cond(x)) # 传统
count = sum(cond(x) for x in data) # 布尔当 0/1(更简洁)
所以 True+True=2 因 bool 继承 int,+调 int 的__add__得 1+1=2(结果是 int)
True + True == 2 的原理:True 是 int 1 的实例(bool 继承 int)、+ 调用 int 的 __add__ → 1+1=2(结果是 int 2 不是 bool)。一系列推论:True+False=1、True*3=3、False-5=-5、True**2=1。结果类型是 int(type(True+True) → int,算术运算「退化」成普通整数,因调的是 int 的方法)。sum 统计的妙用:sum([True,False,True,True]) = 1+0+1+1 = 3——统计「True 的个数」= 统计「满足条件的元素个数」,sum(cond(x) for x in data) 比 sum(1 for x in data if cond(x)) 更简洁。理解「True+True=2 因 bool 继承 int、+调 int 的__add__得 1+1=2(结果是 int 非 bool);sum([True,…])统计 True 个数=满足条件个数,sum(cond(x)for x in data)简洁」,就掌握了算术运算原理。
三、为什么这样设计——历史原因
理解 bool 继承 int 的历史缘由:
历史原因:
① Python 早期(<2.3)没有独立的布尔类型
用整数 1/0 表示真假,条件判断、逻辑运算都用 1/0
if x: ... 里 x 为真就是"非零"
② 大量老代码依赖 1/0 当真假:
flag = 1 # 表示 True
count += (a == b) # a==b 返回 1/0 用于计数
③ Python 2.3 引入 bool 类型(True/False)
为了"不破坏这些老代码",让 bool 做 int 的子类:
True 仍能当 1 用、False 仍能当 0 用
→ 向后兼容:老代码 count += (a==b) 照常工作
(a==b 现在返回 True/False,但等于 1/0)
设计权衡:
好处:向后兼容 + "布尔当数字"的便利(计数、下标)
代价:字典键合并、类型检查要小心(见后)
对比其他语言:
强类型语言(Java):boolean 和 int 完全独立,不能相加
Python:为了历史兼容和实用便利,选择 bool ⊂ int
设计哲学:"布尔就是特殊的整数"符合数学(真=1、假=0)
也符合"能用就用、别过度限制"的实用主义
所以 bool 继承 int 是历史原因(早期用 1/0、2.3 加 bool 时为兼容),兼顾实用
历史原因:① Python 早期(<2.3)没有独立布尔类型(用整数 1/0 表示真假、if x 里 x 为真就是「非零」);② 大量老代码依赖 1/0 当真假(flag = 1、count += (a == b) 用返回的 1/0 计数);③ Python 2.3 引入 bool 类型时为了「不破坏老代码」让 bool 做 int 的子类(True 仍能当 1、老代码 count += (a==b) 照常工作)。设计权衡:好处是向后兼容 + 「布尔当数字」的便利(计数、下标)、代价是字典键合并 + 类型检查要小心。对比:强类型语言(Java)boolean 和 int 完全独立、Python 为历史兼容和实用便利选择 bool ⊂ int。设计哲学:「布尔就是特殊的整数」符合数学(真=1、假=0)。理解「bool 继承 int 是历史原因:早期用 1/0、2.3 加 bool 时为兼容老代码(count+=(a==b))让 bool 做 int 子类;权衡兼容+便利 vs 字典键合并;Java 里 boolean 独立」,就理解了设计缘由。
四、实用价值——计数与下标
理解 bool 当数字的实用技巧:
实用技巧:
① 一行统计满足条件的元素个数(最常用):
sum(x > 0 for x in nums) # 正数个数
sum(c.isdigit() for c in s) # 数字字符个数
sum(1 for x in data if cond) # 传统写法(更啰嗦)
→ 布尔当 0/1 累加,简洁高效
② any / all 的关系:
any(iterable) ≈ sum(bool)>0(有真)
all(iterable) ≈ sum(bool)==len(全真)
(any/all 有短路,更好;但理解上相通)
③ 布尔当下标选值(小技巧,慎用可读性):
result = ["失败", "成功"][is_ok] # is_ok=True→"成功"
sign = [1, -1][x < 0] # x<0→-1 否则 1
⚠️ 可读性不如三元 "成功" if is_ok else "失败",看情况用
④ 矩阵/网格计数:
live = sum(sum(row) for row in grid) # grid 是 0/1 矩阵,数 1
⑤ 加权计数:
score = 3*passed + 1*attended # passed/attended 是布尔
对比 numpy:
np.array([True,False,True]).sum() → 2(同理,布尔当 0/1)
布尔数组做掩码计数是 numpy 核心用法
所以实用:sum(cond for x in data)一行计数、布尔当下标选值、加权计数
实用技巧:① 一行统计满足条件的元素个数(最常用):sum(x > 0 for x in nums)(正数个数)、sum(c.isdigit() for c in s)(数字字符个数)——布尔当 0/1 累加,比 sum(1 for x in data if cond) 简洁;② any/all 的关系:any ≈ sum(bool)>0、all ≈ sum(bool)==len(any/all 有短路更好);③ 布尔当下标选值(["失败","成功"][is_ok]、[1,-1][x<0],可读性不如三元、慎用);④ 矩阵/网格计数(sum(sum(row) for row in grid) 数 1);⑤ 加权计数(3*passed + 1*attended)。对比 numpy:布尔数组做掩码计数是核心用法。理解「实用:①sum(cond for x in data)一行统计满足条件个数(布尔当 0/1)②any/all 关系③布尔当下标选值(慎用可读性)④网格计数⑤加权计数;numpy 布尔掩码同理」,就掌握了实用价值。
五、两个坑——字典键合并与类型检查
理解 bool 是 int 子类带来的两个坑:
坑1:字典/集合里 True 和 1、False 和 0 合并
因为 True == 1 且 hash(True) == hash(1)
→ 字典视它们为"同一个键"
d = {True: "a", 1: "b"}
→ {True: "b"} (只剩一个键!)
键保留"先出现的"(True),值被"后出现的"(b)覆盖
d = {1: "x", True: "y", 1.0: "z"}
→ {1: "z"} (1、True、1.0 全合并,因值相等 hash 相同)
集合同理:
{True, 1, 1.0} → {True}(合并成一个元素)
{False, 0} → {False}
实战风险:
用布尔和整数混当字典键 → 意外合并、数据丢失
→ 别混用;需要区分就转成不同类型的键
坑2:类型检查——isinstance(True, int) 为真
isinstance(True, int) → True(bool 是 int 子类)
→ 想"只接受 int、拒绝 bool"时会误放行 True
def process(n):
if not isinstance(n, int): # True 也通过!
raise TypeError
...
process(True) # 被当 int 1 处理(可能不是你想要的)
精确判断:
只要 bool: isinstance(x, bool)
只要 int 非 bool: isinstance(x, int) and not isinstance(x, bool)
只要精确 int 类型: type(x) is int(不含子类)
所以两坑:①字典键 True/1、False/0 合并(hash 相等)②isinstance(True,int)为真(类型检查要排除 bool)
坑1:字典/集合里 True 和 1、False 和 0 合并——因 True == 1 且 hash(True) == hash(1)、字典视它们为「同一个键」:{True: "a", 1: "b"} → {True: "b"}(只剩一个键,键保留先出现的 True、值被后者 b 覆盖),{1:"x", True:"y", 1.0:"z"} → {1:"z"}(全合并),集合同理 {True,1,1.0} → {True}。实战风险:布尔和整数混当字典键会意外合并、数据丢失。坑2:类型检查——isinstance(True, int) 为真(bool 是 int 子类),想「只接受 int 拒绝 bool」时会误放行 True。精确判断:只要 bool 用 isinstance(x, bool)、只要 int 非 bool 用 isinstance(x, int) and not isinstance(x, bool)、只要精确 int 类型用 type(x) is int。理解「两坑:①字典/集合里 True/1、False/0 合并(hash 相等值相等,键保留先出现值被覆盖)②isinstance(True,int)为真(要排除 bool 用 and not isinstance(x,bool)或 type(x)is int)」,就掌握了两个坑。
六、总结与实践
总结 bool 与 int 的关系:
核心事实:
bool 是 int 的子类(历史原因:早期用 1/0、2.3 加 bool 时为兼容)
True = 1、False = 0(值上完全相等)
能做一切 int 运算(算术、比较、下标、位运算)
便利(用起来):
sum(cond for x in data) 一行统计满足条件个数
布尔当下标、加权计数、网格数 1
坑(要小心):
① 字典/集合键:True 和 1、False 和 0 合并(别混用)
② 类型检查:isinstance(True, int) 为真
只要 int 非 bool → and not isinstance(x, bool)
判断类型速查:
是布尔吗 isinstance(x, bool)
是 int(含 bool) isinstance(x, int)
是 int 非 bool isinstance(x,int) and not isinstance(x,bool)
精确是 int type(x) is int
其他数值类型的类似关系:
int ⊄ float(int 不是 float 子类,但 int 能自动转 float)
bool ⊂ int ⊂ (数值塔 Number)
→ 只有 bool⊂int 这一层是"子类"关系
核心总结:
True+True==2 因 bool⊂int(True=1)
便利:sum 计数、当下标
坑:字典键合并、类型检查要排除 bool
所以 bool⊂int(True=1/False=0),便利计数/下标,坑字典键合并+类型检查
核心事实:bool 是 int 的子类(历史原因)、True=1/False=0(值上完全相等)、能做一切 int 运算。便利:sum(cond for x in data) 一行统计、布尔当下标、加权计数、网格数 1。坑:① 字典/集合键 True 和 1、False 和 0 合并(别混用)、② 类型检查 isinstance(True, int) 为真(只要 int 非 bool 要 and not isinstance(x, bool))。判断类型速查:是布尔 isinstance(x, bool)、是 int 含 bool isinstance(x, int)、是 int 非 bool 加 and not isinstance(x, bool)、精确 int type(x) is int。理解「bool⊂int(True=1/False=0)能做 int 运算;便利 sum 计数/下标;坑字典键合并+类型检查要排除 bool;判断类型 isinstance(x,bool)/type(x)is int」,就掌握了总结与实践。
记忆钩子:「True+True==2 因为 bool 是 int 的子类(issubclass(bool,int)为真、isinstance(True,int)为真)——True 本质就是整数 1、False 就是 0,值上完全相等(True==1、==1.0,但 is 1 为假),所以能参与一切 int 运算(算术 True*3=3、比较 True>False、当下标 lst[True]、位运算);★这是历史原因:Python 早期(<2.3)没独立布尔类型、用 1/0 表真假,2.3 加 bool 时为兼容大量老代码(count+=(a==b))让 bool 做 int 子类;便利:sum(cond for x in data)一行统计满足条件的元素个数(布尔当 0/1 累加)、布尔当下标选值、加权计数;两个坑:①字典/集合里 True 和 1、False 和 0 会合并成一个键(因==相等且 hash 相同,键保留先出现值被后者覆盖,{True:‘a’,1:‘b’}只剩{True:‘b’})②类型检查 isinstance(True,int)为真,想只要 int 不要 bool 得 and not isinstance(x,bool)或 type(x)is int」。
七、常见误区与追问
- 误区:bool 和 int 是完全独立的类型。 不是——
bool是int的子类(issubclass(bool, int)为 True),True本质就是整数 1、False就是 0,值上完全相等(True == 1、False == 0、甚至True == 1.0),所以布尔能直接参与算术运算(True + True == 2)、比较、当下标;这和 Java 等强类型语言里 boolean 和 int 完全独立不同。 - 误区:True + True 会报类型错误。 不会——因为 bool 继承 int,
True + True等价于1 + 1 = 2(结果类型是 int 不是 bool);这个特性常被用来一行统计满足条件的元素个数:sum(x > 0 for x in nums)就是把每个布尔当 0/1 累加得到正数个数。 - 误区:字典里 True 和 1 是两个不同的键。 是同一个键——因为
True == 1且hash(True) == hash(1),字典和集合都把它们当同一个键,{True: "a", 1: "b"}最终只剩一项{True: "b"}(键保留先出现的 True、值被后出现的 b 覆盖);{1: "x", True: "y", 1.0: "z"}三个键全合并成一个;所以别在字典/集合里混用布尔和整数(0/1)当键,会意外合并、丢数据。 - 误区:用 isinstance(x, int) 就能确保 x 不是布尔。 不能——因为 bool 是 int 子类,
isinstance(True, int)返回 True,布尔值会通过isinstance(x, int)检查;如果你的函数「只想接受真正的整数、要拒绝布尔」(避免func(True)被悄悄当func(1)),需要额外判断:isinstance(x, int) and not isinstance(x, bool),或者用type(x) is int(精确类型、不含子类)。 - 追问:为什么 Python 要让 bool 继承 int(而不是做成独立类型)? 历史兼容原因:Python 早期(2.3 之前)根本没有独立的布尔类型,一直用整数 1 和 0 来表示真和假,大量代码依赖这个约定——比如
count += (a == b)用比较返回的 1/0 来计数、flag = 1表示开关;到 Python 2.3 引入True/False时,为了不破坏这些已有代码,就让bool做int的子类,使True仍然能当 1 用、False当 0 用,老代码照常工作;这个设计也顺带带来了「布尔能当数字用」的实用便利(计数、下标),符合「真=1、假=0」的数学直觉。 - 追问:sum([True, False, True]) 为什么等于 2,这有什么实用价值? 因为 bool 是 int 子类、True=1/False=0,sum 对它们求和就是
1 + 0 + 1 = 2,即统计了列表里 True 的个数;实用价值是「一行统计满足某条件的元素个数」:sum(x > 0 for x in nums)数正数、sum(c.isspace() for c in text)数空白字符、sum(sum(row) for row in grid)在 0/1 矩阵里数 1;这比sum(1 for x in nums if x > 0)或写显式循环更简洁,是 Python 里很常见的计数惯用法(numpy 里布尔数组.sum()做掩码计数也是同理)。 - 追问:怎么精确判断一个值「是布尔」还是「是整数但不是布尔」? ① 判断「是布尔」:
isinstance(x, bool)或type(x) is bool;② 判断「是整数(可以包含布尔)」:isinstance(x, int)(注意 True/False 也会返回 True,因为是子类);③ 判断「是整数但不是布尔」:isinstance(x, int) and not isinstance(x, bool),或者用type(x) is int(精确类型判断、不匹配子类,所以 True 会返回 False);实际写库/校验参数时,如果不希望布尔被当整数处理,就要用后两种方式显式排除 bool。
八、加强记忆
True + True == 2,因为 bool 是 int 的子类(issubclass(bool, int) 为 True、isinstance(True, int) 为 True)——True 本质就是整数 1、False 就是 0,值上完全相等(True == 1、True == 1.0,但 True is 1 为 False),所以能参与一切 int 运算(算术 True*3=3、比较 True>False、当下标 lst[True]、位运算)。这是历史原因:Python 早期(<2.3)没有独立布尔类型、用 1/0 表真假,2.3 引入 bool 时为兼容大量老代码(count += (a==b))让 bool 做 int 子类。便利:sum(cond for x in data) 一行统计满足条件的元素个数(布尔当 0/1 累加)、布尔当下标选值、加权计数。两个坑:① 字典/集合里 True 和 1、False 和 0 会合并成一个键(因 == 相等且 hash 相同,键保留先出现的、值被后者覆盖,{True:"a", 1:"b"} 只剩 {True:"b"});② 类型检查 isinstance(True, int) 为真,想「只要 int 不要 bool」得 isinstance(x, int) and not isinstance(x, bool) 或 type(x) is int。一句话「bool⊂int(True=1/False=0、值上完全相等),能做一切 int 运算,是历史兼容原因;便利 sum 一行计数(布尔当 0/1)/当下标;坑字典键 True 和 1 合并、类型检查 isinstance(True,int)为真要额外排除 bool」。