Python Basics: Review
Note of ZJU Python Programming, 2024 Spring & Summer.
基础输入理解
编程题常见的写法:同一行输入两个整数,以空格隔开,例如 3 5
需要了解每一步的作用。
input()
读取一行输入,返回这行所有字符构成的字符串
input().split()
将字符串按空格分割,返回一个由多个子字符串构成的列表。如果存在连续的空格,会当作一个空格进行分割
split()
是从属于字符串类型的方法- 格式
string.split(separator, maxsplit)
separator
:分隔符,默认为(可连续的)空格。注意,split(" ")
和split()
是不同的,前者对于连续的空格会一个个进行分割,返回的列表长度会比较大maxsplit
:分割次数,默认为 -1,即分割所有。如果指定,则分割num-1
次,返回的列表长度最多为num
map(int, input().split())
将列表中的每个字符串转换为整数,返回一个由多个整数构成的迭代器
- 注意返回的是一个 map 的迭代器,而不是一个列表
- 如果想要得到列表,需要使用
list()
函数,例如list(map(int, input().split()))
- 注意,因为输入保证是整数才直接使用
int
,如果有浮点数的可能性,需要使用float
。如果也不一定是float
,需要考虑直接继续字符串进行处理 - 如果已经知道有多少个参数,例如已经知道只有 2 个数,可以使用
a, b = map(int, input().split())
直接赋值,但是如果数量不对应会出现问题
join
: split
的逆操作
在此同时引入 join
函数的使用方法。回顾 s.split(sep)
函数,是将 str 类型的 s 按照 str 类型的分隔符 sep 进行分割,得到一个 list 类型的分割结果,这个 list 的每个元素都是分割得到的字符串。即给定分隔符 sep 的情况下,split 输入一个长字符串,输出一个分割得到的字符串列表。
join
函数的使用方式为 sep.join(lst)
,sep 同样是一个 str 类型的分隔符,lst 常常是一个字符串列表,最终得到一个拼接得到的长字符串,按列表中的字符串顺序,两两之间用 sep 拼接。即给定分隔符 sep 的情况下,join 输入一个字符串列表,输出一个拼接得到的长字符串。
lst 也不一定就是字符串列表,如感兴趣详细可见官方文档:str.join - Python 3.12.3 documentaion
易错点
sep.join(lst)
的 lst 要求是一个字符串列表,务必保证 lst 的每个元素都是字符串,例如 lst 的元素如果是数值类型,那么就会报错。
基础输出理解
最简单的方式
直接输出a
, b
, c
三个量,并自动在两两之间添加空格,最后自动在末尾添加换行符 \n
了解 print
的参数
格式:print(object(s), sep=separator, end=end, file=file, flush=flush)
object(s)
:要打印的对象,可以是多个,用逗号隔开sep
:分隔符,默认为一个空格,所以原始的print
会自动在两两之间添加空格,可以将其改为""
或者其他字符
end
:结束符,默认为换行符\n
,可以将其改为""
(从而不换行)或者其他字符
file
,flush
:较少用,感兴趣可自主了解
格式化字符串 (Format String)
- 分为 3 种,分别是
%
格式化、str.format()
和f-string
%
是最原始的方法,str.format()
在 Python 2.5 之后出现,f-string
在 Python 3.6 之后出现- 个人认为简洁性逐渐提升,推荐编程时选择
str.format()
和f-string
这两种方式
需要注意的是,format
和 f-string
这两种方法在字符串中 {}
具有特殊含义,因此如果想要原样输出 {
或 }
,需要使用 {{
或 }}
进行转义。同理,%
格式化方法中,想要输出 %
需要使用 %%
进行转义。
format
方法和 f-string
方法都有 {X:Y}
的形式,其中
f-string
的X
是需要输出的值,Y
是格式化控制符format
的X
是需要输出的值的索引,Y
也是格式化控制符;X
也可以是一个标识符,但是需要在format
方法中指定X
的值- 在不需要格式化控制符时,
:Y
可以省略
格式化控制符可以控制左、右、居中对齐,以及宽度、精度等,具体可以参考官方文档:Format Specification Mini-Language - Python 3.12.3 documentaion。需要注意的是,当控制的整体宽度大于实际宽度时,几种格式化字符串默认的左右对齐方式有所不同,展示如下:
可以看到,对于字符串类型,%
默认右对齐,format
和 f-string
方法默认左对齐;而对于 int
和 float
类型,三种方法都默认右对齐。推荐编程时不要直接利用这种默认对齐特性,不管你偏好使用哪种格式化字符串方法,建议使用格式控制符 <
或 >
,或者使用 str.ljust(width)
或 str.rjust(width)
来进行对齐。
基础数据类型 (Data Type)
列举如下常见数据类型:
- 数值类型 (numeric type)
- 整数 (integer): int
- 浮点数 (floating point number): float
- 复数 : complex
- 布尔类型 (boolean type)
- 布尔 : bool
- 序列类型 (sequence type)
- 字符串 (string): str
- 列表 : list
- 元组 : tuple
- 映射类型 (mapping type)
- 字典 (dictionary): dict
- 集合类型 (set type)
- 集合 : set
Remark
事实上还有很多内置的数据类型,详见官方文档 Built-in Types - Python 3.12.3 Documentation。
根据官方文档 Numeric Types - int, float, complex - Python 3.12.3 Documentation,布尔类型事实上是 int 类型的子类型 (subtype),具有数值类型的一些特性。
可变 (mutable) 类型与不可变 (immutable) 类型
可变和不可变
参考官方文档 Built-in Types - Python 3.12.3 Documentation 和 Data Model - Python 3.12.3 Documentation。
值可以更改的对象 (object) 被称为可变 (mutable) 的,一旦创建值就不可更改的对象称为不可变 (immutable) 对象。
对象的可变性由其类型 (type) 决定,因此有可变类型与不可变类型。
- 不可变类型:int, float, complex, bool, str, tuple
- 可变类型:list, set, dict
可以发现,int float complex bool 这种仅由一个或两个数值简单地构成的数值 / 布尔类型是不可变的。由任意多元构成的 str 和 tuple 也都不可变,这是需要特别注意的,特别是要对比它们和可变的 list, set, dict 的区别。
Remark
- 元组和列表都是序列类型,元组因为不可变使用限制更多,但是其操作所需资源开销更少
- 对应地,我们认为集合和字典都是无序的,然而根据官方文档 OrderedDict objects - Python 3.12.3 documentaion,Python3.7 之后字典开始保证按照插入顺序,因此可能会在实际编程中发现字典具有一些有序结构的特性,但实际上仍为无序结构
- 字符串和列表在切片、拼接操作上表现类似,但是由于有可变与不可变的区别,因此不能像列表一样对特定的字符进行修改
int
, float
, complex
, bool
int
bin()
,oct()
,hex()
分别能将 int 转换为对应的二进制 / 八进制 / 十六进制表示,返回 str 类型int(value, base)
:将一个 str 按照 base 进制解释,转换为 int 类型后返回value
: 要求是 str 类型,要求能够按照 base 进制解释后转换为 intbase
: 要求是 int 类型,实际上常常有数值限制,如要求2<=base<=36
或base=0
各进制的字面量表示:
- 十进制 (decimal): 6, -3
- 二进制 (binary): 0b101(前缀 0b 或 0B)
- 八进制 (octal): 0o10(前缀 0o 或 0O)
- 十六进制 (hexadecimal): 0x1A(前缀 0x 或 0X)
float
存在精度问题,会存在误差;绝对值非常大或非常小都可能会溢出
complex
创建一个复数:
a.real
和a.imag
分别可以获得上面的复数的实部 (real component) 和虚部 (imaginary component)- 注意实部和虚部的数据类型都是
float
,所以在以上的例子中会有a.real=3.0
,a.imag=5.0
数值类型混合运算
int
, float
, complex
这三种数值类型之间的混合运算会自动进行类型转换,转换规则具体如下:
int
和float
混合运算,int
会被转化为float
int
和complex
混合运算,int
会被转化为complex
float
和complex
混合运算,float
会被转化为complex
这是因为 int
是比 float
更“窄” (narrow) 的数据类型,float
是比 complex
更“窄”的数据类型,因此在混合运算时较“窄”的数据类型会先转换为较“宽”的数据类型,然后再进行运算。这在后面的运算基础 - 基础数学运算符中会有所体现。
官方文档 Numeric Types - int, float, complex - Python 3.12.3 documentation 的原文说明:Python fully supports mixed arithmetic: when a binary arithmetic operator has operands of different numeric types, the operand with the “narrower” type is widened to that of the other, where integer is narrower than floating point, which is narrower than complex. A comparison between numbers of different types behaves as though the exact values of those numbers were being compared.
bool
- 只能取
True
或者False
两种值 (首字母大写) - Python3 中,
bool
是int
的子类(即可认为bool
是一种特殊的int
) ,True
和False
对应的int
中的值分别为 1 和 0 - 当
int
需要被强行解释为bool
时,当且仅当这个int
的值恰好等于 0 时才会被解释为False
,否则就会被解释为True
,例如
如果确定 a
是一个 int
,则当 a
的值是 1, 32873, -1 等所有非零值时,这行 print 都会被执行。只有 a
的值为 0 时,这行 print 才不会被执行。
Remark
bool
是 int
的子类,所以其与数值类型混合运算时也可以参考前面的混合运算准则。
基础运算
基础数学运算符
注意以下几点并思考为什么:
d1
,d2
,e2
,f2
都是 float,e1
和f1
都是 intf2
不严格为 0.32.1 - 2.0 == 0.1
的返回值为 False
运算符优先级 (precedence) 与结合性
同一复杂表达式中,运算符按优先级自高到底进行计算,互相会发生冲突的同优先级运算符按照结合性顺序计算。根据官方文档:Operator precedence - Python 3.12.3 documentaion,运算符优先级如下表所示:
运算符 | 描述 |
---|---|
(expressions...), [expressions...], {key: value...}, {expressions...} | 绑定 / 括号表达式 , 列表 , 字典 , 集合 |
x[index], x[index:index], x(arguments...), x.attribute | 下标 , 切片 , 函数调用 , 属性引用 |
await x | await 表达式(较少用) |
** | 幂运算 |
+x, -x, ~x | 正 , 负 , 按位取反 |
*, @, /, //, % | 乘 , 矩阵乘 , 除 , 整除 , 取余 |
+, - | 加 , 减 |
<<, >> | 左移 , 右移 |
& | 按位与 |
^ | 按位异或 |
| | 按位或 |
in, not in, is, is not, < , <= , > , >= , != , == |
比较运算符,包括成员资格测试和身份测试 |
not x | 布尔“非” |
and | 布尔“与” |
or | 布尔“或” |
if – else | 条件表达式 |
lambda | Lambda 表达式 |
:= | 海象运算符(较新的特性) |
关于结合性,除了幂运算 (exponentiation) 和条件表达式 (conditional expressions) 是从右到左的结合性之外,其他同级运算符都是从左到右的结合性。特别地,比较运算符在出现链接时会被特殊处理,例如 a < b < c
会被视为 a < b and b < c
进行计算,可以参考官方文档:Comparisons - Python 3.12.3 documentaion。
math
库常见运算
如何使用 math
库
首先了解如何导入并使用 math
库中的函数或常数。以导入其中的向下取整函数 floor
和圆周率常数 pi
为例,一种方法是直接导入 math
,这样就需要使用 math.x
来说明你想要调用的 x
是属于 math
这个库的。
另一种方法是直接指明你需要 math
库中的哪几个函数 / 常数,这样调用它们时就不需要加前缀 math.
但需要注意,这种指明的方式可能会造成“命名冲突”。比如说如果一个叫做 fool
的库里面也有一个和 math.floor
功能不同的 floor
的函数,然后你写下了
那么先导入的 math
库中的 floor
就会被 fool
库中的 floor
覆盖,如果你还下意识地认为 floor
是 math
库的 floor
就会出错。带库前缀去使用是一种解决方法,这里给出另外一种解决方法,即让 fool
库中的 floor
在这段代码中被重命名为 floor2
或其他名字:
如果你想使用 math
库中很多的函数 / 常量,然后你既苦于每次调用时都写前缀 math.
,也不想在 from math import
后面如数家珍地把每个都写出来,那么你可以使用一种具有风险的导入方式,把 math
库中所有的函数 / 常量都导入进来:
显而易见,导入了所有的函数 / 常量,将会导致和其他库“命名冲突”的概率大大增加,所以这种导入方式需要谨慎使用。
math
库常见函数 / 常量
并不依照考试列举,而只列举实际编程中较常用的
floor
: 向下取整ceil
: 向上取整abs
,fabs
: 取绝对值,前者返回int
,后者返回float
exp(x)
: 计算指数 \(e^x\)log(x, n)
,log10(x)
: 分别计算对数 \(\log_nx\) 和 \(\log_{10}x\),前者可以覆盖后者的功能sqrt(x)
: 计算平方根 (square root) \(\sqrt{x}\)sin
,cos
,tan
: 三角函数,输入要求为弧度制radians
: 将一个角度值转换成弧度值(等价于乘上 \(\frac{\pi}{180}\))e
,pi
: 自然底数 \(e\) 和圆周率 \(\pi\)
其实都是英文的缩写,对照英文就会觉得取名还是比较自然的。
特殊语句
赋值 (assignment) 语句
赋值语句是没有返回值的。与 C 语言赋值后返回所赋的值不同,Python 中的赋值语句是没有返回值的,因此 a = (b = c)
这种语句在语法上就不被允许。
但特殊的,Python 特别允许连等式的存在,即 a = b = c
。对于这种连等式,会先算出最右侧的表达式的值,然后从左到右依次赋值。对于这个例子,就是先计算出 c
的值,然后把这个值赋值给 a
,然后再把这个值赋值给 b
。
对于 a1, b1, c1 = a2, b2, c2
类型的赋值,则会先计算 a2, b2, c2 的值,然后以此赋值给 a1, b1, c1。
更多关于赋值语句的细节可以阅读官方文档:Assignment statements - Python 3.12.3 documentaion。
易错点
此处容易出错的点除了计算顺序之外,还有值传递和引用传递的问题。
考虑 a = b = []
,由于列表赋值是将其引用进行赋值,因此 a
和 b
得到的是同一个引用值,即这样会导致 a
和 b
是同一个列表,修改其中一个会导致另一个也改变。
循环语句中的 else 子句
参考官方文档:break and continue Statements, and else Clauses on Loops - Python 3.12.3 documentaion
for 循环和 while 循环都可以携带 else 子句,官方文档中对执行 else 子句的描述为:
In a
for
loop, the else clause is executed after the loop reaches its final iteration.In a
while
loop, it’s executed after the loop’s condition becomes false.In either kind of loop, the
else
clause is not executed if the loop was terminated by abreak
.
翻译一下就是,当循环不被 break
打断、正常地因为停止条件结束(for 循环的最后一次迭代结束、while 循环的条件变成 False)的时候,就会执行 else 子句,反之就不会执行。
Else 子句常常用于节约 flag 的使用。如果不使用 else 子句,往往需要用一个 flag 来表示是否因为 break
而结束循环,然后在循环结束后判断这个 flag 是否为 True,而使用 else 子句则可以直接在循环正常结束后执行一些操作。
数据类型进阶
对于较为进阶的几种常见数据类型,前文已经提及:
- 字符串、元组、列表是序列类型
- 字符串和元组是不可变类型
- 列表、集合和字典是可变类型
而与序列类型对应地,我们认为集合与字典是无序类型,当然字典实际上已经在较新版本的 Python 中出现了一些有序的特性,前文已经说过,但是我们仍然认为它是无序的。
列表 (list)
常见的列表操作如下所示:
L.append(x)
:在列表L
尾部追加一个新元素x
。几乎是最常用的列表操作。L.extend(L2)
:将另一个列表L2
扩充到列表L
的后面。也可以使用列表加法,等价于L = L + L2
。L.clear()
:移除列表L
的所有元素L.count(x)
计算列表L
中x
出现的次数L.copy()
:拷贝一份列表L
,拥有于L
不同的内存空间,等价于L[:]
。L.index(value[,start[,stop]])
:计算在指定范围内value
第一次出现的下标,如果有多个只会返回第一个元素的下标。这种写法的意思是,你可以采用如下三种方式调用这个函数:L.index(value)
:在整个列表中查找value
的下标L.index(value, start)
:在从下标start
开始到列表末尾的范围内查找value
的下标L.index(value, start, stop)
:在从下标start
开始到下标stop
的范围内查找value
的下标
L.insert(index, x)
:在下标index
的位置插入x
L.pop(index)
:返回并删除下标为index
的元素,如果不输入index
,默认返回并删除最后一个元素L.remove(value)
:删除值为value
的第一个元素,如果有多个只会删除第一个L.reverse()
:反转列表L
,等价于L = L[::-1]
L.sort()
:对列表元素排序,有参数key
和reverse
,分别表示排序的依据和是否逆序reverse
默认为False
,表示升序排序key
参数是很好用的一个参数,可以输入一个返回“依据”函数或者用于查找“依据”的列表
需要注意的是,index
和 remove
对于列表中存在多个匹配的元素时,会对第一个匹配的元素进行操作。
除了可以替代 extend
方法的列表加法之外,列表还可以进行一种数字乘法,即 L * n
,表示将列表 L
重复 n
次。需要注意的是,对于一维列表会进行数值的拷贝,拷贝生成的数值之间不共享内存;对于高维的列表会进行引用的拷贝,拷贝生成的各个引用对应的内存空间是同一个。
集合 (set)
创建空集合往往只能用 set()
,而不能用 {}
,因为 {}
会被解释为创建空字典。
集合中的元素具有如下特性:
- 可哈希性:元素必须是不可变对象,从而可以被哈希
- 无序性:元素没有顺序,因此不能通过下标访问
- 唯一性:集合中的元素不会重复
集合的增删操作:
S.add(x)
:增加元素x
到集合S
中S.discard(x)
:删除元素x
,如果x
不在集合S
中无效果S.remove(x)
:删除元素x
,如果x
不在集合S
中会报错S.clear()
:清空集合S
类似于列表,集合可以用 len(S)
来获得集合的长度,用 x (not) in S
来判断 x
是否在集合 S
中,用 max(S)
和 min(S)
来获得集合中的最大值和最小值,用 sum(S)
来获得集合中所有元素的和。
不仅列表和集合,字典也都有这些操作,只是统计的是字典的键。
考虑到读者可能对数学中集合的一些术语不太熟悉,在此折叠了一些对集合(真)子集 / 超集、交集、并集、差集、对称差集这些术语的解释。
集合术语说明
- 对于集合 \(A\) 和集合 \(B\),如果 \(A\) 中的所有元素都在 \(B\) 中,那么 \(A\) 是 \(B\) 的子集,\(B\) 是 \(A\) 的超集
- 如果 \(A\) 是 \(B\) 的子集,但不等于 \(B\)(也就是至少有 1 个 \(B\) 中的元素不属于 \(A\)
) ,那么 \(A\) 是 \(B\) 的真子集,\(B\) 是 \(A\) 的真超集 - \(A\cap B\): \(A\) 和 \(B\) 的交集,是一个新的集合,包含 \(A\) 和 \(B\) 中共有的元素
- \(A\cup B\): \(A\) 和 \(B\) 的并集,是一个新的集合,包含 \(A\) 和 \(B\) 中所有的元素
- \(A - B\): \(A\) 和 \(B\) 的差集,是一个新的集合,包含 \(A\) 中不在 \(B\) 中的元素
- \(A\) 和 \(B\) 的对称差集,是一个新的集合,包含 \(A\) 和 \(B\) 中不共有的元素,实际上是 \((A - B) \cup (B - A)\)
因此与集合之间运算相关的方法有:
s1.issubset(s2)
:判断s1
是否是s2
的子集s1.issuperset(s2)
:判断s1
是否是s2
的超集s1.union(s2)
,s1 | s2
:返回s1
和s2
的并集s1.intersection(s2)
,s1 & s2
:返回s1
和s2
的交集s1.difference(s2)
,s1 - s2
:返回s1
和s2
的差集s1.symmetric_difference(s2)
,s1 ^ s2
:返回s1
和s2
的对称差集
注意,集合的交集、并集、差集、对称差集都不会改变原集合的值,而是返回一个新的集合。
以及关系运算符:
s1 == s2
:判断两个集合是否相等,以及s1 != s2
s1 < s2
:判断s1
是否是s2
的真子集s1 > s2
:判断s1
是否是s2
的真超集s1 <= s2
:判断s1
是否是s2
的子集s1 >= s2
:判断s1
是否是s2
的超集
字典 (dict)
字典的创建可以使用 {}
或者 dict()
,删除有del
, pop()
(用法类似 list.pop
clear()
。
与集合相似地,字典的键必须可哈希(即必须是不可变对象
如果不加说明,直接使用len
, max
, min
, sum
, in
这些操作,则默认对字典的键进行操作,而不是包含键和值的元组。字典自身常用的方法有:
D.keys()
:返回字典D
的所有键D.values()
:返回字典D
的所有值D.items()
:返回字典D
的所有键值对,返回的是一个元组的列表D.get(key[,default])
:返回字典D
中键key
对应的值,如果key
不存在,返回default
,默认为None
D.pop(key[,default])
:返回并删除字典D
中键key
对应的值,如果key
不存在,返回default
,默认为None
函数
函数参数 (parameter/argument)
更多内容可以参考官方文档:More on Defining Functions - Python 3.12.3 documentation
在定义函数时,函数的参数在中文常称为“形参”,对应英文 parameter 或 formal parameter;而在实际传参时,函数的参数在中文常称为“实参”,对应英文的 argument 或 actual parameter。但是实际上这两个术语区分并不是非常清晰,下文中有的地方就直接称参数。
定义函数时,部分形参可以指定默认值,这些参数在实际调用函数时可以不传入,而使用默认值。这种参数就称为可选参数 (optional arguments),可选参数必须放在必选参数 (mandatory argument) 的后面,否则会报错。
可选参数默认值的陷阱
可选参数的默认值只会被构造一次。如果默认值是一个可变对象,那么每次调用函数时,如果没有传入这个参数,那么这个参数会是同一个可变对象的引用,例如就会有
根据传参方式的不同,实参分为两种:位置参数 (positional argument) 和关键字参数 (keyword argument)。位置参数是指在调用函数时,按照函数定义的参数顺序传入的参数,而关键字参数是指在调用函数时,通过 key=value
的形式传入的参数。
当一个函数的最后一个形参的形式为 **name
(name
可以替换成别的参数名称)时,它会接收一个字典,包含除与形式参数对应的参数之外的所有关键字参数,例如
这可以与形式为 *name
的形参组合,该形式参数接收包含形式参数列表之外的位置参数的元组。注意,*name
必须出现在 **name
之前。组合使用时,有
解包 (unpacking) 参数列表
参考官方文档 Unpacking Argument Lists - Python 3.12.3 documentaion
这种情况出现于,希望传递给函数的所有位置参数都已经在列表、元组或者集合之中,那么就可以利用 *
运算符解包出这些实参,然后传递给函数。
如果希望以关键字参数的形式传递参数,那么可以使用 **
运算符解包出字典中的键值对。
Lambda 表达式
Lambda 表达式是创建匿名函数的一种方法,例如 lambda a, b: a + b
对应的函数即为
Lambda 表达式的简单应用例子
此处的 Lambda 表达式的输入为一个元组,输出为这个元素的下标为 1 的元素,将其作为元组列表 pairs
的 sort
方法的 key
参数,以及就是以每个元组的第二个元素作为其排序的依据。
如果不使用 Lambda 表达式创建匿名函数,就需要显式地创建函数,即:
文件基础
文件操作流程
- 打开文件:
file = open(filename, mode)
- 处理文件:
- 读操作,例如
file.read()
,file.readline()
,file.readlines()
- 写操作,例如
file.write()
,file.writelines()
- 读操作,例如
- 关闭文件:
file.close()
文件打开模式
模式 | 含义 |
---|---|
'r' |
只读模式,文件必须存在 |
'w' |
写入模式,文件不存在则创建,存在则覆盖 |
'x' |
独占创建模式,文件不存在则创建,存在则报错 |
'a' |
追加模式,文件不存在则创建,存在则追加 |
'b' |
二进制模式,与其他模式结合使用,例如'rb' , 'wb' |
't' |
文本模式,与其他模式结合使用,例如'rt' , 'wt' (默认方式,如果不加 t 或者 b 默认为 t ) |
'+' |
读写模式,与 r/w/x/a 结合使用,例如'r+' , 'w+' ,同时允许读和写 |