← 返回题目列表

Python 的 and / or 返回的是什么?为什么 a or b 能做默认值?

高频 中等 第 8 / 22 题 更新于 2026/07/31
Python短路求值andor

简化版

Python 的 andor 不返回 True/False,而是返回「参与运算的某个操作数本身」(原对象),并且是「短路求值」——只算到能确定结果为止。具体规则:① a and b——如果 a 为假,直接返回 a(短路,不算 b);否则返回 b。即「返回第一个假值,全真则返回最后一个」。② a or b——如果 a 为真,直接返回 a(短路,不算 b);否则返回 b。即「返回第一个真值,全假则返回最后一个」。为什么 a or b 能做默认值x = name or "匿名"——如果 name 是真值(非空字符串)就用 name,否则(空字符串/None)用「匿名」。但有坑:这种「真假」判断用的是对象的真值测试bool()),0""[]None 都是假值——所以 count or 10count=0(合法值)时会错误地变成 10。要点:想「仅在 None 时用默认」应该用 x if x is not None else defaultx = a if a is not None else b,别用 or。核心记忆:and/or 返回操作数本身+短路;a or b 做默认值方便但会把 0/""/[] 当假,精确判空要用 is not None

详细版

and / or 的返回规则

表达式规则返回
a and ba 假→返回 a(短路);a 真→返回 b第一个假值,或最后一个
a or ba 真→返回 a(短路);a 假→返回 b第一个真值,或最后一个
not a逻辑取反永远返回 True/False
# 返回操作数本身,不是布尔
print(3 and 5)        # 5(3 真,返回 b)
print(0 and 5)        # 0(0 假,短路返回 a)
print(3 or 5)         # 3(3 真,短路返回 a)
print(0 or 5)         # 5(0 假,返回 b)
print("" or "默认")   # 默认("" 假,返回 b)
print([] or [1])      # [1]

# 短路:右边有副作用时不执行
def side(): print("called"); return True
False and side()      # 短路,不打印 called
True or side()        # 短路,不打印 called

# or 做默认值(常见但有坑)
name = ""
print(name or "匿名")  # 匿名("" 是假值)
count = 0
print(count or 10)     # 10 ✗ —— count=0 是合法值却被当假!

# 精确判空:用 is not None
val = 0
print(val if val is not None else 10)   # 0 ✓(正确保留 0)

⚠️ and/or 返回「操作数本身」而不是布尔值,这是 Python 一个既强大又容易踩坑的设计——a or b 当默认值很方便,但它判断的是「真值」(bool(a))而不是「是否为 None」,于是 0""[]0.0 这些「合法但为假」的值会被误当成「没值」而替换掉。经典事故:port = config.get("port") or 8080,本意是「配置没设端口就用 8080」,但如果用户就想用端口 0(虽然罕见)或者更常见的 timeout = user_timeout or 30 里用户想要 timeout=0(不超时),or 会把 0 当假、错误地替换成默认值。正确姿势:区分「假值」和「None」——要「仅当值缺失(None)时用默认」,用 x if x is not None else default,或 dict.get(key, default)(键不存在才用默认,键存在即使值是 0 也保留)。or 只适合「假值都该被替换」的场景(如空字符串统一显示「匿名」)。

完整版教学

一、and / or 返回操作数本身

先颠覆一个直觉——它们不返回布尔:

很多语言里 and/or 返回 true/false
Python 里 and/or 返回"参与运算的操作数本身"(原对象)

a and b 的规则:
  求值 a:
    a 为假 → 返回 a(不算 b,短路)
    a 为真 → 返回 b(a 的真假已定,结果取决于 b)
  → 口诀:"返回第一个假值,全真则返回最后一个"

a or b 的规则:
  求值 a:
    a 为真 → 返回 a(不算 b,短路)
    a 为假 → 返回 b
  → 口诀:"返回第一个真值,全假则返回最后一个"

例子:
  3 and 5  → 3 真 → 返回 5
  0 and 5  → 0 假 → 返回 0(短路)
  3 or 5   → 3 真 → 返回 3(短路)
  0 or 5   → 0 假 → 返回 5
  "" or 0 or "x" → ""假→0 假→返回"x"(第一个真值)
  1 and 2 and 3  → 全真 → 返回 3(最后一个)

