4.1 Python程序设计基础
阅读入口
求值顺序
一个表达式里出现多个运算符时,需要一套规则决定先算哪一部分。优先级决定不同级别运算符的先后,优先级高的先结合;结合性决定同一优先级的运算符如何分组。括号可以强制改变默认顺序,写在括号里的部分总是先算。
下表按优先级从高到低排列,同一行的运算符优先级相同:
| 运算符 | 描述 | 示例或备注 |
|---|---|---|
(...)、[...]、{...} | 括号、列表、字典与集合的显示 | 优先级最高 |
x[i]、x[i:j]、x.attr、f(...) | 下标、切片、属性、函数调用 | |
** | 幂运算 | 2**3**2等于2**(3**2) = 512,右结合 |
~、+x、-x | 按位取反、正负号 | 一元运算符 |
*、/、//、% | 乘、除、整除、取模 | // 向下取整 |
+、- | 加、减 | |
<<、>> | 移位 | |
& | 按位与 | |
^ | 按位异或 | |
| | 按位或 | |
in、not in、is、is not、<、<=、>、>=、!=、== | 比较运算符 | 优先级相同,支持链式比较a < b < c |
not | 逻辑非 | |
and | 逻辑与 | 短路:False and x不计算x |
or | 逻辑或 | 短路:True or x不计算x |
if–else | 条件表达式 | x if c else y |
lambda | Lambda表达式 | |
:= | 海象运算符,Python 3.8+ | 优先级最低 |
这张表只适用于Python。C使用不同的运算符集合与优先级,参见C运算符优先级。
表里只列Python的运算符,本节所有表达式题都按这张表判断。教材的例题常写成数学式子的样子,作答时仍按这里的顺序求值。
切片的边界与返回值
步长决定方向和间隔
step为正:从start向右取,停在stop之前,每隔step个位置取一个;step为负:从start向左取,停在stop之后;step不能为0,否则抛出ValueError。
越界只影响结果,不报错
切片会把越界的端点截到序列边界内,因此结果可能是原序列的一部分,也可能是空序列,但不会抛异常。直接写单个下标a[100] 或a[-100] 才是索引访问,越界会抛IndexError。
切片返回新序列
切片得到的是新的序列对象,不是原序列的别名。对列表切片后修改切片,不会改动原列表;但切片只复制最外层,切片里的元素仍与原列表指向同一批对象。
切片是否返回新容器与其中的元素是否被复制是两个层次。容器可变性和别名行为另见Python对象可变性。
切片和索引的行为差别都在这一段里。本节做题时先看写法中有没有冒号,再判断越界的后果。
对象可变性
常见类型
| 类型 | 可变性 | 备注 |
|---|---|---|
int、float、bool、complex | 不可变 | 数值运算产生新对象 |
str | 不可变 | 字符串操作产生新字符串 |
tuple | 不可变 | 元组保存的引用不能替换,引用目标的内容仍可能改变 |
list | 可变 | 可以原地追加、删除或替换元素 |
dict | 可变 | 键必须是可哈希对象 |
set | 可变 | 元素必须是可哈希对象 |
frozenset | 不可变 | 不可变集合,可以作为字典的键 |
“不可变”描述对象自身能否原地改变,不表示对象引用的所有子对象都不可变。例如元组t = ([1],) 中的列表仍可执行t[0].append(2),因为这里修改的是列表;而t[0] = [2] 试图替换元组保存的引用,会抛出TypeError。
别名与复制
赋值b = a创建的是别名,不会复制对象。需要独立列表时,可以使用浅复制b = a[:] 或b = list(a)。浅复制会建立新的外层列表,但列表中的元素引用仍与原列表相同。
a = [1, 2]b = ab.append(3)print(a) # [1, 2, 3]重新绑定
不可变对象上的“修改”会重新绑定变量:
s = "ab"t = st += "c"print(s) # abprint(t) # abct += "c" 没有改变原来那个字符串,而是创建一个新字符串并让t指向它,因此s看到的仍是ab。
判断“修改”是否影响另一个变量时,先确认两个变量是否引用同一个对象,再确认对象是否允许原地改变。
内置函数与列表方法
排序与列表追加
sorted(iterable) 返回新的排序列表,不会原地修改输入;列表的sort() 原地排序并返回None。两者都可按需要传入排序键或反向排序选项,但适用对象不同:sorted接受可迭代对象,sort是列表方法。
因此a = a.sort() 会把None赋给a,而原列表已经被排序。需要保留原列表时可使用sorted(a)。
a.append(b) 将b整体作为一个元素添加到列表末尾;a.extend(b) 遍历b并将其中元素逐个添加。append可以接收任意对象,extend需要可迭代对象。两者都原地修改列表并返回None。
整数序列与配对
range(start, stop, step) 表示从start开始、按step递增或递减的整数序列,不包含stop。省略start时起点为0;省略step时步长为1;步长不能为零。
range对象不是列表,它以紧凑的方式表示序列,需要列表时可以调用list(range(...))。步长的方向与起终点不匹配时,结果为空。
zip从每个输入依次取一个元素组成元组,返回迭代器。默认情况下,一个输入耗尽时迭代停止,较长输入的其余元素不会出现在结果中;需要列表时可用list消费迭代器。
sort、append、extend三组最容易混,区别都落在“返回新对象、原地修改还是返回None”上。
学习安排
本节汇总考试中容易混淆、需要记住的Python细节。四块内容分别回答四类问题:
- 表达式先算哪一部分:优先级、结合方向与短路求值;
- 序列的边界怎么算:切片的缺省值、负索引与越界;
- 输出长什么样:对齐、补零、精度与类型;
- 一次调用做了什么:返回新对象、原地修改,还是返回
None。
四块之间没有推导依赖,可以按自己的薄弱项调整顺序。建议先读知识点里的表格,再合上表手算几个表达式,最后用题集核对。切片和返回值的题最容易在选择题里失分,留到最后复查。
课程约定
- 按Python 3的规则作答;
:=是Python 3.8才加入的写法,旧版本不能使用。 - 求表达式结果时按Python的实际求值顺序,不按数学书写习惯。例如
-3**2先算幂再取负。 - 题目说“切片越界”时按截断或空序列作答;只有单个下标越界才按抛出
IndexError处理。 - 本节只讨论Python。C的表达式规则见4.2 C程序设计基础;两种语言的优先级差异见 运算符优先级。
易错点
- 幂运算右结合,
2**3**2不是(2**3)**2;一元负号的优先级低于**。 not a == b先比较后取非;and先于or,不要按从左到右的顺序平均理解。a = a.sort()会把None赋给a;原地排序与返回新列表要分清。append加入的是一个元素,extend展开的是可迭代对象。- 切片得到新列表,
b = a得到的才是别名。
复习要点
做优先级题时先在草稿上补括号,再按括号求值;切片题先判断step的正负,再套start、stop的缺省值。这两步能把大部分“看着会、算错”的题挡下来。