课程笔记

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
lambdaLambda表达式
:=海象运算符,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)。浅复制会建立新的外层列表,但列表中的元素引用仍与原列表相同。

alias.py
a = [1, 2]
b = a
b.append(3)
print(a) # [1, 2, 3]

重新绑定

不可变对象上的“修改”会重新绑定变量:

rebind.py
s = "ab"
t = s
t += "c"
print(s) # ab
print(t) # abc

t += "c" 没有改变原来那个字符串,而是创建一个新字符串并让t指向它,因此s看到的仍是ab。

判断“修改”是否影响另一个变量时,先确认两个变量是否引用同一个对象,再确认对象是否允许原地改变。

内置函数与列表方法

Python的内置函数可在通常的执行环境中直接调用,无须先导入模块。例如len接收一个支持长度的对象并返回长度;sorted、range和zip分别用于排序、整数序列与按位置配对。

对象方法通过对象访问,例如列表的append和sort。调用前应确认函数或方法的参数、返回值以及是否修改已有对象;名称是否为内置函数,不能单独决定这些行为。

具体规则见排序、列表追加 、range和zip。

排序与列表追加

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的缺省值。这两步能把大部分“看着会、算错”的题挡下来。