not 例外:
  not a 永远返回布尔 True/False(不是操作数)

所以 and/or 返回操作数本身:and 返回第一个假值(全真取最后)、or 返回第一个真值(全假取最后)

颠覆直觉:很多语言 and/or 返回 true/false,Python 里返回「参与运算的操作数本身」(原对象)a and b 规则:a 假→返回 a(短路)、a 真→返回 b,口诀「返回第一个假值,全真则返回最后一个」。a or b 规则:a 真→返回 a(短路)、a 假→返回 b,口诀「返回第一个真值,全假则返回最后一个」。例子:3 and 5→5、0 and 5→0、3 or 5→3、0 or 5→5、"" or 0 or "x"→“x”。not 例外not a 永远返回布尔。理解「and/or 返回操作数本身不是布尔;and 返回第一个假值(全真取最后)、or 返回第一个真值(全假取最后);not 永远返回布尔」,就理解了返回值规则。

二、短路求值

理解短路求值机制:

短路求值(short-circuit evaluation):
  只求值到"能确定结果"为止,右边可能根本不算

a and b:
  a 为假 → 结果必假(不管 b)→ 不算 b,直接返回 a
  a 为真 → 结果取决于 b → 才算 b

a or b:
  a 为真 → 结果必真(不管 b)→ 不算 b,直接返回 a
  a 为假 → 结果取决于 b → 才算 b

验证短路(右边有副作用):
  def f(): print("f"); return True
  False and f()   → 不打印 f(and 左假短路)
  True or f()     → 不打印 f(or 左真短路)
  True and f()    → 打印 f(要算右边)
  False or f()    → 打印 f(要算右边)

短路的实用价值:
  ① 性能:跳过不必要的计算
  ② 安全防护("守卫"):
     x is not None and x.method()   # x 是 None 就短路,不调 method
     len(s) > 0 and s[0] == 'a'      # 空串就短路,不越界
     obj and obj.attr                # obj 假就短路
  ③ 条件初始化:cache = cache or compute()(cache 空才算)

所以短路:只算到能定结果为止,右边可能不算;常用于守卫(先判空再访问)

