摘要:算术、比较、逻辑、成员、身份运算符,Python 中的各种运算符到底怎么用?为什么 1 < x < 10 能用但 not x == y 容易踩坑?本文带你一文搞定运算符机制,配合在线代码运行器,带你理清 Python 优先级顺序。
写代码这些年,我见过无数奇形怪状的 Bug。其中有一大类,既没有语法错误,也没有逻辑逻辑漏洞,却能让程序跑出完全意想不到的结果。
去 Debug 时翻来覆去检查,最后才一拍大腿:少写了一个括号,被运算符的「优先级」给坑了。
在 Python 中,运算符有很多种,而且它们的结合顺序并不完全符合人类的常规直觉。今天我们就把这块地基重新夯实一下,只聊干货和最容易踩坑的细节。
顺便提一句,本站刚才上线了代码一键运行功能。文中的代码块右上角都有个「运行」按钮,点击它就能在浏览器里直接测试,非常方便,大家记得多点点试试。
算术运算符:比数学课本多一点
算术运算符是我们最熟悉的,无非是加减乘除。但在 Python 中,有几个符号需要特别留意:
/是真除法:不管两个数能不能整除,结果永远是浮点数(小数)。//是整除:向下取整,丢弃小数部分。%是取模:计算余数。**是幂运算:也就是乘方。
我们可以看一下它们在 Python 中的具体计算表现。记得点击代码右上角的运行按钮去 playground 亲眼看结果:
a = 10
b = 3
print("真除法 10 / 3 =", a / 3) # 结果是浮点数 3.3333333333333335
print("整除 10 // 3 =", a // b) # 结果是整数 3
print("取模 10 % 3 =", a % b) # 余数是 1
print("幂运算 10 ** 3 =", a ** b) # 10 的 3 次方是 1000
这里有个关于负数整除的经典大坑。比如 -10 // 3,你觉得结果是多少?由于整除在 Python 里执行的是「向下取整」,结果并不是 -3,而是 -4。这个细节在做索引计算或坐标系转换时极其致命。
比较运算符与链式比较的魔法
比较运算符包括 ==、!=、>、<、>=、<=,用于判断两者关系并返回布尔值(True 或 False)。
这里必须要夸一下 Python 极其贴心的设计:链式比较。你可以直接像数学课本上那样连着写:
age = 25
# 在 Python 中这完全合法,相当于 age > 18 and age < 30
result = 18 < age < 30
print("18 < age < 30 结果为:", result)
这在很多其他编程语言里是不能直接实现的(比如在 C++ 或 Java 里,18 < age < 30 会先计算 18 < age 得到 true,然后把 true 隐式转换成 1 去跟 30 比较,结果完全偏离预期)。Python 默默在底层帮我们做了解析。
逻辑运算符与「短路求值」机制
Python 逻辑运算符只有三个:and、or 和 not。
除了要记住它们的优先级关系(not > and > or)之外,最核心的概念就是短路求值:
- 对于
and运算:如果左边已经是False,不管右边写了什么,Python 都不会再去理会它,直接返回False。 - 对于
or运算:如果左边已经是True,Python 就会直接返回结果,不会去计算右边的表达式。
这不仅是为了省电提高效率,在写代码时更能作为一种「保护伞」防崩溃:
# 下面是一段防崩溃的经典写法
def 检查列表(数据):
# 如果 数据 是 None,直接触发短路返回 False,不会执行 len(数据),从而防止报错崩溃
if 数据 is not None and len(数据) > 0:
print("列表非空,第一个元素是:", 数据[0])
else:
print("空列表或 None")
检查列表(None)
检查列表([42, 100])
你可以试着把 is not None 和 len(数据) > 0 的位置调换一下,再次传入 None,程序就会抛出空指针异常。理解了短路机制,你就能写出既简洁又安全的代码。
身份与成员:is 和 == 绝不是一回事
这是 Python 面试必考的经典概念:
==比较的是值是否相等(内容是否一致)。is比较的是身份是否相同(两者是否指向内存里的同一个对象,即内存地址id()是否相等)。
用个接地气的类比:世界上有两个一模一样的苹果,它们的价格和甜度都相同(值 == 是相等的),但它们并不是同一个苹果(身份 is 是不同的)。
看一下代码演示:
list_a = [1, 2, 3]
list_b = [1, 2, 3]
list_c = list_a
print("list_a == list_b:", list_a == list_b) # True,因为内容一模一样
print("list_a is list_b:", list_a is list_b) # False,它们存在内存的不同角落
print("list_a is list_c:", list_a is list_c) # True,因为 list_c 指向了 list_a 的地址
同样的,成员运算符 in 和 not in 可以帮我们快速判断某个元素是否存在于容器中,写起来比写一个 `for` 循环遍历再匹配优雅百倍。
终极对决:Python 运算符优先级总览
当以上所有运算符混在一起时,Python 会按怎样的顺序进行处理呢?
| 优先级 | 运算符 | 描述 |
|---|---|---|
| 1 (最高) | () |
括号(永远的第一优先级) |
| 2 | ** |
幂运算 |
| 3 | +x, -x, ~x |
一元正负号、按位取反 |
| 4 | *, /, //, % |
乘、除、整除、取余 |
| 5 | +, - |
加、减 |
| 6 | <<, >> |
按位左移、右移 |
| 7 | & |
按位与 |
| 8 | ^ |
按位异或 |
| 9 | | |
按位或 |
| 10 | ==, !=, >, >=, <, <=, is, is not, in, not in |
所有的比较、身份、成员运算符(同一级) |
| 11 | not |
逻辑非 |
| 12 | and |
逻辑与 |
| 13 (最低) | or |
逻辑或 |
💡 提示
如果你仔细观察,会发现比较运算符的优先级比逻辑运算符要高。这就是为什么我们写 x > 5 and y < 10 时,即使不加括号,Python 也会正确地先去计算 x > 5 和 y < 10,再去算 and 逻辑。
避坑总结
看完了长篇理论,我们来看看下面这行非常容易翻车的小代码:
# 目标是:当 x 不是 1 且不是 2 时,结果为 True
x = 2
result = not x == 1 or x == 2
print("not x == 1 or x == 2 运算结果为:", result)
你觉得上面的输出是 True 还是 False?
其实答案是 True。因为按照优先级关系,== 先算,然后是 not,最后才是 or。所以这行语句等价于 (not (x == 1)) or (x == 2)。当 x = 2 时,x == 1 为 False,not False 为 True。于是短路机制触发,直接返回了 True,哪怕我们原本想要的是当 x 是 2 时输出 False。
正确的逻辑写法必须加上括号:not (x == 1 or x == 2)。
虽然我列了一整张优先级表格,但实战中最实用的建议是:看不准的优先级,统统手动套上括号。加个小括号不仅能救你一命,更能让其他人(包括三个月后的你自己)在阅读代码时,一眼就能看清你的真实意图,别指望大家去背优先级表。
如有疑问欢迎留言交流。
加载评论中……