← 返回知识卡片总目录

DS1号 · 自学教材系列 | 编程

第 6 章 运算符与表达式

数据存好了,下一步是「算」。这一章讲计算机里的三类运算——算术、比较、逻辑,以及那个让无数新手头疼的问题:一堆运算符挤在一起时,先算谁?学完这一章,你写的代码将从「存数据」进化到「真正处理数据」。

🎯 本章学习目标

  1. 理解「表达式」是什么:由值和运算符组成、能算出一个结果的式子。
  2. 掌握算术、比较、逻辑三类运算符及其结果。
  3. 理解「优先级」和「结合性」,能预判复杂表达式先算哪部分。
  4. 理解除法的两种结果(/ 与 //)和取余 % 的意义。
  5. 理解并演示「短路求值」,说出它为什么能保护计算(检查在前、计算在后)。
  6. 把复合表达式画成「运算树」,用树解释优先级和结合性的意义。

6.1表达式:把数据「算」出新结果

上一章我们「存」数据,这一章我们「算」数据。算数据的写法,叫 表达式(expression)。

一个表达式,就是由值(数据)和运算符(运算符号)组合起来、最终能算出一个结果的式子。

5 + 3        # 表达式,算出 8
x * 2        # 表达式,算出 x 的两倍
age >= 18    # 表达式,算出 True 或 False

表达式无处不在:x = 5 + 3 里,右边的 5 + 3 就是表达式;print(x * 2) 里,x * 2 就是表达式。它的核心特征就一条:能「求值」——算出一个具体的结果。

核心思想 表达式 = 值 + 运算符,能算出结果。计算机会先算出表达式的值,再用这个值。比如 x = 5 + 3,是先算 5 + 3 得到 8,再把 8 存进 x——这和第 4 章「= 先算右边」完全一致。

运算符(operator)是表达式里的「动词」,它告诉计算机对数据做什么操作。根据「算什么」和「算出什么」,运算符分成三大类,下面逐一讲。

表达式可以套表达式:像洋葱一样剥开

看一个稍长的算式:2 * 3 + 4 * (1 + 2)。它不是「一整坨」,而是层层嵌套的表达式——最里层 1 + 2 先算出 3,这个 3 立刻变成外层 4 * 3 的一个操作数,算出 12;再外层 2 * 3 算出 6;最外层 6 + 12 收尾得 18。整个过程像剥洋葱:里层的值一层层「浮出」,供外层使用。子表达式算出的值,就是外层表达式的原料——这是「先求值再使用」在复杂表达式里的形态。

顺带立一个重要的概念边界:语句与表达式。表达式是「名词性」的——它代表一个值,能被求值;语句是「动词性」的——它是一个动作、一条指令。在 x = 5 + 3 里,整行是一条赋值语句(存入这个动作),而右边的 5 + 3 是表达式(被求出 8 的那个部分)。区分的试金石:「它能不能放进 print()?」print(5 + 3) 合法——表达式有值可打印;print(x = 5) 在 Python 里行不通——赋值语句是动作,没有「值」。往后的代码里,你会不断见到「大语句套小表达式」的构造,把这个边界刻在心里,读代码就不再是一团乱麻。

📐 例题 1 表达式的「值」与「类型」双面体检

先预测、再运行核对:5 + 3、5 / 2、5 > 3 三个表达式的值各是什么?类型又各是什么?

思路 每个表达式算出来都同时带两样东西:一个值、一个类型。用 print() 看值,用 type() 验类型——第 4 章学过的 type() 在这里正好派上用场。

解 实测输出:

print(5 + 3)      # 8
print(type(5 + 3))  # <class 'int'>

print(5 / 2)        # 2.5
print(type(5 / 2))  # <class 'float'>

print(5 > 3)        # True
print(type(5 > 3))  # <class 'bool'>

三组结果连成一条主线:运算符决定的不只是「算出什么值」,还有「结果是什么类型」——+ 在整数上得整数(8);/ 哪怕除得尽也永远得小数(2.5,float);比较运算符永远得布尔值(True)。这条主线直接贯通第 5 章:类型是说明书,而现在你看到,每个表达式算完,产出的新值也自带类型标签。

答:值分别为 8、2.5、True;类型分别为 int、float、bool。 ⚠ 易错点:最容易想错的是第二组——不少新手以为「5 除 2 得 2 余 1,所以结果是 2」。Python 的 / 是真除法,永远得小数;想要整数结果,用的是下一节的 //。记住:每个表达式都自带类型,看到运算符先想「它产出什么类型」。

放到工程现场,「表达式」就是计算书上的每一个算式:截面积、压差、当量载荷……区别只在——纸上的算式算完就扔,程序里的表达式随取随算、永不疲劳,数据一换,结果立刻跟着更新。也因此工程师验算的习惯在编程里原样成立:拿不准的式子,丢进 Python 里跑一遍就知道(第 3 章装好的交互环境正是为此准备的「电子计算书」)。

把「剥洋葱」落成一张逐步求值表,以 2 * 3 + 4 * (1 + 2) 为例:

步骤 1:1 + 2   →  3     先算最里层括号
步骤 2:4 * 3   →  12    子表达式算完,值「浮出」顶替原位
步骤 3:2 * 3   →  6     同级运算,从左往右
步骤 4:6 + 12  →  18    顶层收尾,整个表达式的最终值

注意一个关键习惯:算出的值会立刻「顶替」原来的子表达式——步骤 1 得到的 3,直接站进 4 * 3 的操作数位;步骤 2 的 12 站进加法右边。整个求值过程就是「子表达式不断被它的值替换,直到只剩一个值」——计算机算式的机械原理,和你手动验算的思路一模一样。

表达式算出的值有三个去处,往后的代码里天天见:存给变量(x = 5 + 3,右侧就是表达式)、直接打印(print(2 * 3 + 4))、喂给判断(下一章的 if 后面跟的,正是一个算出 True/False 的表达式)。三种用法共用同一条规则——先求值,再使用;区别只在于「值算出来之后去哪」。认出代码里的表达式、能拆出它的求值顺序,是从「会读一行代码」到「会读一段代码」的分水岭。

6.2算术运算符:加减乘除与取余

算术运算符做的是「数学计算」,输入数字,输出数字:

Python 算术运算符
运算符含义例子结果
+加5 + 38
-减5 - 32
*乘5 * 315
/除(得小数)5 / 22.5
//整除(舍去小数)5 // 22
%取余(模)5 % 21
**乘方2 ** 38

有两个运算符需要特别理解:

// 整除 vs / 除法

/ 永远给你精确的小数结果(5/2 = 2.5);// 则把结果向下取整,丢掉小数部分(5//2 = 2)。想得到「完整的几份」,用整除。

% 取余:小学除法的「余数」

5 除以 2,商 2 余 1,那个「余 1」就是 5 % 2 的结果。取余非常有用,最常见的两个场景:

📖 底层原理:算术在 CPU 里怎么算? 你写的 5 + 3,最终会变成 CPU 里加法器的一次运算——把两个数的二进制按位相加,处理进位。而乘法,本质上是一连串「移位 + 加法」,除法则是「移位 + 比较 + 减法」。所以最「贵」(耗时)的运算是乘除,最「便宜」的是加减。了解这个,你以后优化程序性能时会有感觉。

先给 // 补一条重要脚注:它叫「整除」,但准确说是向下取整——结果取「不大于真商的最大整数」。正数时无害:7//2=3;负数时就反直觉了:-7 // 2 得 -4(因为 −3.5 向下取整是 −4,不是 −3)。配套的余数也跟着变:-7 % 2 得 1(不是 −1)。看似别扭,背后却有一条铁的恒等式:a == (a // b) * b + a % b 永远成立(127 = 10×12+7 ✓;−7 = −4×2+1 ✓)。Python 里余数的符号永远跟除数一致,这条恒等式就是保证——商和余数配合,不多不少正好还原被除数。

📐 例题 2 装箱与余数:一批备件的两种问法

库房有 127 件备件,每箱装 12 件。(1) 全部装箱,能装满几箱?(2) 装完后剩几件?(3) 写成 Python 表达式并验证;(4) 追问:怎样用 % 判断一件备件的编号是奇数还是偶数?

思路 「能装满几箱」问的是整除(//);「剩几件」问的是余数(%);奇偶判断的标准姿势是「除 2 看余数」——偶数除 2 余 0,奇数余 1。

解 表达式与实测输出:

total = 127
box = 12

print(total // box)   # 10:整箱数
print(total % box)    # 7:装不下、剩下的件数

# 验证恒等式:商×除数 + 余数 == 被除数
print(total // box * box + total % box == total)   # True ✓

# 奇偶判断
n = 46
print(n % 2)    # 0:余 0,偶数
m = 127
print(m % 2)    # 1:余 1,奇数

(1) 127÷12 商 10 余 7;(2) 10 箱装满、余 7 件;(3) 恒等式验证通过:10×12+7 = 127 ✓;(4) 奇偶判断全靠 % 2——余 0 为偶、余 1 为奇,127 是奇数 ✓。顺带体会 % 的另一副面孔:「分组剩下的」就是「按周期轮到谁」——12 件一组不断重复,余数就是「尾巴」;周期性的事情(每组第几件、每轮第几个)都归 % 管。

答:(1) 10 箱;(2) 7 件;(3) total // box = 10、total % box = 7,恒等式成立;(4) n % 2 余 0 为偶数、余 1 为奇数。 ⚠ 易错点:别把「箱数」写成 total / box——那会得 10.583…(float),箱数必须是整数,用 //。什么时候用 /、什么时候用 //,先问自己「结果该不该带小数」。

给 % 再扫一次雷:它不是百分号。生活里「30%」读「百分之三十」,Python 里根本没有百分数这种写法——想表达百分之三十,写 0.3 或 30 / 100。% 在 Python 里只干一件事:取余。新手最常犯的两个错——把 rate = 5% 当合法语句(会当场报错:SyntaxError),把 x % 2 误读成「x 的百分之二」——都是把生活的百分号带进了代码。记住:代码里的 %,念「余」不念「百分比」。

取余在工程上的用处比想象中大得多:奇偶校验位(数据位按 2 取余算校验值)、分组轮检(设备第 k 次轮到谁,用 k % 组数)、时钟换算(25 点是几点?25 % 12 得 1,凌晨 1 点)。凡是周期性重复的事情,几乎都藏着一个取余——「转了一圈回到哪」,正是 % 的本义。

除法家族的分工:/、//、round() 各管一段

「算除法」看起来是一件事,实际上分成三个需求:要精确结果(真除法 /)、要整数份(整除 //)、要四舍五入(round())。Python 为三个需求各配了一把钥匙:

print(5 / 2)        # 2.5 :真除法——精确值,结果永远是 float
print(5 // 2)       # 2   :整除——向下取整,丢掉小数
print(round(5 / 2)) # 2   :舍入——2.5 向偶数靠(第 5 章讲过的「银行家舍入」)
print(round(7 / 2)) # 4   :3.5 向偶数靠,得 4

三把钥匙混用是新手事故高发区:算「能装几箱」误用 / 得 10.58 箱(箱数哪有小数);算「平均值」误用 // 把 4.7 平均成 4,白白丢了 0.7。选钥匙的口诀就一句:问自己「结果该带小数吗」——该带小数用 /;要整数份用 //;要四舍五入用 round()。另外记得 / 永远出 float(哪怕 4/2 也得 2.0),拿它当整数用还得再转一道——工程上多数场合直接 // 更省心。

取余还有个工程上极常用的姿势——周期采样。传感器每秒读一次数,程序不必每次都处理:每逢序号 k % 100 == 0 的读数才落盘一次,等于把连续的数据流按每 100 个一组分组,只取每组的「排头」。同理,仪表巡检每轮 % N 决定查哪台,定时任务按 % 60 对齐分钟……「按周期取点」的背后全是取余——第 8 章写循环时这个套路还会正式登场。

6.3比较运算符:得出「是/否」

比较运算符做的是「比较判断」,输入两个值,输出一个 布尔值(True 或 False)。它们回答的永远是「是/否」式的问题。

Python 比较运算符
运算符含义例子结果
==等于5 == 5True
!=不等于5 != 3True
>大于5 > 3True
<小于5 < 3False
>=大于等于5 >= 5True
<=小于等于3 <= 5True

⚠️ 最常见的坑 = 和 == 千万别混!= 是赋值(把值存进去),== 才是比较是否相等(问「一样吗」)。x = 5 是「让 x 等于 5」,x == 5 是「x 等于 5 吗?」——一个在「做」,一个在「问」。

比较运算的结果(True/False)是下一章「条件分支」的燃料。没有比较,程序就不知道「该走哪条路」。

🧠 一句话记忆 = 是「赋值」,== 是「相等吗」。一个在做事,一个在提问。

比较运算符的本质值得点破:它是一个问句,而 bool 是它的答案。「压强 > 上限吗?」「长度 == 标称值吗?」——每个比较表达式都在向机器提一个只有是/否两种答案的问题,机器如实回答 True 或 False。这直接接上第 5 章的布尔:True/False 不是凭空存在的摆设,它们诞生于比较,将在第 7 章成为 if 分支的「燃料」——没有比较,程序永远做不了决定。

📐 例题 3 == 的类型细节:三个「像不像」的实测

先预测、再运行核对:(1) 5 == 5.0;(2) "5" == 5;(3) True == 1。(4) 总结 == 的比较规则。

思路 == 比较的是「值」,但不同类型的值怎么比?分三组实验:整数对浮点、字符串对整数、布尔对整数。第 5 章讲过 True 就是 1——这条线索在第三组实验里应该兑现。

解 实测输出:

print(5 == 5.0)    # True:值相等就相等,类型可以不同
print("5" == 5)     # False:文字 "5" 和数字 5 不是一个世界的东西
print(True == 1)    # True:布尔是整数家族特例,True 就是 1

三组结果拼出规则:(1) 数值之间跨 int/float 比较的是数的大小——5 和 5.0 相等;(2) 字符串与数字之间没有可比性,直接判 False("5" 里装的是文字,长得像不代表是);(3) 布尔参与比较时按第 5 章的设定当整数用。合起来一句话:== 先看两边的值能不能放进同一种「度量衡」,能就比大小,不能就 False。

答:(1) True;(2) False;(3) True;(4) 数值跨类型比值,字符串与数字永不相等,布尔按整数参与比较。 ⚠ 易错点:第 (2) 组是数据交换场景的高发事故——从文件、仪表读进来的数据都是字符串(第 5 章讲过),拿去和数字 == 会悄悄得 False、不报任何错,比直接崩溃更隐蔽。比较之前先确认类型,必要时先转换(int(x)、float(x))。

再认识一位 == 的孪生兄弟——is。== 问的是「值相等吗」,is 问的是「是不是同一个对象吗」——后者正是第 4 章 id() 的知识落地处:is 相当于比较两个名字挂的是不是同一块内存。数字 5 和 5.0 值相等(== True)但显然不是同一个对象(is False)。is 的正式戏份在第 10 章(列表共享)才开演,今天先认个脸,记住口诀:== 问值,is 问身份。

既然比较是「问句」,工程上最常见的问法就是「读数在不在正常区间内」。Python 有个贴心的写法——链式比较,允许把「下限 ≤ 读数 ≤ 上限」写成跟数学作业一模一样的样子:

upper = 8.0
lower = 6.0
reading = 7.3

print(reading > upper)               # False:没超上限
print(reading < lower)               # False:没低于下限
print(lower <= reading <= upper)   # True:链式比较,数学式直接写

in_range = lower <= reading <= upper
print(in_range)                       # True:结论存进布尔变量,随时可用

最后一步值得注意:比较的结果(布尔值)可以存进变量——in_range 今后就是一个「结论」,可以被打印、被引用、被下一步判断。这是表达式「先求值再使用」的又一次现身:比较算出 True/False,True/False 又成为新变量的值。数据和结论在程序里流动起来,这就是「处理数据」最朴素的形态。

6.4逻辑运算符:组合多个判断

现实中的判断往往不止一条:「年龄满 18 且 有驾照,才能开车」。这里「且」把两个判断组合了起来。逻辑运算符就是干这个的:

Python 逻辑运算符
运算符含义例子结果
and与(两者都真才真)True and FalseFalse
or或(有一个真就真)True or FalseTrue
not非(取反)not TrueFalse

逻辑运算符的规则,用一张「真值表」最清楚:

逻辑运算的真值表 列出 and、or、not 三种逻辑运算在输入真假组合下的输出。 and(与) or(或) not(非) T and T = T T and F = F F and T = F F and F = F 只有「都真」才真 T or T = T T or F = T F or T = T F or F = F 只有「都假」才假 not T = F not F = T 取反
图 6-1三种逻辑运算的真值表:and 要「都真」,or 要「有一个真」,not 是「取反」。

一个实际例子,把比较和逻辑串起来:

age = 20
has_license = True
can_drive = (age >= 18) and has_license   # True and True = True

底层原理 逻辑运算在硬件层面,就是第 1 章讲的「与门、或门、非门」——三个最基础的逻辑门。你写的 and、or、not,最终都会落到 CPU 里的这几个门电路上。所以「逻辑」不是抽象的数学概念,它是真实存在的电路。

and/or 的隐藏技能:短路求值

and 和 or 求值时有个隐藏机制,叫短路求值(short-circuit evaluation):Python 从左到右逐个看操作数,一旦结果已经注定,就不再往后看。对 and:左边是 False,全式必为 False,右边根本不求值;对 or:左边是 True,全式必为 True,右边同样被跳过。「已经能定案,何必审下一个」——像一场能提前结束的评审。

这个机制最好的证据,是 1/0——正常情况它必报错(除数不能为零)。但把它放进被短路的右侧,它就根本没被执行,连报错的机会都没有。这既解释了现象,也暴露出工程价值:短路求值让你能安排「安全检查的顺序」——先确认「有没有数据」,再去看「数据是多少」,顺序本身就是保护。

📐 例题 4 短路求值实验:让 1/0 证明「右边没被执行」

先预测、再运行核对:(1) False and 1 / 0 会报错吗?(2) True or 1 / 0 呢?(3) False or 1 / 0 呢?(4) 设计一个「用短路求值保护计算」的真实写法。

思路 用 1/0 当「探测器」:如果某段代码被执行了,它必然报错;程序没报错,说明它被短路跳过了。三种组合正好覆盖 and/or 的两条短路规则。

解 实测输出:

print(False and 1 / 0)   # False:and 左边已定案,右边 1/0 没被执行
print(True or 1 / 0)     # True:or 左边已定案,右边 1/0 没被执行
# print(False or 1 / 0) # 这行若执行:ZeroDivisionError: division by zero

# 工程写法:先检查「有没有」,再检查「是多少」
count = 0
safe = count > 0 and 100 / count > 5
print(safe)              # False:count>0 不成立,除法没被碰到,不报错

(1)(2) 都不报错——右边被跳过;(3) 立刻报错——or 左边是 False 定不了案,必须审到右边,1/0 就爆了。(4) 的写法是工程代码的日常:把「数据存在吗」放在 and 左边当守门员,守门员一拦(False),右边的除法自然不会执行。如果写反了顺序,count 为 0 时程序直接崩溃——同一行代码,条件顺序对调,稳定性天壤之别。

答:(1) False,不报错;(2) True,不报错;(3) ZeroDivisionError;(4) count > 0 and 100 / count > 5——检查在前、计算在后。 ⚠ 易错点:短路求值不是「性能优化的小聪明」,而是「安全顺序」的结构性保障;把可能出错的操作放在右边,用左边的检查替它「挡枪」,是 Python 工程代码的标准姿势。

最后看一个进阶小知识,看不懂可以跳过、混个脸熟即可:Python 的 and/or 返回的其实是「定案的那个操作数」,并不总是 True/False。0 and 'x' 得 0(0 为假,定案的就是它)、3 or 'x' 得 3(3 为真,定案的就是它)。在需要布尔值的地方(比如 if 条件),Python 会按第 5 章的「空与零为假」规则自动解读——所以平时感觉不到差别。今天记结论就够:and/or 返回「决定结果的那一方」,深究留给后面章节。

逻辑运算在工程现场的名字叫联锁(interlock):设备要动作,得同时满足若干条件,缺一不可;报警要响起,若干条件满足其一即可。写成代码就是 and / or 的直接翻译:

pressure_ok = True      # 液压压力正常
door_locked = False      # 舱门已锁定

can_actuate = pressure_ok and door_locked
print(can_actuate)       # False:门没锁——任一条件不满足,坚决不动作

need_alarm = pressure_ok or door_locked
print(need_alarm)        # True:至少一个条件成立

and 对应「全部满足才放行」的串联联锁,or 对应「满足其一即触发」的并联报警——第 4 章说过的「与门、或门、非门」,在设计图纸上就是这样的逻辑电路。今天的 and/or/not,本质是同一套布尔逻辑在两处现身:电路里叫门电路,代码里叫逻辑运算符。这套「与或非」的数学由 19 世纪的布尔(George Boole)建立——所以真值只有 True 和 False 的这个类型,就用他的名字命名。

6.5优先级与结合性:先算谁

当一个表达式里有好几种运算符挤在一起,计算机按什么顺序算?答案和小学数学的「先乘除后加减」一样,叫 优先级(precedence)。

2 + 3 * 4    # 先算 3*4=12,再算 2+12=14,而不是 5*4=20

Python 的优先级从高到低,大致是:

运算符优先级(从高到低,简化版)
优先级运算符说明
最高( )括号,永远最先算
高**乘方
中* / // %乘除、整除、取余
低+ -加减
更低== != > < >= <=比较
再低not逻辑非
再低and逻辑与
最低or逻辑或

还有一个概念:结合性(associativity),即「同级运算符谁先算」。比如 10 - 3 - 2,是从左往右算((10-3)-2 = 5),还是从右往左?Python 的算术运算符大多是从左到右结合。

但别急着背表。真正实用的建议只有一条:

核心思想 记不住优先级,就用括号。括号里的先算,永远没错。与其赌「我记的优先级对不对」,不如 (2 + 3) * 4 这样写清楚——既不会出错,读代码的人也一眼看懂你的意图。

🧠 一句话记忆 有疑问就加括号。括号优先级最高,括号一加,歧义全消。

优先级表不用死记,因为它的本质只有一句话:每个表达式都是一棵「运算树」——运算符是枝干、值是叶子;优先级决定的是「谁先和谁结合成子树」。以 2 + 3 * 4 为例:乘法优先级高,3 和 4 先「抱团」成子树算出 12;加法只能等子树出结果再收尾,6+12 一线贯穿最终得 14。把表达式画成树,先算哪步一目了然——树的下层先算、上层后算,顶层运算是最后一步。

表达式是一棵运算树 2 + 3 * 4 的树形结构:乘法节点先结合 3 和 4,加法节点最后结合 2 与乘法结果。 2 + 3 * 4 是一棵「运算树」 + 2 * 3 4 ① 叶子:原始数据 ② 高优先级先结合 3*4 先算出 12 ③ 顶层最后收尾 2+12 = 14 下层先算、上层后算——优先级的本质,就是「树的组装规则」
图 6-2表达式是一棵运算树:优先级决定 3 和 4 先结合(下层先算),加法在顶层最后收尾。

结合性回答的是另一个问题:同优先级的运算符,谁跟谁先抱团。绝大多数运算符「左结合」——从左往右依次算:10 - 3 - 2 是 (10−3)−2 = 5,先算左边的减法。唯一显眼的例外是幂运算 **,它「右结合」:2 ** 3 ** 2 是 2**(3**2) = 2⁹ = 512(不是 (2³)² = 64),从右往左算。为什么幂要特立独行?数学上「2 的 3 的 2 次方」习惯上就是这么读的——Python 顺着数学惯例走。

📐 例题 5 优先级实战:四个复合表达式的预测与验证

先按优先级表手算预测,再运行核对:(1) 2 + 3 * 4 ** 2 % 5;(2) not True or False and True;(3) 2 ** 3 ** 2;(4) 10 - 3 - 2。

思路 先把每题的「运算顺序」写出来,再逐步求值:优先级排序(幂 > 乘除余 > 加减 > 比较 > not > and > or),同级看结合性。第 (2) 题要小心 not 和 and、or 的先后。

解 手算推演:
(1) 4**2 = 16(幂最先)→ 3*16 = 48(乘)→ 48%5 = 3(余)→ 2+3 = 5(加最后)。
(2) not True = False(not 优先)→ False and True = False(and 先于 or)→ False or False = False。
(3) ** 右结合:3**2 = 9 → 2**9 = 512。
(4) 同级左结合:(10−3)−2 = 5。机器核对:

print(2 + 3 * 4 ** 2 % 5)          # 5:幂→乘→余→加
print(not True or False and True)  # False:not→and→or
print(2 ** 3 ** 2)                 # 512:右结合,2 的 9 次方
print(10 - 3 - 2)                  # 5:左结合,(10-3)-2

四题手算与机器完全一致。特别回味 (2):not 只管紧跟在它后面的那个 True(优先级在 or、and 之上),and 在 or 之前算——整句等价于 (not True) or (False and True)。如果当初写成 not (True or False and True),结果就完全不同了。一层括号改变一棵树,这就是优先级的分量。

答:5、False、512、5。 ⚠ 易错点:(3) 是经典陷阱——把 2**3**2 当 (2**3)**2 算成 64,忘了 ** 是右结合;(1) 里 48 % 5 得 3 也常被误算成「3 乘什么再余」的顺序。复合表达式没有把握时,加括号永远是最诚实的做法。

工程现场的真实做法也印证这一点:重要的算式宁可多一层括号,也要让读的人不用查表。计算书里 (P1 - P2) * A 永远直接写成带括号的式子——不是怕 Python 算错,是怕「三个月后的自己」和「审阅的同事」读错。括号是写给人的注释,零成本高回报。

括号改变的是树的形状。把 2 + 3 * 4 和 (2 + 3) * 4 的树并排画出来,差别一目了然:

2 + 3 * 4 的树:            (2 + 3) * 4 的树:

        +                          *
      /   \                      /   \
     2     *                    +     4
         / \                  / \
        3   4                 2   3

  右子树 3*4 先算 → 2+12 = 14    左子树 2+3 先算 → 5*4 = 20

两棵树的叶子完全相同(2、3、4),形状却不同:括号把 + 提到了上层、把 * 压到了下层——运算顺序就是树的层次。这张图也回答了「为什么 2+3*4 不写成 2+(3*4)」:乘法优先级本来就高,树自然长成那样,括号可以省;但 (2+3)*4 的括号一个都不能省,省了树就变形、结果就变味。

6.6字符串的「加法」与「乘法」

最后补一个有趣的细节:+ 和 * 用在字符串上时,有完全不同的含义——这叫「运算符重载」。

"Hello" + " World"   # 拼接 → "Hello World"
"Ha" * 3              # 重复 → "HaHaHa"

同一个 + 号,用在数字上是「相加」,用在字符串上是「拼接」。为什么?因为 Python 会根据两边数据的类型,决定 + 该干什么。数字加数字=求和,字符串加字符串=拼接。

要点 运算符的行为取决于操作数的类型。但要注意:"5" + 3 会报错,因为一边是字符串一边是数字,Python 不知道该「拼接」还是「相加」。这正是第 5 章说的「类型决定操作」——类型不匹配,操作就无意义。

「同一个 + 号,两种行为」——这件事值得用第 5 章的知识再点透一层。运算符的行为从来不由运算符自己决定,而由操作数的类型决定:两个 int 相加,做算术;两个 str 相加,做拼接。第 4 章说过「类型是内存的说明书」,现在看到说明书的第一条「使用效果」:类型不仅决定数据怎么存,还决定运算符怎么干。这不是 + 号任性,而是一门叫「运算符重载」的语言机制——同一个名字的运算符,在不同类型上有各自的实现,就像同一个「打」字,打电话、打报告、打地基各有各的意思。

📐 例题 6 字符串运算与报错修复:从 3.5 行代码看类型边界

(1) 生成 30 个字符宽的分隔线;(2) 拼一行设备读数:「温度23.5℃」(温度值 23.5 是数字);(3) "5" + 3 为什么报错?读报错信息、修复它。

思路 (1) 字符串乘法=重复;(2) 数字拼进字符串前必须先过 str() 这座桥(第 5 章类型转换);(3) 先复现报错,读懂报错在抱怨什么,再对症下药。

解 三步代码与实测:

print("-" * 30)            # ------------------------------:30 字符分隔线

temp = 23.5
print("温度" + str(temp) + "℃")   # 温度23.5℃:str() 先把 23.5 变成文字

# print("5" + 3)           # 若执行:TypeError: can only concatenate
#                           # str (not "int") to str
print("5" + str(3))         # 53:修好——3 先转文字再拼接

(1) "-" * 30 用「乘法」实现重复,正好 30 字符;(2) 数字 23.5 想进字符串队伍,必须先在 str() 桥上「换装」;(3) 报错信息逐词读:can only concatenate str (not "int") to str——「只能把 str 拼给 str,不能拼 int」,Python 把病根说得明明白白。修复就一行:str(3) 过桥。

答:(1) "-" * 30;(2) "温度" + str(23.5) + "℃";(3) "5" + 3 报 TypeError,因为 str 只能与 str 拼接;修复:"5" + str(3) 得 "53"。 ⚠ 易错点:读报错别慌,报错信息就是医生写的诊断书——本例的 TypeError 连「该转谁」都指明了。另注意字符串乘法只认整数:"ab" * 3 合法,"ab" * 3.0 会报错(can't multiply sequence by non-int of type 'float')——重复次数必须是个整数。

拼字符串在真实程序里的戏份极重:生成报告行、组装提示语、给日志加时间戳——只要「往文字里嵌数字」,就绕不开 str() 这座桥。所以第 5 章那句「Python 从不偷偷转类型」在这里结出了果实:"5" + 3 当场报错,不是 Python 不聪明,是它把「类型混用」的隐患在最早的时刻、最响地炸给你看——错得早,好过错得晚。顺带预告:Python 还有更顺手的拼字符串工具(f-string),第 8 章讲字符串时正式介绍,今天这招「str() 过桥」已经够用。

拼接在真实程序里通常一拼就是一整段报告行,套路是「文字块 + str(数字) + 文字块」交替铺开,中间用分隔线隔开:

part = "阀组件"
count = 12

print("-" * 30)
print(part + ":入库 " + str(count) + " 件")
print("合计入库 " + str(count) + " 件")
print("-" * 30)

输出三行报告、两条分隔线——分隔线正是字符串乘法的经典用武之地。往后你写任何「带数据的提示语」「带读数的日志行」,都是这个模式的变奏。

顺带认识比较运算符在字符串上的行为:按编码逐字符比较。"apple" < "banana" 是 True——两个词先比第一个字符,a 和 b 的编号分别是 97 和 98(第 5 章的 ord() 查过),97 < 98,分出胜负就停。第 5 章说「字符在机器里是编号」,这条规则正是那句话的又一次兑现:字符串比大小,本质是拿编号在比。同样,"apple" == "apple" 为 True、== "Apple" 为 False(大小写编号不同)——字符串的比较、判断,用的都是同一套编号逻辑。

本章小结

  1. 表达式 = 值 + 运算符,能算出一个结果;计算机会先求值再使用。
  2. 算术运算符做计算:注意 /(得小数)、//(整除)、%(取余)的区别。
  3. 比较运算符输出 True/False;= 是赋值,== 才是相等判断。
  4. 逻辑运算符 and/or/not 组合判断,底层对应「与门、或门、非门」。
  5. 运算顺序由优先级和结合性决定;记不住就加括号,永不出错。
  6. 每个表达式都自带「值 + 类型」:+ 出 int、/ 出 float、比较出 bool——看运算符先想产出什么类型。
  7. // 是向下取整(−7//2 = −4),余数符号跟除数一致,恒等式 a == (a//b)*b + a%b 永远成立;% 念「余」不念「百分比」。
  8. and/or 有短路求值:结果已定就不再求值——把检查放左边、计算放右边,是安全写法(False and 1/0 不报错)。
  9. 优先级的本质是「运算树」:高优先级先结合成下层子树,顶层最后算;** 右结合(2**3**2=512),其余多左结合。
  10. 运算行为由操作数类型决定(运算符重载):"5"+3 报错、str() 过桥即修——Python 拒绝偷偷转类型。

读完本章,你应该能……

思考题

1. 10 % 3 和 10 // 3 各是多少?用「分糖果」来解释它们的含义。

提示:10 颗糖分给 3 人,每人 3 颗(整除 = 3),剩下 1 颗(取余 = 1)。

2. 为什么 x = 5 和 x == 5 不能混用?后果是什么?

提示:= 是赋值(改 x 的值),== 是判断(返回 True/False)。用错了程序行为完全不同。

3. 计算 (2 + 3) * 4 和 2 + 3 * 4,结果分别是多少?为什么?

提示:前者 20(括号先算),后者 14(乘法优先级高于加法)。

4. 为什么 "5" + 3 会报错,而 "5" + "3" 不会?

提示:类型不匹配。字符串加数字无意义;两个字符串才能拼接成 "53"。

5. -7 // 2 和 -7 % 2 各得多少?为什么「向下取整」对负数会反直觉?

提示:−4 和 1。// 取「不大于真商的最大整数」:−7/2 = −3.5,不大于 −3.5 的最大整数是 −4(不是 −3)。余数随之变成 1,保证恒等式成立:(−4)×2+1 = −7 ✓。正数时两种理解重合,负数时才见分晓。

6. False and 1 / 0 会不会报错?False or 1 / 0 呢?为什么一个没事、一个崩溃?

提示:前者不报错(False 已定案,右边 1/0 根本没被执行——短路求值);后者报 ZeroDivisionError(False 定不了 or 的案,必须求值到右边)。短路求值因此成了安全写法的基础:检查放左边,计算放右边。

7. 2 ** 3 ** 2 等于多少?它跟 (2 ** 3) ** 2 差在哪?

提示:512。** 是右结合,先算 3**2 = 9,再算 2**9 = 512;(2**3)**2 则是 8² = 64。同优先级的结合方向(结合性)决定「谁先抱团」,幂运算右结合是沿用数学惯例的特例。

8. 为什么 10 // 3 * 3 + 10 % 3 恰好还原成 10?这个恒等式在工程上有什么用?

提示:10//3 = 3、10%3 = 1,3×3+1 = 10。商和余数配合能不多不少还原被除数:a == (a//b)*b + a%b。调试时用它反查「装箱装满几组、剩几件」对不对账;负数时它也成立(−7 = (−4)×2+1),是 // 和 % 配套设计的证明。