短路求值:只求值到「能确定结果」为止、右边可能根本不算。a and b:a 假→结果必假→不算 b 返回 a;a 真→才算 b。a or b:a 真→结果必真→不算 b 返回 a;a 假→才算 b。验证(右边有副作用):False and f() 不打印 f、True or f() 不打印 f。短路的实用价值① 性能(跳过不必要计算)、② 安全防护「守卫」(x is not None and x.method() 是 None 就短路不调方法、len(s)>0 and s[0]=='a' 空串就短路不越界)、③ 条件初始化(cache = cache or compute()。理解「短路只算到能定结果为止右边可能不算;and 左假短路、or 左真短路;常用守卫(先判空再访问 x is not None and x.f())+性能+条件初始化」,就掌握了短路求值。

三、真值测试——谁是假值

理解 and/or 判断真假的依据:

and/or 判断"真假"用的是对象的"真值测试"(bool()):
  Python 里每个对象都有真假,由 bool(obj) 决定

假值(falsy)——被当作 False:
  None
  False
  0、0.0、0j(各种数字零)
  ""(空字符串)
  []、()、{}、set()(空容器)
  自定义对象:__bool__ 返回 False,或 __len__ 返回 0

真值(truthy)——被当作 True:
  其他一切:非零数字、非空字符串、非空容器、大多数对象

例子(这些都是假值,会被 or 替换):
  0 or "x"       → "x"(0 是假)
  "" or "x"      → "x"(空串假)
  [] or [1]      → [1](空列表假)
  0.0 or 9       → 9(0.0 假)
  None or "d"    → "d"(None 假)

关键陷阱(区分"假值" vs "None"):
  0、""、[] 是"合法的值"但恰好为假
  → or 会把它们当"没值"替换掉(可能是 bug)
  只有 None 通常才真正表示"没值/缺失"

自定义对象的真值:
  class Box:
      def __bool__(self): return self.count > 0
  → Box() 在 and/or 里按 __bool__ 判真假

所以真值测试:None/0/""/空容器都是假值;or 把它们当没值替换是常见陷阱

and/or 判断真假用对象的「真值测试」(bool():每个对象都有真假。假值(falsy)NoneFalse0/0.0/0j(数字零)、""(空串)、[]/()/{}/set()(空容器)、__bool__ 返回 False 或 __len__ 返回 0 的对象。真值(truthy):其他一切(非零数字、非空字符串、非空容器、大多数对象)。关键陷阱(区分「假值」vs「None」)0""[] 是「合法的值」但恰好为假、or 会把它们当「没值」替换掉(可能是 bug)、只有 None 通常才真正表示「没值/缺失」。自定义对象按 __bool__(或 __len__)判真假。理解「真值测试:None/False/0/空串/空容器都是假值;or 把 0/”/[] 这些合法值当没值替换是陷阱、只有 None 才真表缺失;自定义对象按__bool__/__len__判」,就掌握了真值测试。

四、a or b 做默认值——用法与陷阱

理解 or 做默认值的正确姿势:

or 做默认值(idiom):
  x = value or default
  → value 为真用 value,为假用 default

适用场景("所有假值都该被替换"):
  display_name = user.name or "匿名用户"
  # 空字符串、None 都该显示"匿名" → or 合适
  items = data or []
  # None 或空都用空列表 → 合适

陷阱场景("0/''/[] 是合法值不该被替换"):
  ✗ count = get_count() or 10
    # count=0 是合法的(真的是 0 个)却被换成 10
  ✗ port = config.get("port") or 8080
    # port=0 被误换
  ✗ timeout = user_timeout or 30
    # user_timeout=0(不超时)被误换成 30

正确姿势(仅当 None 才用默认):
  ① 三元 + is not None(最明确):
     x = value if value is not None else default
  ② dict.get 带默认(键不存在才默认,值为 0 保留):
     port = config.get("port", 8080)
     # 注意:get 的 default 只在"键不存在"时用,
     #       键存在且值为 0 → 返回 0(正确)
  ③ 函数默认参数用 None 哨兵:
     def f(x=None):
         if x is None: x = []   # 而不是 x = x or []

判断标准:
  "空/0/[] 也算有效值" → 别用 or,用 is not None
  "空/0/[] 都当没值处理" → or 可以

所以 or 做默认值:假值都替换的场景可用,但 0/''/[] 合法时用 is not None

or 做默认值(idiom)x = value or default(value 真用 value、假用 default)。适用场景(「所有假值都该被替换」)user.name or "匿名用户"(空串/None 都显示匿名)、data or []陷阱场景(「0/”/[] 是合法值不该被替换」)count = get_count() or 10(count=0 被误换)、port = config.get("port") or 8080(port=0 被误换)、timeout = user_timeout or 30(timeout=0 被误换)。正确姿势(仅当 None 才用默认)① 三元+is not None(x = value if value is not None else default,最明确)、② dict.get(key, default)(键不存在才默认、值为 0 保留)、③ 函数默认参数用 None 哨兵(def f(x=None): if x is None: x=[]。判断标准:「空/0/[] 也算有效值」用 is not None、「都当没值」用 or。理解「or 做默认值 x=value or default;假值都替换场景可用(name or 匿名);0/”/[]合法时是陷阱(count or 10 把 0 换掉);正确用 is not None 三元或 dict.get(key,default)或 None 哨兵」,就掌握了 or 默认值的用法与陷阱。

五、and 的实用模式

理解 and 的常见用法:

and 的实用模式:

① 守卫(guard)——先判空/条件再访问,避免报错:
   x is not None and x.process()   # x 是 None 就短路
   user and user.is_active and grant()  # 层层守卫
   → 利用短路,前面不满足就不执行后面

② 条件表达式的一部分(配合 or 模拟三元,旧写法):
   result = cond and a or b   # 旧式"三元"(cond 真取 a 否则 b)
   ⚠️ 陷阱:若 a 本身是假值,会错误返回 b
      True and 0 or "x" → 0 假 → "x"(想要 0 却得 "x")
   → 现在一律用真三元 a if cond else b(无此坑)

③ all/any 的手写版:
   a and b and c   ≈ all([a,b,c]) 但返回操作数
   a or b or c     ≈ any([a,b,c]) 但返回操作数
   → 多个用 all()/any() 更清晰(返回纯布尔)

④ 链式取值:
   config and config.get("db") and config["db"].get("host")
   → 任一层为空就短路(避免 None.get 报错)
   (现代可用 config.get("db",{}).get("host") 或字典链)

所以 and 常用守卫(先判再访问);cond and a or b 旧三元有坑,改用 a if c else b

and 的实用模式① 守卫(guard)——先判空/条件再访问避免报错(x is not None and x.process() 是 None 就短路、user and user.is_active and grant() 层层守卫);② 配合 or 模拟三元(旧写法)result = cond and a or b——陷阱:若 a 本身是假值会错误返回 bTrue and 0 or "x" → “x”,想要 0 却得 “x”),现在一律用真三元 a if cond else b③ all/any 的手写版a and b and call([...]) 但返回操作数、多个用 all()/any() 更清晰;④ 链式取值config and config.get("db")... 任一层为空就短路。理解「and 常用守卫(x is not None and x.f()先判再访问);cond and a or b 旧三元有坑(a 是假值就返回 b)改用 a if c else b;多个条件用 all/any 更清晰」,就掌握了 and 的实用模式。

六、总结与实践

总结 and/or 的核心:

核心规则:
  and/or 返回"操作数本身"(不是布尔)+ 短路求值
  a and b:a 假返回 a(短路),否则返回 b(第一个假值/最后一个)
  a or b: a 真返回 a(短路),否则返回 b(第一个真值/最后一个)
  not a:永远返回布尔

真值依据:
  None/False/0/""/空容器 = 假值
  用对象的 bool()(__bool__ 或 __len__)判定

实践建议:
  ① or 做默认值:仅当"所有假值都该替换"时用
     name or "匿名" ✓;count or 10 ✗(0 被误换)
  ② 精确判"缺失":用 is not None
     x if x is not None else default
  ③ 字典取默认:dict.get(key, default)(值为 0 会保留)
  ④ 守卫用 and:x is not None and x.f()
  ⑤ 三元一律用 a if cond else b(别用 cond and a or b)

一眼判断"or 默认值是否安全":
  问自己:"0、空字符串、空列表算不算有效值?"
  算 → 别用 or,用 is not None
  不算 → or 可以

核心总结:
  and/or 返回操作数+短路
  or 默认值方便但把 0/''/[] 当假(陷阱)
  精确判空用 is not None / dict.get

核心规则and/or 返回「操作数本身」+ 短路求值(a and b:a 假返回 a、否则 b;a or b:a 真返回 a、否则 b;not 永远返回布尔)。真值依据:None/False/0/""/空容器 = 假值,用 bool()__bool__/__len__)判。实践建议① or 默认值仅当「所有假值都该替换」时用、② 精确判缺失用 is not None、③ 字典取默认用 dict.get(key,default)、④ 守卫用 and、⑤ 三元用 a if cond else b。一眼判断:「0/空串/空列表算有效值吗?」算就别用 or 用 is not None、不算 or 可以。理解「and/or 返回操作数+短路;真值 None/0/”/空容器为假;or 默认值仅假值都替换时用、精确判空用 is not None/dict.get、守卫用 and、三元用 a if c else b」,就掌握了总结与实践。

记忆钩子:「Python 的 and/or 返回『操作数本身』不是布尔+短路求值:①a and b——a 假返回 a(短路不算 b)、a 真返回 b(口诀:返回第一个假值,全真取最后)②a or b——a 真返回 a(短路)、a 假返回 b(口诀:返回第一个真值,全假取最后)③not a 永远返回布尔;真假依据是对象的真值测试 bool()——None/False/0/0.0/”/[]/{}/空容器都是假值;★a or b 做默认值(name or ‘匿名’)方便,但判的是『真值』不是『是否 None』,所以 0/”/[]这些合法值会被当没值错误替换(count or 10 在 count=0 时变 10——经典 bug),精确判缺失要用 x if x is not None else default 或 dict.get(key,default)(键存在值为 0 会保留);and 常用『守卫』x is not None and x.f()(先判空再访问);三元一律用 a if cond else b,别用有坑的 cond and a or b」

七、常见误区与追问

  • 误区:and / or 返回 True 或 False。 不是——它们返回「参与运算的某个操作数本身」(原对象):3 and 5 返回 5、0 or 'x' 返回 ‘x’、[] or [1] 返回 [1];只有 not 永远返回布尔值;正是因为返回操作数本身,a or b 才能用来取默认值、a and b.method() 才能用作守卫。
  • 误区:a or default 只在 a 是 None 时才用 default。 不是——or 判断的是「真值」(bool(a)),只要 a 是任何假值(None、0、0.0、""、[]、{})就会用 default;所以 count or 10 在 count=0 时会错误地变成 10、name or "匿名" 在 name="" 时才符合预期;要「仅当 None 时用默认」必须用 a if a is not None else defaultdict.get(key, default)
  • 误区:a and b 一定会把 a 和 b 都算一遍。 不会——and/or 是短路求值:a and b 里如果 a 为假就直接返回 a、根本不计算 b;a or b 里如果 a 为真就直接返回 a、不计算 b;这既能省性能,也能做守卫(x is not None and x.method() 里 x 是 None 就不会去调 method、避免 AttributeError)。
  • 误区:用 cond and a or b 实现三元表达式是安全的。 有坑——如果 a 本身是假值,这个写法会错误地返回 b:True and 0 or "x" 会算成 0 or "x" = “x”,你想要 0 却得到 “x”;正确的三元表达式是 a if cond else b(Python 2.5+ 支持),它没有这个问题、语义也更清晰,应该一律用它替代 cond and a or b 这种旧式写法。
  • 追问:a and b、a or b 具体返回哪个操作数? a and b:先看 a,如果 a 为假(falsy)就短路返回 a(不算 b)、如果 a 为真就返回 b——效果是「返回第一个假值,如果全为真则返回最后一个」;a or b:如果 a 为真就短路返回 a(不算 b)、如果 a 为假就返回 b——效果是「返回第一个真值,如果全为假则返回最后一个」;多个连用同理,如 a or b or c 返回第一个真值、全假返回 c,a and b and c 返回第一个假值、全真返回 c。
  • 追问:哪些值在 Python 里是「假值」?自定义对象怎么控制真假? 内置假值有:None、False、数字零(0、0.0、0j、Decimal(‘0’))、空字符串 ""、空容器([]、()、{}、set()、range(0));其余基本都是真值。自定义对象的真假由 __bool__ 方法决定(返回 True/False);如果没定义 __bool__,则看 __len__(返回 0 为假、非 0 为真);两个都没有,对象默认为真值;所以给类实现 __bool____len__ 就能控制它在 if、and/or 里的真假行为。
  • 追问:为什么 port = config.get("port") or 8080 是个隐患,应该怎么写? 隐患在于:如果配置里 port 明确设成了 0(或者你想区分「没配置」和「配置为某个假值」),or 会把 0 当假、错误地替换成 8080;正确写法有两种:① 如果 config 是字典,直接用 config.get("port", 8080)——default 只在「键不存在」时生效,键存在且值为 0 会正常返回 0;② 如果需要显式区分 None,用 port = p if (p := config.get("port")) is not None else 8080;核心原则是:当 0、""、[] 等假值是「合法的有效值」时,绝不能用 or 取默认,要用 is not Nonedict.get(key, default)

八、加强记忆

Python 的 andor 返回「操作数本身」(原对象、不是布尔值)并且短路求值a and b——a 为假就短路返回 a(不算 b)、a 为真返回 b(口诀「返回第一个假值,全真取最后一个」);a or b——a 为真就短路返回 a(不算 b)、a 为假返回 b(口诀「返回第一个真值,全假取最后一个」);not a 永远返回布尔。真假依据是对象的真值测试 bool()——NoneFalse0/0.0/0j""[]/()/{}/set()(空容器)都是假值(自定义对象看 __bool____len__)。a or b 做默认值方便(name or "匿名")但判的是「真值」不是「是否 None」——所以 0""[] 这些合法值会被当「没值」错误替换(count or 10count=0 时变 10 是经典 bug);精确判缺失要用 x if x is not None else defaultdict.get(key, default)(键存在值为 0 会保留)。and 常用作「守卫」(x is not None and x.method(),先判空再访问);三元一律用 a if cond else b(别用有坑的 cond and a or b)。一句话「and/or 返回操作数本身+短路(and 返第一个假值、or 返第一个真值);None/0/”/空容器是假值;or 做默认值方便但把 0/”/[] 当假(陷阱),精确判空用 is not None 或 dict.get;守卫用 and、三元用 a if c else b」。