ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python运算符详解:优先级、陷阱与实战避坑指南

2026/10/7 4:29:34 拓冰建站 浏览量
Python运算符详解:优先级、陷阱与实战避坑指南 1. 全景总览Python 操作运算符不是会算数就够的很多人学 Python 是从算术运算开始的、-、*、/谁都会写但 Python 的操作运算符Operators远不止算数这么简单。从列表拼接、字典合并到集合交集从短路求值到位掩码操作运算符渗透在 Python 编程的每一个角落。我见过不少入了门、写了半年项目的开发者遇到//和%的结果符号问题照样一脸懵看到海象运算符:在条件表达式里被误用导致语法错误还有人因为is和的区别没搞清楚在跨模块对象比较时踩出诡异 bug。这篇文章会把 Python 里七大类运算符全部过一遍每种运算符不只讲怎么用更重点讲为什么是这个结果以及哪些地方容易翻车。这不是一篇从官方文档抄一遍的说明书。每个知识点我都会结合真实项目中踩过的坑、调试过的案例把底层原理和避坑经验一起给你。不管你是刚开始学 Python 的新手还是写了几年想查漏补缺的老手这篇文章里的内容都能帮你在写代码时少走几条弯路。我先把目录摆出来这样你能快速找到自己需要的段落运算符分类与优先级先把全局观建立起来算术运算符里最容易忽略的三个细节比较运算符与身份运算符的等值陷阱赋值运算符和原地操作的隐蔽差异逻辑运算符的短路求值与返回值特性位运算符的实用场景与补码陷阱运算符重载让自定义对象不露怯一份可以贴墙上的常见坑速查表2. 七大运算符分类先建立一张全景地图2.1 运算符分类速览Python 官方把运算符分成七大类我直接给你一张全景表类别运算符典型用途算术 - * / // % **数学计算比较 ! 值比较赋值 - * / // % ** :绑定与更新变量逻辑and or not布尔逻辑成员in not in容器包含性检测身份is is not对象身份内存地址比较位运算^ ~ 这七类你大概率都见过但每个人踩坑的原因几乎都出在知道分类、不知道分类背后的语义边界。举个例子在数字之间做加法在字符串、列表之间做拼接在元组、集合之间做合并可一旦你把用在字典上直接给你抛TypeError: unsupported operand type(s) for : dict and dict。这个错误我见过无数次newer 开发者尤其容易中招。为什么字典不能用因为字典合并存在键冲突语义无法表达冲突时保留哪个值这种规则Python 宁可让你显式写{**d1, **d2}或d1 | d2也不给含糊的空间。这种一个符号在不同类型上有不同语义的特性是 Python 运算符体系的核心也是避坑的重灾区。所以全景图的意义在于你要知道什么时候该用哪个运算符而不是只会算数。2.2 运算符优先级看似由高到低实则处处藏雷优先级这张表科班出身的人能背但实际写代码时该踩的坑一个也没少。我把从高到低的常见优先级列出来幂运算**一元正负号x-x按位取反~x乘法、除法、取模、整数除法* / % //加法、减法 -移位 按位与按位异或^按位或|比较运算 ! 身份is / is not成员in / not in逻辑非not逻辑与and逻辑或or条件表达式x if cond else y赋值与海象 :这里最容易翻车的点有三个。第一**的优先级比一元负号高。-2 ** 2的结果是-4不是4。因为**先于-执行等价于-(2 ** 2)。如果你想表示负二的平方必须写(-2) ** 2。第二not的优先级低于比较运算但高于and和or。所以not a b会先算a b再取反这个符合直觉但not a and b究竟怎么解析答案是(not a) and b。因为not比and优先级高结果和你口头理解的not (a and b)完全不同。第三布尔运算符and/or优先级低于比较运算符这倒是符合直觉a 1 and b 2会按(a 1) and (b 2)执行。优先级造成的 bug 不会报错只会给出错误结果所以是最隐蔽的。我的习惯是只要表达式里混了三种以上运算符能加括号就加括号绝对不靠我记得优先级顺序硬写。这不算婆婆妈妈这是省下三小时调试时间的最好方式。3. 算术运算符加减乘除背后的三个隐藏细节3.1 除法三兄弟/、//、%的符号逻辑Python 3 里/永远返回浮点数哪怕4 / 2也是2.0。这一点很多人已经知道了但有两个细节值得展开说。第一个是//的行为它是向下取整不是向零取整。这个区别在负数场景下会直接改变结果print(7 // 3) # 2正数时与向零取整结果相同 print(-7 // 3) # -3而不是 -2 print(7 // -3) # -3-7 // 3数学上等于-2.333...向下取整得到-3。如果你是从 C 语言或 Java 转过来的很容易按截断小数的思维预期-2结果就会在这里出错。比如实现分页或二分查找时用mid (left right) // 2如果 left 或 right 出现负数计算出的 mid 就会比预期小一格可能导致死循环或越界。第二个是%取模的结果符号永远与除数右边的操作数保持一致print(-7 % 3) # 2不是 -1 print(7 % -3) # -2-7 % 3的结果是2因为 Python 保证a (a // b) * b a % b这个恒等式成立。-7 // 3 -3(-3) * 3 -9所以余数必须是2才能让-9 2 -7。这在处理循环数组索引、哈希分桶尤其是负索引场景时非常关键。比如你想实现一个循环队列index (current - 1) % size当current 0时得到-1 % sizePython 返回size - 1恰好是数组末尾这就非常优雅。在 C 语言里你还需要额外判断负数在 Python 里天然避开了。关于//和%的配合我踩过一个实际 bug当时做一个时间戳换算把毫秒数转成天-时-分-秒用户输入的是距今 N 天的倒计时时间为负值。我最初用total_seconds // 86400取天数再用total_seconds % 86400取剩余秒数结果发现负数时间戳下两个值都对不上调试半天才反应过来是向下取根和余数符号规则的问题。后来在所有地方显式写了文档注释//是地板除%结果跟随除数符号。之后再也没在符号逻辑上翻过车。3.2 浮点数运算0.1 0.2不等于0.3算术运算符的另一个深坑是浮点精度。0.1 0.2 0.3的返回值是False因为这个计算在 IEEE 754 双精度标准下得到的是0.30000000000000004。这不是 Python 的问题几乎所有现代编程语言都一样。但 Python 有一个特性让这个问题更显眼交互式终端里直接输出浮点数时会显示得比较友好而repr()会暴露真实值。处理浮点数比较有三条路用math.isclose()判断近似相等。涉及金额计算时用Decimal。能用整数运算就别碰浮点数。在普通业务逻辑中比如判断某个百分比是否达到阈值我用abs(a - b) 1e-9这种简单的容差判断就够了。需要严谨的场景比如金融计算、物理仿真直接转Decimal或改用整数分/厘单位别在浮点上纠结。import math # 错误的写法极其常见 if 0.1 0.2 0.3: print(equal) # 不会执行 # 推荐的写法 if math.isclose(0.1 0.2, 0.3): print(equal)3.3 幂运算与超大数**的边界与性能**在 Python 中支持任意大整数2 ** 1000直接给你一个 300 多位的大数完全不用像 C 语言那样考虑溢出。这本是好事但也意味着你可能无意中写出一个天文数字级别的循环或内存消耗。比如10 ** 1000000会在内存里创建一个约 4MB 的整数对象这个操作本身很快但如果你在一个循环里反复做这种大数运算内存就会爆炸。另外注意**的运算顺序2 ** 3 ** 2在 Python 中结果是512因为幂运算的绑定规则是右结合的等价于2 ** (3 ** 2)。这个和大多数运算符左结合的习惯不同也是常见的混淆点。pow(a, b, mod)是取模幂运算在密码学或算法题里非常有用pow(2, 10, 1000)只计算2^10 mod 1000效率远高于先算2 ** 10再取模因为前者不会产生巨大的中间结果。4. 比较运算符与身份运算符等于不等于别栽在身份上4.1比较值is比较身份调用对象的__eq__方法比较的是值是否相等is比较的是两个变量是否指向同一个对象也就是内存地址是否相同。这个区别基础知识里都有但实际场景中坑点无处不在。a [1, 2, 3] b [1, 2, 3] print(a b) # True值和元素都一样 print(a is b) # False两个不同的列表对象最常见的误用场景是跟None做比较。判断一个变量是否为None应该用is None不是 None。虽然两者在绝大多数情况下结果相同但会调用对象的__eq__方法而某些自定义对象的__eq__可能被重写成永远返回 True或永远返回 False导致判断失灵。class WeirdObject: def __eq__(self, other): return True obj WeirdObject() print(obj None) # True因为 __eq__ 总是返回 True print(obj is None) # False这才是正确的判断入坑的另一个经典场景是整数缓存。CPython 会对小整数通常是 -5 到 256做缓存所以a 100; b 100; a is b返回 True。但一旦超过这个范围256和257的行为就不同了a 256 b 256 print(a is b) # True命中缓存 a 257 b 257 print(a is b) # 可能是 False取决于解释器实现这个问题并非在所有 Python 实现中都一致PyPy 和 CPython 行为可能有差异。所以绝对不能依赖is去比较整数、字符串这种不可变类型的值。用is只做两件事比较None、比较单例对象比如True、False和某些枚举常量。4.2 链式比较Python 独一份的糖衣语法Python 支持链式比较a b c等价于a b and b c而且中间的b只求值一次。这个特性在写区间判断时非常优雅# 判断 x 是否在 [0, 100] 区间内 if 0 x 100: pass在其它语言里你得写x 0 x 100在 Python 里直接链式搞定。这也是为什么很多从 Java 转 Python 的人第一次看到这种写法会觉得竟然还能这样。链式比较有个容易忽略的坑不要在里面嵌入带副作用的函数调用。比如a random.random() b虽然中间的random.random()只求值一次但如果你写成a next(it) b这种带迭代器推进的写法一旦逻辑复杂起来行为会很难预测。我建议仅在普通变量或简单属性比较时用链式语法保持可读性优先。4.3 浮点数比较的三个层次除了常见的陷阱浮点比较在数值分析中还有绝对值容差和相对容差的区别。math.isclose()默认使用相对容差rel_tol1e-09对大数和小数有不同的表现。print(math.isclose(1e10, 1e10 1)) # True相对误差很小 print(math.isclose(0.0, 1e-10)) # False绝对差太小但相对差无穷大如果你比较的对象是金额、浓度这种必须绝对精确到小数位的量建议把rel_tol和abs_tol同时设置if math.isclose(actual, expected, rel_tol1e-6, abs_tol1e-9): pass5. 赋值运算符从到:的认知升级5.1 复合赋值x y不等于x x y对可变对象在 Python 中x y和x x y对不可变对象int、str、tuple行为基本一致但对可变对象list、dict、set就完全不同了。a [1, 2, 3] b a a [4] # 原地扩展 print(b) # [1, 2, 3, 4]a 和 b 指向同一个对象b 也跟着变了 c [1, 2, 3] d c c c [4] # 新创建一个列表 print(d) # [1, 2, 3]c 重新绑定到新对象d 不受影响在可变对象上调用的是__iadd__它原地修改对象调用的是__add__它生成一个新对象再重新赋值。这个差异在传参、共享引用时会直接改变程序行为。你的函数里如果接收一个列表参数并做了args item那么函数外部传入的列表也会被修改这在 Python 里叫做副作用。写 API 时如果不想让外部数据被意外篡改要么改成args args [item]要么在函数开头用args args.copy()做防御性拷贝。这个坑我曾经在实现一个队列管理器时踩得很惨。当时在类的方法里用self._tasks new_tasks觉得只是给任务列表追加元素结果外部有另一个对象也引用了同一个_tasks数据被悄悄污染排查了很久才发现是复合赋值原地修改的锅。5.2 海象运算符:赋值与表达式合二为一但要小心边界Python 3.8 引入了海象运算符:允许在表达式中完成赋值。最经典的用途是避免在循环或条件判断中重复调用同一个函数# 不用海象运算符 data read_data() if data is not None: process(data) # 用海象运算符 if (data : read_data()) is not None: process(data)看起来简洁但坑也藏在简洁里。第一海象运算符的优先级非常低几乎和赋值运算符一样。所以几乎在所有非最简单场景中都要加括号# 错误的写法 if data : read_data() is not None: # 等价于 data : (read_data() is not None) pass # 正确的写法 if (data : read_data()) is not None: pass第二海象运算符不能用在赋值表达式所在语句之外引用变量。比如print(x : 5)是合法的会打印 5但x : 5, y : 6这种多个变量同时赋值的写法是语法错误除非写成(x : 5), (y : 6)。第三海象运算符在列表推导式中的作用域很特殊它会泄漏到外层。这算是一个特性但也容易造成意外# 列表推导式中用 :变量 i 会泄漏出去 values [(i : x * 2) for x in range(3)] print(i) # 4i 泄漏到了外部我个人的习惯是海象运算符用在while循环读取数据块、正则匹配、文件读取这类赋值-判断-使用三连场景其它地方尽量不用。它是一个利器但不是让你炫技的玩具。代码写完三个月后自己回来看一个复杂的海象表达式很可能要花五分钟才能理清逻辑。5.3 可变默认参数的赋值陷阱这个坑虽然不完全是运算符的问题但由于的存在显得让人防不胜防。def append_item(item, lst[]): lst.append(item) return lst第一次调用append_item(1)返回[1]第二次调用append_item(2)返回[1, 2]。默认参数lst[]只在函数定义时被创建一次之后所有调用共享同一个列表。这在内部表现为记忆效应在外部表现为完全意想不到的行为。修复方式很简单用None作为默认值内部再创建新列表。def append_item(item, lstNone): if lst is None: lst [] lst.append(item) return lst这类问题看起来和运算符无关但归根结底是绑定语义在可变对象上造成的。Python 里的变量不是盒子而是标签做的是把标签贴到对象上不是把值复制进盒子。理解这一点赋值运算符相关的坑基本能避掉 80%。6. 逻辑运算符短路求值返回的不一定是布尔值6.1and/or返回操作数不是 True/False这是 Python 新手最常误解的点之一。and和or返回的不是布尔值而是参与求值的操作数本身。规则很简单a and b如果a为假返回a否则返回ba or b如果a为真返回a否则返回bprint(0 and hello) # 0因为 0 为假直接短路 print(1 and hello) # hello print(0 or hello) # hello print(1 or hello) # 1因为 1 为真直接短路这套规则在实际编码中极其常用。最常见的写法是用or提供默认值username input_name or anonymous如果input_name是空字符串假值就使用anonymous。同样and常用于守卫表达式user and user.name如果user为Noneuser and user.name返回None不会抛出AttributeError。这比写一长串if user is not None:要简洁不少。但这里有一个重要的坑如果值可能是0、空列表、False这类合法但为假的值用or做默认值就会误伤。# 用户设置了一个配置值为 0 表示不启用为空表示未设置 setting config.get(timeout) or 30 # 如果 timeout 设为 0上面的表达式会返回 30而不是 0这里正确做法是用显式的 None 判断setting config.get(timeout) if setting is None: setting 30判断是 None和是假值是完全不同的两个概念。用or之前一定要想清楚你要的是哪一个。6.2 短路求值不只是节省性能更是程序语义的一部分短路求值意味着and和or在左侧结果已经能确定整个表达式的结果时不再计算右侧。这在两个场景下有关键作用一是避免不必要的计算二是避免报错。# 避免除零错误 if divisor ! 0 and dividend / divisor 10: pass # 避免空指针错误Python 里的 NoneType 错误 if obj is not None and obj.attr 1: pass如果把两个判断的顺序换一下dividend / divisor 10 and divisor ! 0程序会在除数为 0 时直接抛ZeroDivisionError。还有一点值得注意and和or的短路只会发生在运行时不会因为右侧是一个肯定会报错的表达式而在编译期阻止你。比如False and 1 / 0的结果是False因为右侧根本没有执行。6.3not的关键禁忌别在in表达式里乱用not的优先级我们已经提过低于比较运算符高于and/or。最常见的误用是把not a in b和a not in b视为等价。事实上它们在结果上确实等价但语义上not in是专门为成员检测设计的运算符可读性更强也很少引起误解。# 推荐的写法 if key not in dict: pass # 等价但不推荐的写法 if not key in dict: pass从性能上讲没有明显差别但not in在语义上更清晰也避免了not (key in dict)的括号细节。写代码是给人看的能用清晰的运算符表达清楚就不要用组合逻辑绕弯子。7. 成员与身份运算符in 的判断效率与 is 的复制警告7.1in的性能差距list 与 set 的差别是 O(n) 和 O(1)in运算符在不同容器上的性能差异巨大。在列表中in是线性扫描时间复杂度和列表长度成正比在集合和字典中in是哈希查找时间复杂度和容器大小无关。import time data_list list(range(1000000)) data_set set(data_list) target 999999 start time.time() print(target in data_list) print(list 查找耗时:, time.time() - start) start time.time() print(target in data_set) print(set 查找耗时:, time.time() - start)实测下来百万级数据上 list 的in查找大约是 set 的几百倍耗时。这不是 Python 实现太慢而是两种数据结构的本质区别list 是顺序存储按位置访问set 是哈希表按内容定位。所以当你需要频繁判断某个元素是否在容器中时一开始就考虑用set或dict而不是把所有元素装进 list 再做in判断。这是一个写代码之前就该做好的数据结构和运算符搭配决策。7.2is与副本拷贝复制对象时最容易犯的身份错觉除了小整数缓存is在字符串上的行为更迷惑。Python 会缓存部分字符串尤其是短字符串和看起来像标识符的字符串导致a hello; b hello; a is b返回 True。但一旦字符串由拼接、切片或格式化产生缓存机制就不一定生效了a hello b hel lo print(a is b) # CPython 中可能是 True字面量拼接会被编译器折叠 c .join([h, e, l, l, o]) print(a is c) # 可能是 False运行时拼接产生新对象 d hello print(a is d.strip()) # 取决于解释器实现大概率 False还有一种常见误用场景是复制自定义对象后立刻用is验证复制成功。比如你用切片复制列表new_list old_list[:]然后new_list is old_list返回 False这是正常的因为切片创建了新对象。但如果你做的是new_list old_list.copy()结果当然也是 False。用type(x) is type(y)判断类型时也要注意这不是安全的写法。正确的类型判断应该用isinstance()因为isinstance支持继承关系而type() is只能做精确类型匹配。在判断子类实例时type判断会让你误判。class Animal: pass class Dog(Animal): pass d Dog() print(type(d) is Animal) # False print(isinstance(d, Animal)) # True8. 位运算符看似底层实则是高效编码的利器8.1 位运算基础与补码陷阱位运算符、|、^、~、、在 Python 中直接作用于整数在二进制下的每一位。很多人写业务代码觉得位运算用不上但在标志位管理、权限控制、哈希算法、性能优化场景里位运算的效率优势极其明显。Python 整数是无限精度的这带来一个和 C 语言截然不同的坑负数在内存中并不像 C 语言那样是有限的二进制补码Python 给你的是无限长度的补码概念这直接导致~运算结果和直觉不一样。print(~5) # -6而不是预期的 -5 或 0xFFFFFFFA print(~0) # -1 print(~-1) # 0~x的规则在 Python 中是-(x1)。因为~会把所有位取反包括无限延伸的符号位。~5等价于-(51) -6。右移对于负数来说也不是简单除以 2。-5 1的结果是-3不是-2。因为右移操作在负整数上仍然是向下取整语义-5 / 2 -2.5向下取整是-3。编写位运算代码前我建议先在交互式环境里把正负数都测试一遍再写入正式代码。这个建议听起来很基础但我在写权限标志位判断时几乎每一次都要用实际数值确认过正负号行为才能在压测时不翻车。8.2 位运算的三个实用场景第一个是权限标志位管理。用一个整数存储多个开关状态每一位代表一个权限READ 1 0 # 1 WRITE 1 1 # 2 EXEC 1 2 # 4 user_perm READ | WRITE # 3 has_write bool(user_perm WRITE) # True has_exec bool(user_perm EXEC) # False把四个布尔值压缩到一个整数字段里存数据库时只占一列读取时用位运算解包效率高且逻辑清晰这在配置管理中非常常见。第二个是快速乘除 2 的幂。x 1等价于x * 2x 1等价于x // 2。注意这个等价关系只对标量整数操作成立不要用在负数上碰运气。第三个是利用异或^特性做值交换和加解密基础# 不用临时变量交换两个整数 a 10 b 20 a ^ b b ^ a a ^ b # 现在 a 20, b 10异或交换在算法题里是个经典技巧但实际业务代码中我不建议用可读性实在太差。了解原理即可真正写代码时老老实实用临时变量或者直接a, b b, a。8.3 位运算的优先级再次提醒位运算的优先级介于移位和比较之间也就是说a b 0会被解析成a (b 0)而不是(a b) 0。这又是一个优先级导致结果错误的典型雷区。判断a 和 b 按位与非零时必须写成if (a b) 0: pass不加括号的位运算判断我见过太多次了。一个很普通的权限判断代码因为漏掉了括号直接导致权限校验失效这是一个在生产环境里能引起安全事故的 bug。请大家一定把括号养成习惯。9. 运算符重载让自定义对象融入语言生态9.1 基础重载__add__、__sub__怎么实现运算符之所以对不同类型的对象有不同行为本质是对象定义了对应的特殊方法。自定义类也可以通过实现这些特殊方法让自己支持、-、*等运算符。拿三维向量举一个实际例子class Vector3D: def __init__(self, x, y, z): self.x x self.y y self.z z def __add__(self, other): return Vector3D( self.x other.x, self.y other.y, self.z other.z ) def __repr__(self): return fVector3D({self.x}, {self.y}, {self.z}) v1 Vector3D(1, 2, 3) v2 Vector3D(4, 5, 6) print(v1 v2) # Vector3D(5, 7, 9)这只是最基础的形态。实际中还要处理__iadd__原地加法__radd__右加法比如5 v1__eq__相等判断__lt__小于判断用于排序9.2NotImplemented和NotImplementedError是两码事重载中最容易踩的坑是把NotImplemented和NotImplementedError搞混。NotImplementedError是一个异常类用于在抽象方法中声明子类必须实现。NotImplemented是一个单例对象用于二元运算符表示我不支持这种组合运算。class Number: def __init__(self, value): self.value value def __add__(self, other): if isinstance(other, Number): return Number(self.value other.value) return NotImplemented # 关键点返回这个单例而不是抛异常当__add__返回NotImplemented时Python 会尝试调用对方的__radd__如果对方也没有实现最终才抛TypeError。如果你错误地在__add__里raise NotImplementedError程序会直接崩溃而不会给另一个对象一个机会来处理这个运算。在实现数值运算类时注意NotImplemented的返回值是必须的。这也是运算符重载正确性和普通异常处理之间最容易被忽视的界限。9.3 运算符重载实际应用中的注意清单重载__eq__时有个隐藏问题如果你只重载__eq__而没有同步重载__hash__这个对象就会变成不可哈希的放进字典键或集合时会报错。Python 的要求是如果两个对象相等它们的哈希值也必须相等。反之如果重载了__hash__但没有保证相等对象的哈希一致字典查找就会出现能相等但找不到键的诡异现象。重载比较运算符时推荐用functools.total_ordering装饰器它可以根据你定义的__eq__和__lt__自动补全、、from functools import total_ordering total_ordering class Person: def __init__(self, name, age): self.name name self.age age def __eq__(self, other): return self.age other.age def __lt__(self, other): return self.age other.age p1 Person(Alice, 30) p2 Person(Bob, 25) print(p1 p2) # True print(p1 p2) # True运算符重载的能力是把双刃剑。它让自定义类型用起来像内置类型但过度重载会严重损害代码可读性。比如把一个业务类重载来表示合并订单如果你不加足够的文档说明同事看到order1 order2会一头雾水。重载前先问自己这个运算符语义是不是足够直觉如果答案不明确不如改成命名方法order1.merge(order2)。10. 常见问题速查表把踩过的坑贴墙上经常有人问我Python 运算符还有哪些坑 与其零散地记不如整理成一张速查表。下面这份表格是我把多年排查过的线上问题整理出来的每一行背后都对应一个真实 bug场景错误写法正确写法坑的本质负数的整除-7 // 3期望 -2结果是 -3向下取整不是截断负数的取模-7 % 3期望 -1结果是 2余数符号跟随除数浮点精度0.1 0.2 0.3math.isclose(0.10.2, 0.3)IEEE 754 精度幂和负号-2 ** 2期望 4结果是 -4要写(-2) ** 2优先级高于负号链式右结合2 ** 3 ** 2期望 64结果是 512幂运算是右结合None 判断x Nonex is None__eq__可能被重载列表拷贝b a后改 b 想不改变 a用b a.copy()变量是标签不是盒子复合赋值别名a [x]共享对象被改用a a [x]__iadd__原地修改and/or 返回值认为返回布尔值返回操作数本身短路求值语义or 默认值config.get(port) or 8080想忽略 0显式判断 None0 是假值但合法位运算判断a b 0(a b) 0优先级低于右位移负数-5 1期望 -2结果是 -3向下取整语义~x取反~5期望 0xFFFFFFFA结果是 -6无符号位概念类型判断type(x) is intisinstance(x, int)继承关系被忽略可变默认参数def f(a, lst[])用 None 加内部创建默认值只创建一次重载没回 NotImplemented抛NotImplementedError返回NotImplemented二元运算符双派发机制这张表我建议你收藏起来写代码遇到相似场景时翻一眼。很多问题不是不懂而是以为自己懂但记错了细节。11. 我的最后一个建议用类型标注与静态检查工具兜底运算符相关的坑很多靠记忆和经验能避开但人总会遗忘、会劳损、会在 deadline 前写出糊涂代码。所以我强烈建议在项目里启用类型标注并且把mypy或pyright加入 CI 流程。比如def calc(a: int, b: int) - int这种基础标注能让静态检查工具在a b之前就告诉你类型是否可能不匹配Optional[int]结合is not None的检查也能在编译期提前发现潜在的空值运算问题。类型标注不能消除所有运算符坑比如浮点精度、负数整除这类语义问题但它能帮你拦住一批最基础、最容易被忽略的类型错误把踩坑概率显著降低。我自己的项目里用pyright做了严格模式后跟运算符相关的低级 bug 几乎消失排查时间也从小时级降到了分钟级。写了这么长最后再说一点心得。运算符是 Python 的基石它并不枯燥只是细节多。不要把这篇内容当知识点背而是带着实际场景去看写出一个-7 // 3试试打断点看一次把a [1]放在共享列表的场景里观察一次引用变化。只有亲手踩过坑你才会对运算符体系真正形成肌肉记忆。如果这篇文章能帮你少踩几次坑、少熬几次夜那就算值回票价了。