# Python的运算符有哪些?and、or为什么会返回对象?
面试官问:"Python有哪些运算符?and和or为什么不总是返回True或False?"
很多人会列举加减乘除、大于小于,但对逻辑运算符的返回值感到困惑。看到3 and 5返回5、0 or "hello"返回"hello",以为是特殊情况。真正要讲清楚的是:Python的and、or采用短路求值,返回的是决定真假的那个对象本身,而不是转换后的布尔值。
# 简要回答
Python运算符按功能分为:算术运算符(+、-、*、/、//、%、**)、比较运算符(==、!=、>、<、>=、<=、is、is not、in、not in)、逻辑运算符(and、or、not)、位运算符(&、|、^、~、<<、>>)、赋值运算符(=、+=、-=等)和其他运算符(如海象运算符:=)。
and和or采用短路求值,返回决定结果的对象,而不是True或False。x and y在x为假时返回x,否则返回y;x or y在x为真时返回x,否则返回y。这种设计让表达式更简洁,比如result = x or default可以在x为空时使用默认值。
not是唯一总是返回布尔值的逻辑运算符,它对操作数取反后返回True或False。
# 详细回答
# 算术运算符
常见的数学运算:
print(10 / 2) # 5.0,真除法总是返回浮点数
print(-7 // 2) # -4,地板除向负无穷取整
print(-7 % 2) # 1,余数符号跟随除数
print(2 ** 10) # 1024
2
3
4
/总是返回浮点数,即使能整除。//是向下取整,对负数来说是朝负无穷方向,所以-7 // 2是-4而不是-3。%和//配套,满足a == (a // b) * b + a % b,因此余数的符号跟随除数。这两点是C/Java转过来的录友最容易踩的坑。
# 比较运算符
==、<这类比较对内置类型返回True或False。Python的比较运算符支持链式比较,1 < x < 10等价于1 < x and x < 10,中间的x只计算一次:
age = 25
print(18 <= age < 60) # True
print(1 < 3 > 2) # True,等价于 1 < 3 and 3 > 2
2
3
is和is not比较对象身份,已在《is和==有什么区别?》中详细讲解。
in和not in判断成员关系,比如3 in [1, 2, 3]、"py" in "python"。它的本质是调用容器的__contains__()方法,没有该方法时退回到迭代逐个比较。
# 逻辑运算符:and、or、not
这是面试重点。先明确真值测试:在if、while、and、or、not这类需要判断真假的位置,Python会检查对象的真假。None、False、数值零(0、0.0)、空容器(""、[]、{}、set()等)为假,其余对象默认为真。自定义类可以通过__bool__()或__len__()改变规则。
and和or只用真值测试做判断,返回的却是原来的操作数对象,不是布尔值。
and:遇假即停
x and y的求值规则:
- 先计算
x,如果x为假,直接返回x,不再计算y。 - 如果
x为真,返回y(不管y真假)。
or:遇真即停
x or y的求值规则:
- 先计算
x,如果x为真,直接返回x,不再计算y。 - 如果
x为假,返回y(不管y真假)。
用一段代码同时验证两条规则:
print(0 and 5) # 0,左边为假,直接返回左边
print(3 and 5) # 5,左边为真,返回右边
print([] or "default") # default,左边为假,返回右边
print("Carl" or "anon") # Carl,左边为真,直接返回左边
nickname = ""
print(nickname or "匿名用户") # 匿名用户
2
3
4
5
6
7
nickname or "匿名用户"是常见的默认值写法:空字符串为假,or返回右边的对象。它的坑在于0、""、[]都会被当成"没有值"。如果0是合法输入,就要写成x if x is not None else default。
not总是返回布尔值
not 0是True,not [1, 2]是False。它只有一个操作数,不存在短路问题,也不会返回原对象。

图中展示了and和or的短路求值机制:and在左侧为假时直接返回左侧对象,or在左侧为真时直接返回左侧对象,都不会继续计算右侧表达式。
# 短路求值的实际意义
短路求值不仅是性能优化,还能避免错误:
data = []
if data and data[0] == 1:
print("第一个元素是1")
print("没有报错") # 没有报错
2
3
4
data是空列表,and在左边就停下了,data[0]根本不会执行,所以不会抛IndexError。同理,user and user.name可以在user为None时避免AttributeError。把"保护条件"写在and左边,是短路求值最常见的用法。
# 位运算符
对整数的二进制位进行操作:
| 运算符 | 含义 | 示例 |
|---|---|---|
& | 按位与 | 5 & 3 → 1 |
\| | 按位或 | 5 \| 3 → 7 |
^ | 按位异或 | 5 ^ 3 → 6 |
~ | 按位取反 | ~5 → -6 |
<< | 左移 | 5 << 1 → 10 |
>> | 右移 | 5 >> 1 → 2 |
&、|不是逻辑运算符。它们不短路,两边都会求值;对bool使用时虽然能得到True或False,但和比较运算混用时优先级会坑人:x > 1 & x < 5实际是x > (1 & x) < 5。条件判断一律用and、or。
# 赋值运算符
=把名字绑定到对象,+=、-=、*=等是复合赋值。x += 1看起来等价于x = x + 1,但对可变对象并不完全一样:列表的+=会调用__iadd__()原地修改,x = x + [1]则创建新列表再重新绑定。如果还有其他名字指向原列表,两种写法的结果就不一样。这一点和《可变对象与不可变对象》直接相关。
Python 3.8引入了海象运算符:=(赋值表达式),可以在表达式中同时赋值和使用值:
words = ["python", "go", "javascript"]
if (n := len(words)) > 2:
print(f"共{n}个单词") # 共3个单词
2
3
海象运算符可以减少重复计算或重复调用,但不要滥用,影响可读性时还是拆开写更清楚。
# 运算符优先级
常见优先级从高到低:
**(幂运算)~、+、-(一元运算符)*、/、//、%+、-(二元加减)<<、>>(位移)&(按位与)^(按位异或)|(按位或)- 比较运算符(
==、!=、>、<等) notandor
两个容易错的点:**右结合且高于一元负号,所以-2 ** 2是-4;not低于比较运算,所以not x == y是not (x == y)。拿不准就加括号。
# 知识拓展
面试官可能追问:
Q1:老代码里的cond and a or b有什么问题?
这是三元表达式出现前的模拟写法,问题在于a为假时会错误地返回b。比如True and 0 or 10结果是10而不是0。Python 2.5起就有条件表达式,直接写a if cond else b。
Q2:1 or 2 and 0返回什么?
返回1。and优先级高于or,表达式等价于1 or (2 and 0);左边1为真,or直接短路,右边根本不计算。
Q3:in运算符的时间复杂度是多少?
取决于容器。列表、元组、字符串需要逐个比较,是O(n);集合和字典的键基于哈希查找,平均是O(1)。频繁做成员判断时,先把数据转成集合。
Q4:为什么~5等于-6?
Python的整数语义规定~x等于-(x+1),可以理解为在无限位补码下按位取反。Python整数没有固定位宽,所以不会像C语言那样出现~5等于某个大正数的情况。
Q5:a < b < c和a < b and b < c完全等价吗?
结果一样,但链式比较中的b只计算一次。如果b是一个有副作用或很耗时的函数调用,两种写法的行为就会不同。
评论
验证登录状态...