上一期我们把变量和数据类型捋了一遍今天轮到一个看起来“没什么可讲”的话题——运算符。其实Python里的运算符比你想的要细比如同样是除法C里两个整数相除会直接截断小数Python里却会老老实实算出小数结果再比如你说你懂取余那你知道-7 % 3在Python里等于多少吗不知道的都当心一点这玩意儿很容易写出“不报错但结果就是不对”的代码。这篇“Python入门四运算符”不只列运算符清单我把新手最容易踩的坑、老手容易忽略的细节全翻出来过一遍。适合刚学完数据类型、准备动手写小程序的朋友也适合从Java或C转过来的同学快速对齐Python的运算符习惯。1. 运算符全景Python里到底有多少种运算符1.1 七大类运算符速览运算符你可以理解成“表达式的动词”它把两个操作数连接起来产生一个新结果。Python里一共七大类日常写代码基本都能覆盖算术运算符、-、*、/、//、%、**比较运算符、!、、、、赋值运算符、、-、*、/、%、**、//逻辑运算符and、or、not位运算符、|、^、~、、成员运算符in、not in身份运算符is、is not这七类里我个人的学习建议是算术、比较、赋值、逻辑这四个必须熟练掌握成员运算符其实就是用来判断“某个元素在不在一个容器里”比如if a in abc很好理解身份运算符is先会用但里面的门道留到后面讲位运算符入门阶段可以先认识等你要写算法或者做底层相关的东西再深入。1.2 为什么Python的运算符和C、Java差别这么大网上搜“运算符”会出来一大堆C语言和Java的教程很多结论拿到Python里是错的。这里给你打个预防针和--C里有自增自减Python压根没这两个运算符想自增就写count 1。我之前在社区见过有人问“Python为什么没有i”其实不是Python设计者偷懒而是Python刻意避免这种“原地修改变量”的写法让代码行为更可预测。逻辑运算符C和Java里是、||、!Python直接用英文单词and、or、not读起来反而更接近自然语言。三目运算符C风格是条件 ? 结果A : 结果BPython是结果A if 条件 else 结果B我刚转过来时老是写反顺序这个只能多练。除法语义C里int / int结果还是 intPython 3 里/永远返回浮点数想整数除法就得用//。明确这些差异之后再去看网上的资料心里就有数了C或Java的运算符文章可以参考思路但动手前一定先在Python里验证一遍。2. 算术运算符细节最密集的一个区域2.1 除法有三兄弟/、//、%Python的除法相关运算符有三个分别是/、//、%它们解决的是不同的问题/标准除法结果永远是浮点数比如7 / 2 3.5。//向下取整除法floor division结果永远是整数当操作数是整数时即取“商”的向下整数值。%取余数即“除完之后剩下多少”。可以说最容易被忽略的就是//的“向下取整”这三个字。很多从C语言转过来的人会把//理解为“截断小数”也就是向零取整这在正数里没问题一到负数就露馅了。我放个表格你一眼就能看明白表达式结果说明7 // 23向下取整就是3-7 // 2-4向下取整因为-3.5向下走到-47 % 217 3*2 1余1-7 % 21不是-1Python里余数始终跟除数同号-7 // 2配-7 % 2商-4余1验证-4*2 1 -7记住一个公式被除数 商 * 除数 余数。Python保证a // b和a % b满足这个等式而且余数的符号和除数一致。所以-7 % 3的结果是2因为在Python里-7 // 3 -3-3 * 3 2 -7。C语言里同一个式子结果是-1这就是语言语义的差异。2.2 整数除法和取余的实用场景//和%看着不起眼实际开发里到处都是。最典型的几个场景判断奇偶num % 2 0是偶数num % 2 1是奇数。这个比位运算num 1直观多了。取数字的每一位比如拿到一个三位数123123 // 100得到百位1123 % 10得到个位3。分页一次展示固定数量的条目总条数 // 每页条数算页数。注意如果总数不能被整除要用(总条数 每页条数 - 1) // 每页条数向上取整才能算对页码。轮转比如一个列表长度固定下标index % 列表长度可以保证下标永远不越界做循环队列常用的手法。我查资料时看到热搜词里有“李白打酒python”那类逻辑题经常用到//和%的交替计算其实就是反复“倒来倒去”的算术推演。作为入门练习确实很练手建议有空可以试一下。2.3 幂运算**和布尔参与运算的冷知识**是幂运算2 ** 3就是8。大多数人知道这个但不知道**是右结合的也就是2 ** 3 ** 2等价于2 ** (3 ** 2)结果是512而不是64。刚开始学的时候建议顺手加个括号别靠“我记得”来猜。另一个冷知识布尔值True和False在Python里其实是int的子类True就是1False就是0。所以True 1等于2False * 10等于0。虽然不推荐把布尔当数字用但理解这一点能帮你解释很多“诡异”行为比如[1, 2][True]取到的是2因为下标True被当成1了。3. 赋值运算符等号背后的门道3.1 链式赋值与多变量同时赋值在Python里是“把右边的对象绑定到左边这个名字”它不是C语言里的“把值塞进内存地址”。第一个好用的技巧是链式赋值a b 0 # a和b都会指向整数0第二个是同时给多个变量赋值a, b 1, 2 a, b b, a # 交换变量一行搞定第二种写法尤其常用因为不用再引入第三个临时变量。Python之所以能这么写是因为赋值时会先计算右侧的所有表达式再同时做绑定a, b b, a实际上是先把右侧的值形成一个元组(b, a)然后从左到右解包绑定。3.2 增量赋值背后的可变与不可变增量赋值、-这些看起来就是简化写法但里面藏着一个和“可变对象/不可变对象”相关的坑。看这个例子s hello s world # 字符串不可变实际上是生成一个新字符串再绑定字符串、数字、元组都是不可变对象等价于“重新创建对象”。但列表是可变的的行为就微妙了l1 [1, 2] l2 l1 l1 [3] print(l2) # [1, 2, 3]l2跟着变了 m1 [1, 2] m2 m1 m1 m1 [3] print(m2) # [1, 2]m2没变为什么会这样因为l1 [3]是原地扩展列表l1和l2指向同一个列表对象改了大家都看得见而m1 m1 [3]是先创建一个新列表再把m1重新绑定过去m2仍然指向原来的旧列表。这是新手写代码非常容易踩的坑尤其在函数传参或者多个变量引用同一个列表时后果就是“莫名其妙改坏了别人的数据”。3.3 海象运算符:知道有这个东西就行Python 3.8 引入了海象运算符:作用是在表达式里直接赋值。它最典型的场景是避免重复计算# 传统写法 line input(请输入一行: ) while line: print(你输入了:, line) line input(请输入一行: ) # 海象写法 while (line : input(请输入一行: )): print(你输入了:, line)后一种在 while 条件里读输入同时把值存到line里省去了循环开头和结尾两处重复的input调用。入门阶段写不出来这种东西没关系但阅读代码时看到:要知道是什么意思。有一点要注意海象运算符的外层必须加括号直接写while line : input(...)在有些情况下会报语法错误规范写法是包一层括号。4. 比较运算符值比较、身份比较与链式比较4.1 比较的结果和“链式比较”的写法比较运算符返回的一定是布尔值True或False这个简单。值得说的是Python支持链式比较可以直接写1 x 10它等价于1 x and x 10。C语言里这么写会先算1 x得到一个真假值然后再拿真假值和10比较逻辑就完全不对了。Python的链式比较更贴近数学直觉我写判断范围的代码时特别喜欢用score 85 if 60 score 90: print(及格了但还能更好)4.2和is到底该用哪个这是Python入门阶段最经典的一个坑。简单说比较两个对象的值value是否相等。is比较两个变量是否指向同一个对象identity。a [1, 2, 3] b [1, 2, 3] print(a b) # True值一样 print(a is b) # False是两个不同的列表对象但初学者常常会遇到一个“诡异”现象x 256 y 256 print(x is y) # True m 257 n 257 print(m is n) # 可能是False也可能True取决于解释器这是因为Python会缓存小整数对象通常是-5到256所以256这个对象在进程中只有一个x和y都指向它。一旦超出这个范围解释器就可能创建新对象了。字符串也有类似的驻留机制但规则不那么透明。所以在日常开发中判断数字或字符串相等一律用只有判断“是不是None”才用is None。我在实际项目里见过有人用 None的写法能跑但不符合Python社区的约定习惯代码评审时也会被提出来。4.3 浮点数比较不要直接写浮点数比较是新手最容易困惑的地方为什么0.1 0.2 0.3是False因为十进制小数在计算机里用的是二进制存储0.1和0.2在二进制下是无限循环小数存进去就已经不是精确值了。正确做法是判断两个浮点数是否“足够接近”import math print(math.isclose(0.1 0.2, 0.3)) # True或者用绝对误差if abs((0.1 0.2) - 0.3) 1e-9: print(相等)这个细节在做金额计算、量化策略回测的时候特别重要。热搜词里有“python量化交易策略代码”那里面全是浮点数比较和算术运算如果不知道这个坑策略的判定逻辑可能悄悄出错。5. 逻辑运算符短路求值带来的“隐藏行为”5.1 真值表与短路求值逻辑运算符就三个and、or、not。它们的真值表很好记但真正的重点是“短路求值”short-circuit evaluationand左侧为假时右侧表达式根本不会执行直接返回左侧的值左侧为真时返回右侧的值。or左侧为真时右侧表达式不会执行直接返回左侧的值左侧为假时返回右侧的值。not先判断真假再返回相反布尔值。看这段代码def 返回假(): print(这行不会打印) return False print(True or 返回假()) # True右函数没被调用 print(False and 返回假()) # False右函数没被调用短路求值不仅仅是“快一步”它背后还承载了一种编程习惯把可能出错的检测放在前面后面再访问依赖它的数据。比如if key in dict and dict[key] value如果键不存在第二个表达式根本不会执行自然就不会抛KeyError。5.2 返回值不一定是布尔值新手最容易忽略的是and和or返回的是“操作数本身”不一定是True或False。看例子print(0 or hello) # hello print(1 and 2) # 2 print(0 and 2) # 0Python的规则是先判断左侧操作数的真值再根据结果返回某个操作数。这让or变成一个很常用的“默认值”写法username input(请输入用户名:) or 匿名用户如果用户直接回车input返回空字符串空字符串在布尔上下文里是假or就会返回右侧的默认值。这种写法干净、明确我写配置文件读取时经常用。5.3not的优先级容易被搞混not的优先级比and、or高但比比较运算符低。所以not 1 2等价于not (1 2)意思是“1不等于2”结果是True。如果你写(not 1) 2那就是先取反再比较结果就是False了。这类写法可读性极差我的建议是只要有混用就加括号。代码是写给人看的别省那几个字符。6. 位运算符冷门但实用的工具箱6.1 位运算符号一览位运算符按二进制位做运算按位与|按位或^按位异或~按位取反左移右移放个最小的例子表达式二进制过程结果5 3101 01115 | 3101 | 01175 ^ 3101 ^ 01165 1101左移一位变1010105 1101右移一位变1026.2 位运算的经典应用场景位运算看起来离业务开发很远但有三个场景非常常见第一个是权限管理。用二进制位表示权限标记读写执行各占一位读权限 4 # 0b100 写权限 2 # 0b010 执行权限 1 # 0b001 权限 读权限 | 写权限 # 6既有读又有写 if 权限 读权限: print(可以读)这种写法在大型系统里不少见|用来合并权限用来判断权限是否存在。第二个是快速乘除2的倍数。x 1等价于x * 2x 1等价于整除2。二进制里乘以2就是左移一位除2就是右移一位。不过日常代码我不推荐这么写可读性太差但算法竞赛和源码阅读里经常会看到。第三个是判断奇偶x 1奇数最低位是1偶数最低位是0。这个比x % 2快一点但同样只推荐在性能敏感的循环里用平时还是% 2清晰。6.3 位运算符和逻辑运算符别搞混是“按位与”and是“逻辑与”。最经典的坑print(2 1) # 0因为 10 01 00 print(2 and 1) # 1真值判断后返回右侧操作数新手写括号时容易把and写成结果代码不报错但逻辑和预期完全不一样。排查这类bug还挺费时间因为表面看语法没问题。我的经验是涉及条件判断一律用and/or只有明确要做二进制运算时才用/|。7. 运算符优先级一张表加一条建议7.1 常用的优先级顺序Python运算符优先级从高到低可以整理成下面这份精简表够用就行不用全背优先级运算符说明高()括号永远是最高级│**幂运算右结合│x-x~x正负号、按位取反│*///%乘除取余│-加减│位移│按位与│^按位异或│|按位或│!比较│not逻辑非│and逻辑与低or逻辑或拿一个容易写错的例子感受一下print(2 3 * 4 ** 2) # 4**2先算16再3*1648再加250 print( (2 3) * 4 ** 2 ) # 80加了括号明显更清晰7.2 链式比较是一种特殊规则1 2 2这种连续比较看起来很怪在Python里它等价于1 2 and 2 2结果是True。原因是Python的链式比较会逐段比较而且每个操作数只计算一次。如果你是想比较三个数是否相等应该写成a b and b c不要写成a b c虽然后者也能工作但语义上它做的是链式比较理解起来绕。7.3 实战建议优先级靠不住括号最可靠背优先级表既费劲又容易记错我的建议很简单凡是自己不能一秒看懂的表达式全部加括号。这不丢人反而显得专业。代码的首要读者是人其次才是机器。加了括号别人读你的代码时不用反复推算顺序维护成本立刻降下来。比如下面这个写法你要是不加括号读者得想半天# 推荐 if (a or b) and not c: print(ok) # 不推荐 if a or b and not c: print(ok)两段代码逻辑其实一样因为and优先级高于or但第二行至少让我这种老手都得停下来逐字推算一遍完全没有必要。8. 入门者最容易踩的坑与自查清单8.1 常见错误速查表把我在社区答疑和项目里高频看到的运算符相关报错整理成一张表你可以直接存下来对照场景错误表现原因与解决abc 123TypeError: can only concatenate str类型不匹配要手动转成字符串str(123)10 / 0ZeroDivisionError除数为0先判断再计算浮点比较0.1 0.2 0.3是False用math.isclose或误差比较a [1]b aa [2]原以为b不变结果b也变了对列表是原地扩展理解可变对象if a b判断两个条件期望and逻辑实际是位运算条件判断用and-7 % 3结果不对期望C语言逻辑结果是2Python余数与除数同号循环外误用is比较数字大整数is判断飘忽不定数字一律用8.2 能自查的动手练习题学运算符光看不练很容易飘。我推荐几个“练完就能有感觉”的小题目第一题输入一个整数判断它是奇数还是偶数输出对应结果。用% 2写。第二题输入一个三位数利用//和%分别取出百位、十位、个位再输出各位之和。第三题输入一个年份判断是否为闰年。闰年条件是能被4整除但不能被100整除或者能被400整除。这题会用到比较运算符和逻辑运算符的配合是一道很好的综合题。第四题用and、or写一个简化版输入校验输入年龄如果为空就默认18如果小于0就提示重新输入。第五题定义两个变量a60、b13分别打印a b、a | b、a ^ b、a 1、a 1的结果然后手动换算成二进制验证一遍位运算基本就入门了。还有那个“李白打酒”的逻辑题本质是反向用算术运算符拆解数字变化过程做这类题对//、%和循环的结合理解很有帮助算是有趣的进阶练习。8.3 我的一点个人心得最后聊点实际的体会。我在刚开始学Python的那阵子干过一件“笨事”打开交互式命令行把各种运算符组合挨个敲一遍一边敲一边猜结果再对照输出校正自己的理解。比如print(10 // 3)、print(-10 // 3)、print(10 % -3)、print(False or 1)、print(2 ** 3 ** 2)一行一行地试。这个过程看起来简单但它能帮你把“运算符的规则”从“我知道”变成“我有直觉”。很多后来写代码时不用想就能规避的坑都是那会儿试出来的。如果你只是想搞懂语法翻完这篇文章应该就够了但如果你想真正把Python用好建议你今天晚上就打开交互环境把上面每一个例子亲手验证一遍遇到结果和预期不一致的恭喜你那才是收获最大的地方。