Python Notes

Python 基本语法 1

对象

在 Python 中一切都是对象

变量

Python 中直接赋值即可创建任意类型的变量。Python 采用基于值的内存管理模式。赋值语句的执行过程是:首先把等号右侧表达式的值计算出来,然后在内存中寻找一个位置把值存放进去,最后创建变量并指向这个内存地址(给内存地址贴标签)。Python 中的变量并不直接存储值,而是存储值的内存地址。因此,变量类型可以随时改变。

变量名必须以字母或下划线开头。以下划线开头的变量具有特殊含义

内置函数 type(x) 用于查看变量类型,或使用 isinstance(x, int) 来测试变量是否为指定类型。

数字

  • 整数 - int、 实数 - float、复数 - complex

由于精度的问题,实数运算可能会有一定的误差。因此,应尽量避免在实数之间直接进行相等性比较,而是应该以两者之差的绝对值是否足够小作为两个实数是否相等的依据。

>>> 0.4 - 0.1
0.30000000000000004
>>> 0.4 - 0.1 == 0.3
False
>>> abs(0.4 - 0.1 - 0.3) < 1e-6  # or math.isclose(a, b)
True

Python 3.6 支持在数字中间插入单个下划线来提高数字的可读性 1_000_000

  • 分数 / 高精度实数
>>> from fractions import Fraction
>>> from decimal import Decimal
>>> x = Fraction(3, 5)  # 分数
>>> x.numerator  # 查看分子
3
>>> x.denominator  # 查看分母
5
>>> y = Decimal(1/9)  # 高精度实数

字符串

Python 使用单引号、双引号、三单引号、三双引号作为定界符(delimiter)来表示字符串,并且不同的定界符之间可以互相嵌套。字符串之间拼接可以用 +''.join()

str 类型字符串与 bytes 类型字节串之间编码与解码

>>> 'Hello World'.encode('utf-8')
b'Hello World'
>>> b'Hello World'.decode('utf-8')
'Hello World'

列表、元组、字典、集合

比较项 list tuple dict set
定界符 [ ] ( ) { } { }
是否可变
是否有序
是否支持下标 是(使用整数序号作为下标) 同 list 是(使用“键”作为下标)
元素是否可重复 “值”可重复,“键”不可
元素查找速度 非常慢 很慢 非常快 非常快
新增和删除元素速度 尾部操作快,其他位置慢 不允许

如果元组中只有一个元素,后面的逗号不能省略。例如,(3,)

运算符

易忘运算符 功能
/ 除法
// 求整商,如果操作数中有实数
% 求余数
**
or
and
not
is 测试是否为同一个对象(如果两个对象是同一个,两者具有相同的内存地址)

== 用于判断两个对象的值是否相等,is 用于判断两个对象是否是同一个。

关键字

import keyword
print(keyword.kwlist)  # 查看关键字

内置函数

Python的内置函数封装在模块 __builtins__ 中,用 C 语言实现,可通过内置函数 dir(obj) 查看所有内置函数和内置对象:>>> dir(__builtins__)

类型转换与类型判断

bin(x)oct(x)hex(x) 分别用于将整数转换为二进制、八进制和十六进制,其参数必须为整数。

>>> bin(10)
'0b1010'  # 0b开头表示二进制数,同理0o、0x开头表示八、十六进制数

int(x, base)float(x)complex(real, imag) 分别用于将其他类型的数据转换为整数、实数和复数。int('0b1010', 2) 的第二个参数 base 用于说明 x 的进制。

str(obj)bytes(x)list(x)tuple(x)dict(x)set(x)frozenset(x) 分别用于将其他类型数据转换为字符串、字节串、列表、元组、字典、可变集合和不可变集合,或创建空对象。

计算

abs(x) 用于返回 x 的绝对值或复数 x 的模。

max(x, key=None)min(x, key=None)sum(x, start=0) 用于计算包含有限个元素的可迭代对象中所有元素的最大值、最小值以及所有元素之和。key 参数用于指定比较大小的规则,start 参数用于指定求和的初始值(指定 start 时返回 start + sum(x))。

round(x, ndigits) 用于对 x 进行四舍五入,ndigits 参数用于指定返回值的小数位数,默认返回整数。

基本输入与输出

input() 用于接收用户的键盘输入,输入的内容以字符串类型对待。

print(value, sep='', end='\n', file=sys.stdout) 用于将内容输出到控制台或指定文件中。sep 参数用于指定数据之间的分隔符,默认为空格。end 参数用于指定输出后的结束字符串,默认为换行符。file 参数用于指定输出位置,默认为控制台。

with open('output.txt', 'w') as f:
	print("Hello, world!", file=f)  # 将内容写入到 output.txt 文件中

排序

sorted(iterable, key=None, reverse=False) 对可迭代对象进行排序并返回新列表。key 参数用于指定排序规则。reverse 参数用于指定升序或降序,默认为 False 表示升序。

reversed(iterable) 对可迭代对象进行翻转并返回可迭代的 reversed 对象。

具有惰性求值特性的函数

惰性求值(Lazy Evaluation):仅在结果真正需要被计算时才进行计算。

range(start, stop, step) 返回包含左闭右开区间 [start, stop) 内以 step 为步长的整数。start 默认为 0,step 默认为 1。常用于控制循环的次数。

enumerate(iterable, start) 用于枚举可迭代对象中的元素,返回可迭代的 enumerate 对象,其中每个元素都是包含索引和值的元组。start 参数用于指定 index 的起始值。

>>> for index, value in enumerate(range(10, 15)):
	print((index, value), end=' ')
(0, 10) (1, 11) (2, 12) (3, 13) (4, 14)

zip(*iterables) 用于把多个可迭代对象中的元素压缩到一起,返回一个可迭代的 zip 对象。其中每个元素都是包含原来对象的对应位置上元素的元组,最终结果中元素的个数取决于可迭代对象中最短的那个。

>>> list(zip('abcd', [1, 2, 3]))
[('a', 1), ('b', 2), ('c', 3)]

map(func, *iterables) 把一个函数 func 依次映射到可迭代对象的每个元素上,返回一个可迭代的 map 对象,其中每个元素是原对象经过函数 func 处理后的结果。

>>> list(map(str, range(5)))
['0', '1', '2', '3', '4']

filter(func, iterable) 将一个单参数函数 func 作用到一个可迭代对象上,返回该对象中使得该函数返回值等价于 True 的元素所组成的 filter 对象。若指定函数为 None,则返回对象中等价于 True 的元素。

reduce()

reduce(func, seq, initial=None) 将双参数函数 func 以迭代的方式从左到右依次应用到序列 seq 中的每个元素,并把中间计算结果作为下一次计算的第一个操作数,最终返回单个值作为结果。initial 参数用于指定一个初始值。该函数从 functools 库中导入,并可以结合 operator 库中提供的多种运算使用。

>>> from functools import reduce
>>> from operator import add, sub, mul, truediv, floordiv, mod, pow
>>> seq = list(range(1, 10))
>>> reduce(add, seq)
45

len()

len(obj) 返回对象 obj 包含的元素的个数,适用于 list、tuple、dict、set、str 以及 range 对象,不适用于具有惰性求值特性的对象。

all(), any()

all(iterable) 如果可迭代对象 iterable 中所有元素 x 的 bool(x) 都为 True,则返回 True。对于空的可迭代对象也返回 True。

any(iterable) 只要可迭代对象 iterable 中存在元素 x 使得 bool(x) 为 True,则返回 True。对于空的可迭代对象返回 False。

help()

help(obj) 返回对象 obj 的帮助信息。

用于查看函数的使用帮助

序列结构

Python 中常用的序列结构包括列表、元组、字符串、字典、集合等,其中列表、元组、字符串等有序序列以及 range 对象均支持双向索引,即第一个元素索引为 0,第二个元素索引为 1,以此类推;若使用负数作为索引,则最后一个元素索引为 -1,倒数第二个元素索引为 -2,以此类推。

列表

列表是包含若干元素的有序连续内存空间。当列表增加或删除元素时,列表对象自动进行内存的扩展或收缩,从而保证相邻元素之间没有缝隙。列表应尽量从列表尾部进行元素的追加与删除操作。

列表的创建与删除

使用“=”直接将一个列表赋值给变量即可创建列表对象。也可以使用 list() 把可迭代对象转换为列表。需要注意的是,把字典转换为列表时默认是将字典的”键“转换为列表,若想把字典的元素转换为列表,需要使用字典的 dict.items() 方法,也可以使用 values() 将字典的值转换为列表。当一个列表不再使用时,可以使用 del 命令删除。

列表常用方法

  • append()insert()extend()

append(x) 用于向列表尾部追加一个元素。insert(index, x) 用于向列表任意指定位置插入一个元素。extend(x) 用于将另一个可迭代对象中的所有元素追加到当前列表的尾部。这三个方法都不影响列表对象的内存地址(id(obj) 函数可以查看对象 obj 的内存地址)。

  • pop()remove()clear()

pop([index]) 用于删除并返回列表指定位置上的元素,不指定参数时默认为 -1,即最后一个元素,对空列表调用会抛出异常。remove(x) 用于删除列表中首个与指定值相等的元素。若不存在则抛出异常。clear() 用于清空列表中的所有元素,保留列表对象。这三个方法同样都不影响列表对象的内存地址。同时,还可以使用 del 命令通过索引删除列表中指定位置的元素。

  • count()index()

count(x) 用于返回列表中指定元素出现的次数,若不存在则返回 0。index(x) 返回指定元素在列表中首次出现的索引,若不存在则抛出异常。

  • sort()reverse()

sort(key=None, reverse=False) 用于对列表中的元素进行排序,key 参数用于指定排序规则,reverse 参数默认为 False 表示升序,为 True 则表示降序。reverse() 用于将列表中的所有元素进行翻转,即首尾交换。

sort()reverse() 对列表的排序都是原地排序,即列表的内存地址不变,但原列表中数据的顺序全部丢失,变为排序后的顺序。若不想丢失原来的顺序,则可以使用内置函数 sorted()reversed(),分别返回新列表和 reversed 对象。

  • copy()

copy() 返回列表的浅复制。

浅复制是指生成一个新的列表并把原列表中所有元素的引用都复制到新列表中。若原列表中只包含不可变类型的数据则一般没有问题。但是,若原列表中包含可变类型的数据,由于浅复制只是复制元素的引用,于是修改任何一个都会影响到另外一个。

>>> x = [1, 2, [3, 4]]
>>> y = x.copy()  # 浅复制
>>> y
[1, 2, [3, 4]]
>>> y[2].append(5)  # 对可变的数据进行修改
>>> y
[1, 2, [3, 4, 5]]
>>> x
[1, 2, [3, 4, 5]]  # 由于浅复制,原列表也会改变
>>> y.append(6)  # 对可变的数据进行修改
>>> y
[1, 2, [3, 4, 5], 6]
>>> x
[1, 2, [3, 4, 5]]  # 不可变的数据不受影响

列表的切片操作和标准库 copy 中的 copy() 函数都是返回浅复制。若想使用深复制,则可以使用标准库 copy 中的 deepcopy() 函数。所谓深复制,是指对原列表中的元素进行递归,把所有的值都复制到新列表中,不再是复制引用,从而新列表和原列表之间是相互独立的。

列表支持的运算符

列表支持加法运算符 +,但该操作不属于原地操作,而是返回一个新列表,并且效率较低。更推荐使用复合赋值运算符 += 实现列表追加元素,并且属于原地操作,与 extend() 方法一样高效。

列表也支持乘法运算符 *,用于列表和整数相乘,表示序列重复,返回新列表。*=也可用于列表元素重复,与 += 一样属于原地操作。

成员测试运算符 in 可用于测试列表中是否包含某个元素,查询时间随着列表长度的增加而线性增加。但是同样的操作对于集合而言则是常数级的。

>>> x = [1, 2, 3]
>>> y = [4, 5]
>>> id(x)
2832802183680
>>> id(y)
2832798807040
>>> x += y
>>> x
[1, 2, 3, 4, 5]
>>> id(x)  # 内存地址不变
2832802183680
>>> x *= 2
>>> x
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
>>> id(x)  # 内存地址不变
2832802183680
>>> 5 in x
True

列表推导式

列表推导式(List Comprehensions)提供了一种创建列表的简洁方法,使用方括号作为定界符。其语法形式为:

[expression for variable1 in sequence1 if condition1
			for variable2 in sequence2 if condition2
			for variableN in sequenceN if conditionN]

列表推导式在逻辑上等价于一个循环语句,但形式上更加简洁。

aList = [x * x for x in range(10)]
# 等价于
aList = []
for x in range(10):
	aList.append(x * x)

列表推导式常见的应用:

  • 实现嵌套列表的平铺
>>> vec = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
# 也可以使用 itertools 库中的 chain() 函数
>>> from itertools import chain
>>> list(chain(*vec))  # *vec 使用序列解包,将 vec 中的所有子列表作为单独的参数传递给 chain()
[1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 过滤不符合条件的元素
>>> import os
>>> [filename for filename in os.listdir('E:\\') if filename.endswith((.png', '.jpg', '.gif'))]
['logo.png']
  • 同时遍历多个列表
>>> [(x, y) for x in [1, 2, 3] if x > 1 for y in [3, 4, 5] if x != y]
[(2, 3), (2, 4), (2, 5), (3, 4), (3, 5)]
  • 实现矩阵转置
>>> matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
# 也可以使用 zip() 和 list() 来实现
>>> list(map(list, zip(*matrix)))
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

切片

切片不仅仅适用于列表,还适用于元组、字符串、range 对象,但是列表的切片操作具有最强大的功能。不仅可以使用切片来截取列表中的任何部分得到一个新列表,还可以来修改和删除列表中的部分元素,甚至可以为列表增加元素。

切片的语法:[start:stop:step]

start 表示切片开始的索引,默认为 0;stop 表示切片结束的索引(不包含),默认为列表的长度;step 表示切片的步长,默认为 1。当这三个为默认值时可以省略,省略步长的同时还可以省略第二个冒号。当 step 为负整数时,表示反向切片,即顺序变为从右向左,此时 start 应大于 stop

  • 使用切片获取列表的部分元素

切片可以返回列表中部分元素组成的新列表。与使用索引作为下标访问列表的元素不同,切片不会因为下标越界而抛出异常,而是简单地在列表尾部截断或返回一个空列表。

>>> x = [1, 2, 3, 4, 5]
>>> x[::]  # 返回包含原列表中所有元素的新列表
[1, 2, 3, 4, 5]
>>> x[:]  # step 为 1 时,可以省略第二个冒号
[1, 2, 3, 4, 5]
>>> x[::-1]  # 返回包含原列表中所有元素的逆序列表
[5, 4, 3, 2, 1]
>>> x[::2]  # 改变步长,隔一个取一个
[1, 3, 5]
>>> x[1:3]  # 指定切片开始和结束的索引,左闭右开区间
[2, 3]
>>> x[3:1:-1]  # 步长为负整数时,从右侧(索引值大)向左侧(索引值小)挨个选取
[4, 3]
>>> x[0:100]  # 切片结束索引大于列表长度,直接从尾部截断,不抛出异常
[1, 2, 3, 4, 5]
>>> x[100:]  # 切片开始索引大于列表长度,返回空列表
[]
  • 使用切片为列表添加元素

使用切片为列表添加元素不影响列表对象的内存地址,属于原地操作。添加元素时需保证 startstop 的值相等,通过为原列表的切片赋值另一个列表来完成。

>>> x = [1, 2, 3, 4, 5]  # 延续上一节
>>> x[:0] = [6]  # 相当于 x[0:0],start 默认为 0 可以省略
>>> x
[6, 1, 2, 3, 4, 5]
>>> x[1:1] = ['1']
>>> x
[6, '1', 1, 2, 3, 4, 5]
>>> x[len(x):] = ['last']  # 同理,stop 默认为列表长度可以省略
>>> x
[6, '1', 1, 2, 3, 4, 5, 'last']
>>> x[1:1] = ['dreamless', 'drugs']  # 也可以一次性添加多个元素
>>> x
[6, 'dreamless', 'drugs', '1', 1, 2, 3, 4, 5, 'last']
  • 使用切片替换和修改列表中的元素
>>> x = [6, 'dreamless', 'drugs', '1', 1, 2, 3, 4, 5, 'last']  # 延续上一节
>>> x[3:6] = ['a', 'b', 'c']  # 替换列表元素,等号两边列表的长度相等
>>> x
[6, 'dreamless', 'drugs', 'a', 'b', 'c', 3, 4, 5, 'last']
>>> x[9:] = [6, 7, 8]  # 切片是连续的,等号两边列表的长度可以不相等
>>> x
[6, 'dreamless', 'drugs', 'a', 'b', 'c', 3, 4, 5, 6, 7, 8]
>>> x[::2] = [1]  # 切片不连续(step = 2),等号两边列表的长度必须相等
ValueError: attempt to assign sequence of size 1 to extended slice of size 6
>>> x[::2] = range(6)
>>> x
[0, 'dreamless', 1, 'a', 2, 'c', 3, 4, 4, 6, 5, 8]
  • 使用切片删除列表中的元素

可以通过赋值空列表的方法来删除元素,也可以使用 del 命令与切片结合来删除元素,并且切片可以不连续。

>>> x = [0, 'dreamless', 1, 'a', 2, 'c', 3, 4, 4, 6, 5, 8]  # 延续上一节
>>> x[:3] = []  # 删除列表中前三个元素
>>> x
['a', 2, 'c', 3, 4, 4, 6, 5, 8]
>>> del x[5:]  # 切片连续时 del 生效
>>> x
['a', 2, 'c', 3, 4]
>>> x[::2] = []  # 切片不连续时赋值空列表的方法失效
ValueError: attempt to assign sequence of size 0 to extended slice of size 3
>>> del x[::2]  # 切片不连续时 del 生效
>>> x
[2, 3]
  • 切片得到的是列表的浅复制
>>> x = [1, 3, 5]
>>> y = x[::]  # 切片,浅复制
>>> x == y  # 两个列表的值相等
True
>>> x is y  # 两个列表并不是同一个对象
False
>>> id(x) == id(y)  # 两个列表对象的内存地址(值的引用)不相等
False
>>> id(x[0]) == id(y[0])  # 但是相同的值在内存中只有一份
True

元组

元组同样支持使用双向索引来访问其中的元素,但元组属于不可变序列,不可以直接修改元组中元素的值,也无法为元组增加或删除元素,只能使用 del 命令删除整个元组。元组的切片只能用来访问元组中的元素。从一定程度上讲,元组可以看作是轻量级的列表或”常量“列表,因此元组的访问速度要比列表更快。

生成器推导式

生成器推导式使用圆括号作为定界符,与列表推导式最大的不同是,生成器推导式的结果是一个生成器对象。生成器对象属于迭代器对象,具有惰性求值的特点,只在需要时生成新元素,比列表推导式具有更高的效率。

使用生成器对象的元素时,可以将其转化为列表或元组,也可以使用生成器对象的 __next__() 方法或内置函数 next() 进行遍历,或直接使用 for 循环来遍历。不管使用哪种方法,都只能从前向后正向访问其中的元素,没有任何方法可以再次访问已经访问过的元素(一个生成器对象中的元素只能访问一次),也不支持使用下标直接访问其中任意位置的元素。若需要重新访问元素,必须重新创建该生成器对象。enumerate、filter、map、zip 等对象也具有同样的特点。

>>> g = ((i + 2) ** 2 for i in range(10))  # 创建生成器对象
>>> g
<generator object <genexpr> at 0x00000293920A5890>
>>> tuple(g)  # 将生成器对象转换为元组
(4, 9, 16, 25, 36, 49, 64, 81, 100, 121)
>>> list(g)  # 生成器对象已遍历结束,没有元素了
[]
>>> g = ((i + 2) ** 2 for i in range(10))  # 重新创建生成器对象
>>> g.__next__()  # 使用生成器对象的 __next__() 方法来获取元素
4
>>> g.__next__()
9
>>> next(g)  # 使用内置函数 next() 来获取元素
16
>>> x = map(str, range(10))
>>> x
<map object at 0x00000293925182B0>
>>> next(x)
'0'
>>> '0' in x  # 访问过的元素无法再次访问
False
>>> '1' in x  # 未访问的元素
True

字典

字典中元素的”键“可以是任意不可变数据,如整数、实数、复数、字符串、元组等类型的可哈希数据,但不能使用如列表、集合、字典等可变类型的数据。字典的”键“不允许重复,而”值“可以重复。字典在内部维护的哈希表使得检索操作非常快。使用内置字典类型 dict 时不必太在乎元素的顺序。如果需要一个可以严格记住元素插入顺序的字典,可以使用标准库 collections 中的 OrderedDict 类。

哈希是指能将大量的信息压缩成一个小的、固定大小的值的函数。如果一个对象是可哈希的,则其具有一个固定的哈希值,该值在该对象的整个生命周期都不会改变,即无论何时计算这个对象的哈希值,都会得到相同的结果(不可变的对象是可哈希的)。字典的键和集合的元素必须是可哈希的,才能借此进行快速查找。内置函数 hash() 可以用于测试一个对象是否可哈希,不必关心函数的返回值是什么,重点是对象是否可哈希,若对象不可哈希则会抛出异常。

>>> hash((1,))
-6644214454873602895
>>> hash([1])
TypeError: unhashable type: 'list'

字典元素的访问

字典中的每个元素表示一种映射关系,根据提供的”键“作为下标可以访问对应的”值“,若”键“不存在会抛出异常。字典提供了一个 get(key) 方法用于返回指定”键“对应的”值“,并且允许指定该”键“不存在时返回指定的”值“。字典还提供了 setdefault(key, value) 方法用于返回指定”键“对应的”值“,若该”键“不存在,就添加一个新元素并设置该”键“对应的”值“(默认为 None)。

>>> x = {'a': 0, 'b': 1, 'c': 2}
>>> x['a']
0
>>> x['d']
KeyError: 'd'
>>> x.get('a')
0
>>> x.get('d', 'Not Exists.')
'Not Exists.'
>>> x.setdefault('d', 3)
3
>>> x
{'a': 0, 'b': 1, 'c': 2, 'd': 3}

对字典直接进行遍历时默认是遍历字典的”键“,若要遍历字典的元素需要使用字典的 items() 方法,若要遍历字典的”值“需要使用字典的 values() 方法。

字典元素的添加、修改与删除

当以指定”键“为下标为字典元素赋值时,若该”键“存在,表示修改该”键“所对应的值,若不存在则表示添加一个新的”键值对“。

>>> x = {'a': 0, 'b': 1, 'c': 2}
>>> x['a'] = 'a'  # 指定的键存在
>>> x
{'a': 'a', 'b': 1, 'c': 2}
>>> x['d'] = 3  # 指定的键不存在
>>> x
{'a': 'a', 'b': 1, 'c': 2, 'd': 3}

字典的 update() 方法可以将另一个字典的元素一次性全部添加到当前字典中,若两个字典中存在相同的”键“,则以另一个字典中的”值“为准对当前字典进行更新。另外,也可以用上述的 setdefault() 方法来为字典添加新元素。update() 可以一次性添加多个元素,而 setdefault() 一次只能添加一个元素。

>>> x = {'a': 0, 'b': 1, 'c': 2}
>>> x.update({'a': 123, 'd': 3})
>>> x
{'a': 123, 'b': 1, 'c': 2, 'd': 3}

字典的 popitem()pop(key[, default]) 方法可以弹出并删除指定的元素。popitem() 方法无参数,删除并返回最后添加进字典的元素,对空字典会抛出异常。pop() 方法弹出指定”键“对应的元素,default 参数指定若”键“不存在时返回的指定的值。当然,也可以用 del 命令来删除指定的元素。字典的 clear() 方法用于清空字典中的所有元素,copy() 方法用于返回字典的浅复制。

>>> x = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4}
>>> x.popitem()
('e', 4)
>>> x
{'a': 0, 'b': 1, 'c': 2, 'd': 3}
>>> x.pop('a')
0
>>> x
{'b': 1, 'c': 2, 'd': 3}
>>> x.clear()
>>> x
{}

集合

集合中的元素必须是不可变类型的数据,并且元素之间不允许重复。

集合元素的添加与删除

集合的 add() 方法用于添加新元素,若该元素已存在则忽略该操作,不会抛弃异常。update() 方法用于合并另一个集合中的元素到当前集合中,并自动去除重复元素。

集合的 pop() 方法会从前向后删除并返回集合中的一个元素,若集合为空则抛出异常。remove(x) 方法用于删除集合中特定值的元素,若不存在则抛出异常。discard(x) 方法功能同 remove(),但若元素不存在会自动忽略,而不会抛出异常。clear() 用于清空集合。

>>> x = {1, 2, 3, 4, 5}
>>> x.add(6)
>>> x
{1, 2, 3, 4, 5, 6}
>>> x.update({1, 2, 7})
>>> x
{1, 2, 3, 4, 5, 6, 7}
>>> x.pop()
1
>>> x.pop()
2
>>> x
{3, 4, 5, 6, 7}
>>> x.remove(1)
KeyError: 1
>>> x.discard(1)
>>> x
{3, 4, 5, 6, 7}
>>> x.clear()
>>> x
set()

集合运算

>>> a_set = {1, 2, 3, 4, 5}
>>> b_set = {3, 4, 5, 6, 7}
>>> a_set | b_set  # 并集
{1, 2, 3, 4, 5, 6, 7}
>>> a_set.union(b_set)
{1, 2, 3, 4, 5, 6, 7}
>>> a_set & b_set  # 交集
{3, 4, 5}
>>> a_set.intersection(b_set)
{3, 4, 5}
>>> a_set - b_set  # 差集(A - B 返回包含在集合 A 中但不包含在集合 B 中的所有元素)
{1, 2}
>>> a_set.difference(b_set)
{1, 2}
>>> a_set ^ b_set  # 对称差集(A 的差集与 B 的差集的并集,即 (A - B) | (B - A))
{1, 2, 6, 7}
>>> a_set.symmetric_difference(b_set)
{1, 2, 6, 7}
>>> x = {1, 2}
>>> y = {1, 2, 3}
>>> z = {4, 5, 6}
>>> x < y  # 子集
True
>>> x.issubset(y)
True
>>> y > x
True
>>> y.issuperset(x)
True
>>> y.isdisjoint(z)  # 测试交集是否为空
True

序列解包

序列解包也可以称为多重赋值。星号 * 在列表、元组、字典(默认为对”键“操作)、集合、字符串等可迭代对象前使用,表示序列解包,即将其中的元素一一展开。

>>> x, y, z = 1, 2, 'a'  # 多个变量同时赋值
>>> x
1
>>> y
2
>>> z
'a'
>>> x, y = y, x  # 交换两个变量的值
>>> x
2
>>> y
1
>>> print(*[1, 2, 3], 4, *(5, 6))  # * 表示可迭代对象的序列解包
1 2 3 4 5 6
>>> print([1, 2, 3], 4, (5, 6))
[1, 2, 3] 4 (5, 6)

字符串

字符串属于不可变有序序列,同样支持双向索引、比较大小、计算长度、元素访问、切片、成员测试(in)等。由于字符串不可变,所以所有对字符串对象涉及“修改”的方法都是返回修改后的新字符串,原字符串并无任何改变。

字符串编码格式

UTF-8 以一个字节表示英语字符,以三个字节表示常用汉字。

GB2312/GBK 前者是我国制定的中文编码,以一个字节表示英语字符,以两个字节表示常用汉字。

Python 3 完全支持中文字符,默认使用 UTF-8 编码格式,无论是一个数字、英文字母,还是一个汉字,计算字符串长度时都按一个字符处理。

转义字符

转义字符 含义
\n 换行符
\f 换页符
\t 制表符(Tab)
\r 回车
\\ 一个反斜线
\' 一个单引号
\" 一个双引号

在使用文件路径、URL和正则表达式等场景下时,为了避免对字符串中的转义字符进行转义,可以使用原始字符串。在字符串的前面加上字母 r 或 R 表示原始字符串。

>>> path = 'C:\Windows\notepad.exe'
>>> print(path)
C:\Windows
otepad.exe
>>> path = r'C:\Windows\notepad.exe'
>>> print(path)
C:\Windows\notepad.exe

字符串格式化

%-formatting

格式规范:'%[-][+][0][m][.n][格式字符]' % x

[-] 用于指定x为左对齐。

>>> a = 'Leon'
>>> b = 'Hello, %-10s' % a
>>> b
'Hello, Leon      '

[+] 用于对正数加正号。

>>> a = 1234
>>> b = '%+d' % a
>>> b
'+1234'

[0] 用于指定空位填0,适用于数值型的格式符(%d%f)。

>>> a = 7
>>> b = '%03d' % a
>>> b
'007'

[m] 用于指定最小宽度。

>>> a = 'Leon'
>>> b = 'Hello, %10s' % a
>>> b
'Hello,       Leon'

[.n] 用于指定浮点数的精度。

>>> a = 1234
>>> b = '%.2f' % a
>>> b
'1234.00'

[格式字符]

格式字符 含义
s 字符串
c 整数转化为 Unicode 字符、字符串转化为其第一个字符
d 十进制整数
o 八进制整数
x 十六进制整数
e 以 e 为底的指数
f 浮点数

x 待格式化的内容,需与格式字符一一对应。

str.format()

格式规范:"{[field_name]:[format_spec]}".format(x)

[field_name] 用于指定要插入的变量 x 的名称或用数字表示索引(从 0 开始)。

[format_spec] = [[fill]align][sign][#][0][width][grouping_option][.precision][type]

# 使用数字表示索引
>>> print("0: {0:.4s}, 1: {1:.4s}".format('dreamless', 'drugs'))
0: drea, 1: drug

# 使用关键字参数和属性( arg_name.attr_name )
>>> class Person:
	def __init__(self, name, age):
		self.name = name
		self.age = age

>>> person = Person("Leon", 22)
>>> msg = "My name is {person.name}, and I am {person.age} years old.".format(person=person)
>>> print(msg)
My name is Leon, and I am 22 years old.

[fill] 填充字符,用于填充空白部分的单个字符,可以为任意字符,默认为空格。

[align] 对齐方式,< 表示左对齐,> 表示右对齐,^ 表示居中对齐,= 表示将填充字符放在符号和数字之间(仅适用于数值类型)。

>>> print("{:*^20}".format("PYTHON"))  # 填充字符为 * 并居中对齐
*******PYTHON*******

[sign] 符号,+ 表示在正数前面显示正号和负号,- 表示只在负数前显示负号(默认行为), 空格表示在正数前面留空格。

>>> print("{:=+10}".format(22))  # 将填充字符(空格)放在正号与数字之间
+       22

[#] 对于二进制、八进制、十六进制,如果加上 #,则会加上 0b0o0x 前缀。

>>> print("{:#b}".format(22))
0b10110

[0] 用于指定空位填充为0(仅对数值类型有效)。

[width] 用于指定输出的最小字符宽度。如果输出的值小于这个数,将会使用填充字符补足。

[grouping_option] 指定数字的千位分隔符为 _,

>>> print("{:_}".format(5000000))
5_000_000

[.precision] 精度,对于浮点数,表示小数点后的位数。对于字符串,表示输出的最大字符数。

>>> print("{:.2f}".format(3.1415926))
3.14
>>> print("{:.3s}".format('ecust'))
ecu

[type]

格式字符 含义
b 二进制整数
% 输出百分比形式的浮点数("{:.2%}".format(0.25)
sdoxefc %-formatting

f-string

格式规范:f"{[f_expression]:[format_spec]}",更详细见 Python 官方教程 - Literals - Formatted string literals

[f_expression] 可以是任何有效的 Python 表达式,如变量名、算术表达式、调用函数、条件表达式、访问对象属性等。当f-string 被计算时,这个表达式的值会被计算并转换为字符串。

# 变量名 / 方法调用
>>> name = "Leon"
>>> f"{name}"
>>> f"{name.upper()}"

# 算术表达式
>>> x = 10
>>> y = 20
>>> f"{x + y}"

# 列表和字典访问
>>> numbers = [10, 20, 30]
>>> info = {'name': 'Leon', 'age': 22}
>>> f"{numbers[1]}, {info['name']}"

# 调用函数
>>> def greet(name):
	return f"Hello, {name}!"

>>> name = "Leon"
>>> f"{greet(name)}"

# 条件表达式(三元运算符)
>>> x = 5
>>> f"{x if x > 10 else 'x is less than or equal to 10'}"

# 访问对象属性
>>> class Person:
	def __init__(self, name, age):
		self.name = name
		self.age = age

>>> person = Person("Leon", 22)
>>> f"My name is {person.name}, and I am {person.age} years old."

[format_spec]str.format()

字符串常用操作

查找

find()rfind() 用于查找一个字符串在另一个字符串指定范围(默认为整个字符串)中首次和最后一次出现的位置,若不存在则返回 -1。rfind() 相当于从右向左查找。

参数:find(sub[, start[, end]]) -> int 2

>>> s = 'apple, peach, banana, peach, pear'
>>> s.find('p')
1
>>> s.find('p', 5, 10)
7

index()rindex() 用法同上,不同之处在于,若不存在则抛出异常。

count() 用于查找一个字符串在另一个字符串中出现的次数,若不存在则返回 0。

分隔

split()rsplit() 用于以指定的分隔符,从字符串的左端和右端开始将其分隔成多个字符串,并以列表形式返回。

参数:split([sep[, maxsplit]]) sep 参数指定分隔符,maxsplit 参数指定分隔的最大次数,默认为 -1,即没有限制。若不指定分隔符,则字符串中任何空白符号(包括空格、换行符、制表符等)都将被认为是分隔符(多个连续的空白符号视为一个),返回不含空字符串的列表。但是,明确传递 sep 参数时,连续的相邻分隔符之间会得到空字符串。

>>> s = 'apple, peach, banana, peach, pear'
>>> s.split(',')
['apple', ' peach', ' banana', ' peach', ' pear']
>>> s.split('e')
['appl', ', p', 'ach, banana, p', 'ach, p', 'ar']

partition()rpartition() 用于以指定的分隔符将原字符串分隔为三部分,即分隔符之前的字符串、分隔符字符串、分隔符之后的字符串,并以元组的形式返回。若指定的分隔符不存在,则返回原字符串和两个空字符串。若存在多个指定的分隔符,partition() 以从左向右的第一个分隔符作为分隔符,rpartition() 则相反。

连接

join() 以指定的连接符将多个字符串(多为列表形式)连接,返回新字符串。

>>> li = ['apple', 'peach', 'banana', 'pear']
>>> ' '.join(li)  # 以空格作为连接符
'apple peach banana pear'
>>> ','.join(li)
'apple,peach,banana,pear'  # 以逗号作为连接符

join()split() 可以一起用于删除字符串中多余的空白字符,如果有连续多个空白字符,只保留一个。

>>> s = 'aaa     bb   c d eee  '
>>> ' '.join(s.split())
'aaa bb c d eee'

大小写转换

lower()upper() 用于将字符串转换为小写、大写的字符串。

capitalize() 用于将字符串的首字母转换为大写。

title() 用于将字符串中的每个单词的首字母转换为大写。

swapcase() 用于将大小写互换。

>>> s = 'What is Your Name?'
>>> s.lower()
'what is your name?'
>>> s.upper()
'WHAT IS YOUR NAME?'
>>> s.capitalize()
'What is your name?'
>>> s.title()
'What Is Your Name?'
>>> s.swapcase()
'wHAT IS yOUR nAME?'

替换

replace() 用于替换字符串中的指定字符,每次只能替换一种指定的字符。

参数:replace(old, new[, count]) 若提供了 count 参数,则只替换前 count 次出现的 old 字符。count 参数默认为 -1,即全部替换。

>>> s = 'a, b, c, a, a, d'
>>> s.replace('a', '123')
'123, b, c, 123, 123, d'
>>> s.replace('a', '123', 1)
'123, b, c, a, a, d'

maketrans()translate() 前者用于生成一个字符映射表,指定字符的一一对应的转换关系。而后者用于按照映射表的对应关系来替换字符串中的字符。这两个方法组合可以同时处理多个不同的单字符。

参数:maketrans(input, output[, delete]) 可选参数 delete 用于指定将在 translate() 后被删除的字符。

>>> s = 'My favorite color is purple.'
>>> table = ''.maketrans('abcdefghij', '0123456789')  # 创建转换表,也可以用 str.maketrans()
>>> s.translate(table)
'My 50vor8t4 2olor 8s purpl4.'
>>> table = str.maketrans('abcdefghij', '0123456789', 'l')
>>> s.translate(table)
'My 50vor8t4 2oor 8s purp4.'

修剪

strip()rstrip()lstrip() 用于删除字符串两端、右端和左端的连续的空白字符或指定字符。其参数所指定的字符并不视为整体,而是单独一个一个删除。

>>> s = '  12345 \n\n'
>>> s.strip()
'12345'
>>> s = 'aaahelloworlddddaa'
>>> s.strip('a')
'helloworldddd'
>>> s.strip('ad')  # 删除 'a' 和 'd'
'helloworl'

判断

startswith()endswith()用于判断字符串的前缀和后缀是否是指定的字符串,返回 True 或 False。

参数:s.startswith(prefix[, start[, end]]) -> bools.endswith(suffix[, start[, end]]) -> bool startend 参数用于指定字符串的检测范围。

endswith() 可接收字符串元组作为参数来判断文件的扩展名。

>>> import os
>>> [filename for filename in os.listdir('E:\\') if filename.endswith((.png', '.jpg', '.gif'))]
['logo.png']

检查

通过运算符 in 可以判断一个字符串是否出现在另一个字符串中,返回 True 或 False。

>>> 'e' in 'Dreamless Drugs'
True

isalnum() 用于检查字符串中的所有字符是否都是字母或数字,返回 True 或 False。

isalpha() 用于检查字符串中的所有字符是否都是字母,返回 True 或 False。

isdigit() 用于检查字符串中的所有字符是否都是数字,返回 True 或 False。

isdecimal() 用于检查字符串中的所有字符是否都是十进制数字,返回 True 或 False。它比 isdigit() 更严格。

isnumeric() 用于检查字符串中的所有字符是否都是数字或用 Unicode 表示的数字,返回 True 或 False。

isspace() 用于检查字符串中的所有字符是否都是空白字符,返回 True 或 False。

>>> "\t\n\r".isspace()
True

isupper()islower() 用于检查字符串中的所有字符是否都是大写、小写,返回 True 或 False。

排版

center()ljust()rjust() 返回指定宽度的新字符串,原字符串以居中、左对齐、右对齐的对齐方式出现在新字符串中。若指定的宽度大于原字符串长度,则用指定的字符填充。

参数:s.center(width[, fillchar]) -> copy of s

zfill() 返回指定宽度的字符串,在左侧以字符 0 填充。

>>> s = 'Dreamless Drugs'
>>> s.center(20)
'  Dreamless Drugs   '  # 默认以空格填充
>>> s.center(20, '=')
'==Dreamless Drugs==='
>>> s.ljust(20, '=')
'Dreamless Drugs====='
>>> s.rjust(20, '=')
'=====Dreamless Drugs'
>>> s.zfill(20)
'00000Dreamless Drugs'

切片

字符串同样能使用切片功能,但仅限于读取其中的元素,不支持字符串修改。

>>> s = 'Dreamless Drugs'
>>> s[:5]
'Dream'

输入转换

用户通过 input() 的输入都为字符串类型,对于整数、实数和复数可以直接使用int()float()complex() 进行转换,而对于列表、元组等需要使用 eval() 进行转换而不能直接使用 list()tuple()。为了检查用户输入的字符串是否是安全的,可以使用标准库 ast 中的安全函数 literal_eval()

>>> x = input()
[1, 3, 5, 7, 9]
>>> x
'[1, 3, 5, 7, 9]'
>>> eval(x)
[1, 3, 5, 7, 9]
>>> import ast
>>> ast.literal_eval(x)  # 更推荐
[1, 3, 5, 7, 9]

正则表达式

正则表达式(Regular Expression)可以使用预定义的模式去匹配一类具有共同特征的字符串,其功能通过标准库 re 提供。【官方教程

正则表达式语法

元字符 说明 示例
. 默认模式下,匹配除换行符之外的任意单个字符  
[] 匹配位于 [] 中的任意一个字符 [a-z] 匹配任意一个小写字母、[0-5][0-9] 匹配所有两位数 00 到 59
- [] 之内用于表示范围 [a\-z] 使用转义后可匹配字符 -
^ 匹配以 ^ 后面的字符串开头的字符串 ^http 只匹配以 'http' 开头的字符串
[^] ^ 放在 [] 内表示反向字符集 [^xyz] 匹配除了 'x''y''z' 之外的任何字符
$ 匹配在换行符之前以 $ 前面的字符串结束的字符串 foo.$'foo1\n' 中的匹配结果为 'foo1'
| 匹配位于 | 之前或之后的字符(正则表达式),可以用 | 分隔任意数量,匹配时将从左向右依次尝试,当完全匹配时后面的分支将不会被进一步测试 A|B|C|D
* 匹配位于 * 之前的单个字符或模式的 0 次或多次重复 ca*t 可以匹配 'ct'(0 次 'a')、'cat'(1 次 'a')和 'caaat'(3 次 'a')等
+ 匹配位于 + 之前的单个字符或模式的 1 次或多次重复 ca+t 不会匹配 'ct'
? 表示 ? 之前的字符是可选的,即它可以出现 0 次或 1 次 ab? 可以匹配 'a''ab'
*?+??? ? 在其他元字符之后表示非贪婪模式,即匹配尽可能少的字符串,而 *+? 都是匹配尽可能多的字符串 <.*>'<a> b <c>' 中将匹配整个字符串,而 <.*?> 将只匹配 '<a>'
{m} 指定 {m} 前一个字符的匹配次数 a{6} 将恰好匹配 6 个 'a'
{m,n} 指定匹配 {m,n} 前一个字符 m 到 n 次,将尝试匹配尽可能多的重复,若省略 m 则指定下限为零,若省略 n 则指定无上限 a{4,}b 将匹配 'aaaab' 以及在 'b' 前面有更多 'a' 的字符串,但不会匹配 'aaab'
{m,n}? 使 {m,n} 尝试匹配尽可能少的重复 对于字符串 'aaaaaa'a{3,5} 将匹配 5 个 'a'a{3,5}? 将匹配 3 个 'a'
\ 用于转义所有元字符,以及 \ 后面可以跟各种字符来表示各种特殊序列 使用 \\$ 来匹配 '$'
\A 确保匹配将从字符串的开头位置开始  
\b 表示单词边界,匹配单词头或单词尾 r'\bcat\b' 只匹配包含单词 'cat' 的字符串,但不会匹配 'concatenate' 中的 'cat' 部分
\B \b 含义相反,表示非单词边界 r'\Bcat\B' 会匹配 'concatenate' 中的 'cat' 部分,但不会匹配独立的单词 'cat'
\d 匹配任何数字,相当于 [0-9] \d 会在字符串 'apple123' 中匹配 '1''2''3'
\D \d 含义相反,相当于 [^0-9] \D 会在字符串 'apple123' 中匹配 'a''p''p''l''e'
\s 匹配任何空白字符,包括空格、制表符、换行符等,相当于 [ \t\n\r\f\v] \s 会匹配字符串 'Hello\tWorld\n' 中的 \t\n
\S \s 含义相反,匹配任何非空白字符,相当于 [^ \t\n\r\f\v]  
\w 匹配任何字母、数字以及下划线,相当于 [a-zA-Z0-9_] \w+ 表示 \w 模式可以连续出现一次或多次
\W \w 含义相反,相当于 [^a-zA-Z0-9_]  
\Z 确保匹配将从字符串的末尾位置开始  
\number 引用相同编号组(())中的内容,number 为正整数 (\w+)\s+\1 可以匹配任何两个相同的连续单词, \1 表示引用第一个子模式 (\w+) 中的内容,即 (\w+) 可以匹配一个单词,\1(\w+) 也可以匹配一个相同的单词
\n\f\r\t 也支持字符串的标准转义字符,分别匹配一个换行符、换页符、回车符、制表符  
() 表示一个子模式(组),圆括号中的内容作为一个整体对待,可以使用 \number 进行匹配 (cat)+ 可以匹配 'catcat''catcatcat' 等一个或多个连续 'cat' 的情况
(?:...) 表示非捕获组,即匹配圆括号中的正则表达式但不捕获  
(?#...) 一般位于正则表达式最后来表示注释,从而允许在正则表达式内部添加注释,不会被返回  
(?=...) 用于想要匹配的正则表达式之后,如果 = 后的内容在字符串中紧跟着出现则匹配,但并不返回 = 之后的内容 abc(?=def) 在字符串 'abcdef' 中匹配并返回 'abc'
(?<=...) 用于想要匹配的正则表达式之前,如果 <= 后的内容在字符串中紧跟着出现则匹配,但并不返回 <= 之后的内容 (?<=abc)def 在字符串 'abcdef' 中匹配并返回 'def'
(?!...) 用于想要匹配的正则表达式之后,如果 ! 后的内容在字符串中紧跟着出现则匹配,但并不返回 之后的内容 abc(?!def) 只有在字符串 'abc' 之后没有 'def' 时才会匹配
(?<!...) 用于想要匹配的正则表达式之前,如果 != 后的内容在字符串中紧跟着出现则匹配,但并不返回 != 之后的内容 (?!=abc)def 只有在字符串 'def' 之前没有 'abc' 时才会匹配
(?P<name>) 为组命名  
(?P=name) 匹配之前命名为 name 的组所匹配的任何字符串  
(?aiLmsux) 设置匹配标志,可以是单个字母,也可以是多个字母的组合,每个字母的含义与编译标志相同  

元字符在 [] 不起作用,只是表示普通的字符,如 [ak$] 会匹配'a''k''$'$ 通常是一个元字符,但在 [] 中它被剥夺了它的特殊性质。

为什么使用 \\\\ 来匹配 '\':在 Python 字符串中,反斜杠是一个转义字符,为了在字符串中表示一个字面的反斜杠,需要使用 \\。在正则表达式中,反斜杠也是一个转义字符,为了在正则表达式中匹配一个字面的反斜杠,也需要使用 \\。因此,为了在 Python 字符串中表示一个正则表达式,该正则表达式可以匹配一个字面的反斜杠,则需要使用 \\\\,当然也可以使用原始字符串 r'\\'

正则表达式模块 re

Python 标准库 re 提供了正则表达式操作所需要的功能,既可以直接使用其中的函数处理字符串,也可以使用编译后的正则表达式对象处理字符串。

编译标志

编译标志(flags)会影响正则表达式的匹配行为。

flags 说明
re.ASCII or re.A 表示 \b\d\s\w 将只匹配 ASCII 字符,而不是整个 Unicode 字符集
re.IGNORECASE or re.I 令匹配对大小写不敏感
re.LOCALE or re.L 支持本地字符集的字符(不常用)
re.MULTILINE or re.M 多行匹配模式,可以使 ^$ 能够匹配字符串中每一行的开头和结尾
re.DOTALL or re.S 使 . 能够匹配任何字符,包括换行符
re.UNICODE or re.U 匹配 Unicode 字符集(默认标志)
re.VERBOSE or re.X 允许在正则表达式中加入空白字符和注释

常用函数

  • re.search(pattern, string, flags=0)

pattern 参数指正则表达式,可用原始字符串防止转义,string 参数指用于匹配的字符串,flags 参数的值可以是上述的各个编译标志。search() 函数用于在整个字符串中搜索正则表达式的首个匹配项,如果匹配成功就返回 Match 对象,否则返回 None。返回的 Match 对象可以用 group() 方法查看内容。

  • re.match(pattern, string, flags=0)

match() 函数从字符串的开始处进行匹配,如果匹配成功就返回 Match 对象,否则返回 None

>>> import re
>>> text = re.search(r'hello', 'Say hello!')  # search() 在整个字符串中匹配
>>> text.group()
'hello'
>>> text = re.match(r'hello', 'Say hello!')  # match() 从字符串的开头匹配,匹配失败
>>> text.group()
AttributeError: 'NoneType' object has no attribute 'group'
>>> text = re.match(r'hello', 'hello Say hello!')  # match() 匹配成功
>>> text.group()
'hello'
  • re.fullmatch(pattern, string, flags=0)

fullmatch() 只在正则表达式匹配整个字符串时才返回 Match 对象,否则返回 None。因此,可以用于确保整个字符串都符合特定的格式。

>>> import re
>>> text = re.fullmatch(r'\d+', '12345')  # 确保整个字符串都由数字 [0-9] 构成
>>> if text:
	print('The string is made of digits.')
else:
	print('The string has non-digit characters.')
The string is made of digits.
  • re.split(pattern, string, maxsplit=0, flags=0)

split() 根据正则表达式的匹配项来分隔字符串,返回一个列表。maxsplit 参数指定字符串的最大分隔次数,并且字符串的其余部分作为列表的最终元素返回。如果在 pattern 中使用元字符 ()(捕获组),那么分隔符也会作为结果返回,并且分隔符如果在字符串的开头或结尾得到匹配,那么结果则会以空字符串开头或结尾。

>>> import re
>>> re.split(r'\W+', 'Words, words, ws.')  # 最后一个匹配项是 '.',但是在 '.' 之后字符串没有内容,因此返回的最后一项为空字符串
['Words', 'words', 'ws', '']
>>> re.split(r'(\W+)', 'Words, words, ws.')  # 正则表达式使用了 (),分隔符也被返回
['Words', ', ', 'words', ', ', 'ws', '.', '']
>>> re.split(r'\W+', 'Words, words, ws.', maxsplit=1)  # 指定最大分隔次数为 1,字符串的剩余部分以一个整体作为最后一项返回
['Words', 'words, ws.']
  • re.findall(pattern, string, flags=0)

findall() 以列表的形式按照匹配的顺序返回字符串中的所有匹配项。相对于 search() 只返回第一个匹配项,findall() 则会返回所有匹配项。如果 pattern 中有一个或多个捕获组,则会返回一个元组列表。如果没有匹配,则会返回一个空列表。

>>> import re
>>> text = 'Emails: example1@gmail.com, example2@163.com'
>>> re.findall(r'(\w+)@(\w+)\.(\w+)', text)  # 多个捕获组,返回元组列表
[('example1', 'gmail', 'com'), ('example2', '163', 'com')]
  • re.finditer(pattern, string, flags=0)

finditer() 返回包含所有匹配项的迭代器,而不是由字符串组成的列表,其中每个匹配项都是 Match 对象。相比于 findall()finditer() 可以对得到的匹配项使用更全面的功能。

>>> import re
>>> text = 'The rain in Spain falls mainly in the plain.'
>>> matches = re.finditer(r'\b\w+ain\b', text)
>>> for match in matches:  # 获取每个匹配项的内容
	print(match.group(), end=' ')
rain Spain plain 

# 注意!迭代器 matches 的元素只能访问一次
>>> for match in matches:  # 获取每个匹配项的起始和结束位置
	print(f'Match: {match.group()}, Start: {match.start()}, End: {match.end()}')
Match: rain, Start: 4, End: 8
Match: Spain, Start: 12, End: 17
Match: plain, Start: 38, End: 43
  • re.sub(pattern, repl, string, count=0, flags=0)

sub() 使用 repl 替换字符串中的所有匹配项,返回新字符串,如果没有匹配则返回原字符串。repl 参数可以是字符串或返回字符串的可调用对象(例如函数),该可调用对象作用于每个匹配的 Match 对象。count 参数表示替换的最大次数,默认为 0,即替换所有的匹配项。

>>> import re
>>> text = re.sub(r'drugs', 'clouds', 'Dreamless drugs are the best drugs ever.')
>>> text
'Dreamless clouds are the best clouds ever.'
>>> text = re.sub(r'drugs', 'clouds', 'Dreamless drugs are the best drugs ever.', count=1)  # 指定最大替换次数
>>> text
'Dreamless clouds are the best drugs ever.'
>>> def upper_func(match):  # 接收匹配的 Match 对象为参数
	return match.group(0).upper()

>>> text = re.sub(r'\b[a-z]+\b', upper_func, 'hello world')  # repl 为函数
>>> text
'HELLO WORLD'
  • re.subn(pattern, repl, string, count=0, flags=0)

subn() 执行与 sub() 相同的操作,只不过返回的是一个元组,包括新字符串和替换次数。

>>> import re
>>> text = re.subn(r'drugs', 'clouds', 'Dreamless drugs are the best drugs ever.')
>>> text
('Dreamless clouds are the best clouds ever.', 2)
  • re.escape(pattern)

escape() 用于将字符串中所有特殊的正则表达式字符转义成普通字符,可以用于处理用户输入的数据,因为不知道输入的字符串中可能包含哪些特殊字符。主要目的是让一个字符串在任何正则表达式中都能被当作字面量(即不具有特殊含义)来看待。例如,有一个包含 .*? 这三种特殊字符的字符串,若想在文本中查找这个确切的字符串,而不是它作为正则表达式所代表的 pattern,就可以使用 escape() 来正确转义后再进行匹配。

>>> import re
>>> re.escape('http://www.python.org')
'http://www\\.python\\.org'  # 双反斜杠 \\ 表示一个字面的反斜杠 \,然后 \. 表示字面的 .,因此 \\. 才能匹配 .
  • re.compile(pattern, flags=0)

compile() 用于将正则表达式 pattern 编译为正则表达式对象,便于表达式在单个程序中的多次使用。

正则表达式对象

通过 compile() 函数将正则表达式 pattern 编译成正则表达式对象后,可以使用更多的方法来处理字符串。

search()match()fullmatch()findall()finditer()

  • Pattern.search(string[, pos[, endpos]]

search() 用于在整个字符串或指定范围中进行搜索,返回第一个匹配的 Match 对象,若没有匹配则返回 Nonepos 参数给出开始搜索的索引,默认为 0。endpos 参数限制字符串的搜索范围。

>>> import re
>>> text = 'Dreamless drugs are the best drugs ever.'
>>> p = re.compile(r'drugs')
>>> p.search(text)
<re.Match object; span=(10, 15), match='drugs'>
>>> p.search(text, 20)  # 设定搜索从 20 开始
<re.Match object; span=(29, 34), match='drugs'>
  • Pattern.match(string[, pos[, endpos]]

match() 从字符串开头或指定位置进行搜索,且必须以模式开头,才会返回 Match 对象,其余同 search()

>>> import re
>>> text = 'Dreamless drugs are the best drugs ever.'
>>> p = re.compile(r'drugs')
>>> print(p.match(text))  # 无匹配返回 None
None
>>> p.match(text, 10)
<re.Match object; span=(10, 15), match='drugs'>
  • Pattern.fullmatch(string[, pos[, endpos]]

如果整个字符串与该正则表达式匹配,则返回相应的 Match 对象。

  • Pattern.findall(string[, pos[, endpos]]Pattern.finditer(string[, pos[, endpos]]

re.findall()re.finditer(),只是增加了 posendpos 参数。

split()

Pattern.split(string, maxsplit=0)re.split()

sub()subn()

Pattern.sub(repl, string, count=0)Pattern.subn(repl, string, count=0)re.sub()re.subn()

groupspattern 属性

Pattern.groups 返回捕获组的数量,Pattern.pattern 返回编译后正则表达式对象的模式字符串。

Match 对象

group()groups()groupdict()

Match.group([group1, ...]) 返回匹配的一个或多个子模式内容,group1 默认为 0(返回整个模式内容);Match.groups() 返回一个包含所有匹配的子模式内容的元组;Match.groupdict() 返回一个包含所有匹配的命名的子模式内容的字典。

>>> import re
>>> m = re.match(r'(\w+) (\w+)', 'Dreamless Drugs, harmless')
>>> m.group()  # 返回整个模式内容
'Dreamless Drugs'
>>> m.group(1)  # 返回第一个子模式内容
'Dreamless'
>>> m.group(2)  # 返回第二个子模式内容
'Drugs'
>>> m.group(1, 2)  # 以元组形式返回指定的多个子模式内容
('Dreamless', 'Drugs')
>>> m.groups()  # 以元组形式返回所有匹配的子模式内容
('Dreamless', 'Drugs')

>>> m = re.match(r'(?P<first_name>\w+) (?P<last_name>\w+)', 'Melanie C')
>>> m.group('first_name')  # 使用命名的子模式
'Melanie'
>>> m.group('last_name')
'C'
>>> m.groupdict()
{'first_name': 'Melanie', 'last_name': 'C'}

start()end()span()

Match.start([group])Match.end([group]) 返回匹配的子字符串的起始位置和结束位置后一位的索引。group 参数用于指定第几个捕获组,默认为 0,表示整个匹配的子字符串。Match.span([group]) 返回一个包含 start()end() 的元组。

>>> import re
>>> m = re.match(r'(\w+) (\w+)', 'Dreamless Drugs, harmless')
>>> m.start()
0
>>> m.end()
15
>>> m.span()  # 整个匹配的子字符串
(0, 15)
>>> m.span(1)  # 第一个捕获组匹配的子字符串
(0, 9)
>>> m.span(2)  # 第二个捕获组匹配的子字符串
(10, 15)

expand()

Match.expand(template) 用于通过一个模板字符串进行正则表达式的替换操作,其中该模板字符串可以包含正则表达式的捕获组,捕获组可以用 \number 或捕获组名(\g<name>)的方式进行引用。

>>> import re
>>> m = re.match(r'(\w+) (\w+)', 'Dreamless Drugs, harmless')
>>> m.group()  # 查看匹配结果
'Dreamless Drugs'
>>> m.expand(r'\2 \1')  # 使用 \number 引用捕获组进行替换
'Drugs Dreamless'

>>> m = re.match(r'(?P<first_name>\w+) (?P<last_name>\w+)', 'Melanie C')
>>> m.group()  # 查看匹配结果
'Melanie C'
>>> m.expand(r'\g<last_name> \g<first_name>')  # 使用捕获组名来引用捕获组进行替换
'C Melanie'

lastindexlastgrouprestring 属性

Match.lastindex 返回最后匹配的捕获组的索引,若无匹配则为 NoneMatch.lastgroup 返回最后匹配的捕获组的名称;Match.re 返回生成此 Match 对象的正则表达式对象,从而可得到patternMatch.string 返回传递给生成此 Match 对象的正则表达式对象的字符串。

程序控制结构

在程序控制结构中,都要根据条件表达式的值来确定下一步的执行流程。条件表达式的值只要不是 False、0、空值 None、空列表、空元组、空字典、空集合、空字符串等其他空可迭代对象,Python 均认为与 True 等价。

选择结构

单分支选择结构

当表达式的值为 True 或其他与 True 等价的值时,表示条件满足,语句块被执行。

if expression:
	# 语句块

双分支选择结构

if expression:
	# 语句块 1
else:
	# 语句块 2

三元运算符构成的表达式也属于双分支选择结构,其语法为:value1 if condition else value2。当条件表达式 condition 的值与 True 等价时,表达式的值为 value1,否则表达式的值为 value2value1value2 本身也可以是复杂表达式,也可以包含函数调用,甚至是三元运算符构成的表达式。此外,三元运算符构成的表达式也具有惰性求值的特点。

多分支选择结构

if expression1:
	# 语句块 1
elif expression2:
	# 语句块 2
elif expression3:
	# 语句块 3
else:
	# 语句块 n

选择结构的嵌套

if expression1:
	# 语句块 1
	if expression2:
		# 语句块 2
	else:
		# 语句块 3
else:
	if expression4:
		# 语句块 4

循环结构

for 循环与 while 循环

for 循环一般用于循环次数可以提前确定的情况,尤其适用于枚举遍历可迭代对象中元素的场合。while 循环一般用于根据一定的条件重复执行一段代码的情况,并且往往难以提前确定循环次数。对于带有 else 子句的循环结构,如果循环因为条件表达式不成立或序列遍历结束而自然结束时则执行 else 中的语句,如果循环是因为执行了 break 语句而导致循环提前结束则不会执行 else 中的语句。

# for 循环
for variable in sequence:
	# 循环体
[else:
	# 代码块]
	
# while 循环
while condition:
	# 循环体
[else:
	# 代码块]

使用 while True: 可以构建一个无限循环。

breakcontinue 语句

breakcontinue 语句一般常与选择结构异常处理结构结合使用。break 语句被执行会使得所属层次的循环提前结束,即完全跳出当前循环,不再执行剩余的迭代。continue 语句被执行会使得本次循环提前结束,忽略 continue 之后的语句,提前进入下一次循环。breakcontinue 语句都只对最内层的循环有效。

循环优化技巧

在多重循环嵌套的情况下,要尽量减少内层循环中不必要的计算,尽可能地将计算向外提。

在使用模块中的方法时,可以通过将其转换为局部变量来提高运行速度。

import math  # 或者使用 from math import sin[ as loc_sin],效果一样
for i in range(1000000):
	math.sin(i)
	
loc_sin = math.sin  # 将 math.sin 转换为局部变量
for i in range(1000000):
	loc_sin(i)

如果需要测试一个序列是否包含一个元素应尽量使用字典或集合,将多个字符串连接成一个字符串时应尽量使用 join() 方法而不是运算符 +,对列表进行元素的插入和删除应尽量从尾部进行。最终的最终,首先应将代码写对,保证完全符合功能要求,再进行必要的优化来提高性能。

异常处理结构

Python 内置异常类的继承层次,其中 BaseException 是所有内置异常类的基类。在使用异常处理结构捕获和处理异常时,应尽量具体一点,建议先尝试捕获派生类,再捕获基类。最常用的是 Exception 类。

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

异常处理结构的基本思路是先尝试运行代码,如果没有问题就正常执行,如果发生了错误就尝试着去捕获和处理,最后实在没办法才崩溃。在函数中简易地抛出异常也可以使用 raise 关键字,其基本语法为 raise ExceptionType('message')

try - except

该异常处理结构类似于单分支选择结构,try 子句中的代码块包含可能会引发异常的语句,except 子句用于捕获相应的异常。如果 try 子句中的代码引发异常并被 except 子句捕获,就执行 except 子句中的代码块。如果 try 子句中的代码没有出现异常就继续执行异常处理结构之后的代码。如果出现异常但没有被 except 捕获,则继续向外层抛出。如果所有层都没有捕获并处理该异常,则程序崩溃。基本语法如下:

try:
	# 可能会引发异常的代码
except ExceptionType[ as e]:
	# 如果 try 中的代码出现异常并被 except 捕获,执行此处的代码

except 后跟想要捕获的异常类型,常用的为 Exception 类,当然异常类型越具体越好,具体的类型见上e 是被抛出的异常类型的实例,可以访问其属性和方法,以获取更多关于异常的信息。

try - except - else

该异常处理结构类似于双分支选择结构,如果 try 中的代码没有引发异常,则执行 else 中的代码。如果 try 中的代码抛出了异常并被 except 捕获则不会执行 else 中的代码。

try:
	# 可能会引发异常的代码
except ExceptionType[ as e]:
	# 如果 try 中的代码出现异常并被 except 捕获,执行此处的代码
else:
	# 如果 try 中的代码没有出现异常,就继续执行此处的代码

try 中不应该放太多代码,而是应该放最有可能引发异常的代码。

try - except - finally

在该异常处理结构中,无论 try 中的代码是否发生异常,也不管抛出的异常是否成功被 except 捕获,finally 子句中的代码都会被执行。因此,finally 中的代码常用来做一些清理或释放资源的工作。当然,也可以结合 else 子句使用。

try:
	# 可能会引发异常的代码
except ExceptionType[ as e]:
	# 如果 try 中的代码出现异常并被 except 捕获,执行此处的代码
else:
	# 也可以结合 else 子句
finally:
	# 无论如何此处的代码都会被执行

可以捕获多种异常的异常处理结构

使用多个 except 子句即可实现捕获多种异常,一旦 try 子句中的代码抛出异常,就按照顺序依次检查与哪一个 except 的异常类型匹配,一旦捕获到异常,其他 except 子句将不会再尝试捕获。

try:
	# 可能会引发异常的代码
except ExceptionType1:
	# 处理异常类型 1 的代码
except ExceptionType2:
	# 处理异常类型 2 的代码
except ExceptionType3:
	# 处理异常类型 3 的代码

函数

函数定义

函数使用 def 关键字来定义,函数名尽量小写并用下划线 _ 来连接单词。

def 函数名([参数列表]):
	'''注释'''
	函数体

定义函数时,开头部分的注释可以为用户提供提示和使用帮助。使用 help()print(函数名.__doc__) 来查看函数的使用帮助,会返回函数开头注释的内容。并且在调用该函数时输入左侧左侧圆括号之后,会立刻显示该函数的使用说明。

定义函数时使用 return 语句结束函数执行的同时返回任意类型的值,函数返回值的类型与 return 语句返回表达式的类型一致。不论 return 语句出现在函数的什么位置,一旦得到执行将直接结束函数的执行。如果函数没有 return 语句、有 return 语句但是没有执行或者 return 语句执行了但是没有返回任何值,解释器都会认为该函数以 return None 结束,即返回空值。

装饰器

装饰器(decorator)本身也是一个函数,它接受其他函数作为参数并对其进行一定的改造之后返回新函数,如类方法、静态方法等。在函数前使用 @ 来执行。

# 定义装饰器 before
def before(func):
def wrapper(*args, **kwargs):
	print('Before function called.')
	return func(*args, **kwargs)
return wrapper

# 使用 before 装饰器修饰函数
@before
def test():
	return 3

# 调用函数
test()

# 运行结果
Before function called.
3

递归函数

递归函数是指在函数内部调用自身,并需要存在一种递归结束条件。

# 定义一个计算阶乘的递归函数
def factorial(n):
	if n == 0:
		return 1  # 0 的阶乘为 1
	else:
		return n * factorial(n - 1)  # n 的阶乘为 n 乘 (n-1) 的阶乘

函数参数

一般来说,在函数内部直接修改形参的值不会影响实参。但是,如果传递给函数的是列表、字典、集合等可变序列,并且在函数内部使用索引或序列自身支持的方式为可变序列增加、删除元素或修改元素的值时,修改后的结果是可以反映到函数之外的,即不仅形参得到了修改,实参也得到了相应的修改。

如果一个函数需要以多种形式来接收参数,一般按照把位置参数放在最前面,然后是默认参数,接下来是一个星号的可变参数,最后是两个星号的可变参数的顺序。

位置参数

位置参数在调用函数时形参和实参的顺序必须严格一致,并且形参和实参的数量必须相同。

>>> def student(name, gender, age):
	return (name, gender, age)

>>> student('Leon', 'male', 22)
('Leon', 'male', 22)
>>> student()
TypeError: student() missing 3 required positional arguments: 'name', 'gender', and 'age'

默认参数

默认参数是指在定义函数时可以为形参设置默认值,若为形参设置了默认值,则可以不用必须为其传递实参,此时函数会直接使用函数定义时设置的默认值,当然也可以通过传递实参来替换默认值。需要注意的是,在定义带有默认参数的函数时,任何一个默认参数的右边都不能再出现没有默认值的普通位置参数。可以使用 函数名.__defaults__ 来查看函数所有默认参数的当前值,其返回值为一个元组。

>>> def student(name, gender, age, s_id='1901'):
	return (name, gender, age, s_id)

>>> student.__defaults__
('1901',)

多次调用函数并且不为默认参数传递值时,默认参数只在函数定义时进行一次初始化,后续的调用不会再初始化。这对于像列表等可变类型的默认参数可能会导致逻辑错误。因此,要避免使用列表等可变序列作为函数参数的默认值。

>>> def demo(item, li=[]):  # 默认参数为可变类型
	li.append(item)
	return li

>>> demo(5, [1, 2, 3, 4])
[1, 2, 3, 4, 5]
>>> demo('d', ['a', 'b', 'c'])  # 为默认参数传递了值,所以每次调用函数都会初始化
['a', 'b', 'c', 'd']
>>> demo('a')
['a']
>>> demo('b')  # 没有为默认参数传递值,所以这一次调用函数时默认参数没有初始化,即返回的列表还是上一次的列表,因此结果不是想要的 ['b'],而是 ['a', 'b']
['a', 'b']

# 对上述 demo() 函数的改进,不使用可变序列作为函数参数的默认值
>>> def demo(item, li=None):
	if li is None:  # 如果 li 为 None,则 li 赋值为空列表;如果 li 不为 None,则 item 会添加到现有的列表 li 中
		li = []
	li.append(item)
	return li

此外,若在定义函数时某个参数的默认值为另一个变量的值,那么该参数的默认值只依赖于函数定义时该变量的值,即函数的默认参数是在函数定义时确定值的,所以只会被初始化一次。

>>> i = 3
>>> def demo(n=i):  # 默认参数 n 的值取决于变量 i 的当前值
	return n

>>> demo()
3
>>> i = 5
>>> demo()  # 函数定义后再改变 i 的值不影响参数 n 的默认值
3
>>> def demo(n=i):  # 重新定义函数
	return n

>>> demo()
5

关键字参数

关键字参数主要指调用函数时参数的传递方式,与函数定义无关。通过关键字参数可以按照参数名字来传递值,明确指定哪个值传递给哪个参数,从而可以使实参的顺序与形参的顺序不一致,但不会影响传递结果。

>>> def student(name, gender, age, s_id='1901'):
	return (name, gender, age, s_id)

>>> student(name='Leon', gender='male', age=22)  # 按照关键字参数来传递参数的值
('Leon', 'male', 22, '1901')

可变参数

可变参数主要有两种形式:*args**kwargs,前者用于接收任意多个位置实参并将其放在元组中,后者用于接收类似于关键字参数的多个实参并将其放入字典中(args 和 kwargs 只是常用名字,可以取其他任意名字替代)。

>>> def demo(*args):
	return args

>>> demo(1, 2, 3)  # 接收多个位置参数并放在元组中
(1, 2, 3)

>>> def demo(**kwargs):  # 接收多个关键字参数并放在字典中
	return kwargs

>>> demo(x=1, y=2, z=3)
{'x': 1, 'y': 2, 'z': 3}

传递参数时的序列解包

传递参数时的序列解包是针对于实参来说。在调用含有多个位置参数的函数时,若实参为列表、元组、字典、集合等可迭代对象时,可以在实参名称前加一个星号 * 表示序列解包,Python 会自动将序列中的值依次传递给各个形参。若实参为字典,一个星号只能对其的键或值进行解包。若想要将字典的元素转换成类似于关键字参数的形式进行传递,则需要使用两个星号 ** 进行解包。但是,对于这种形式的序列解包,要求实参字典中的所有键都必须是函数的形参名称,或者与函数中两个星号的可变参数相对应。

>>> def student(name, gender, age):
	return f'{name} is {age} years old, and {name} is {gender}.'

>>> leon = ['Leon', 'male', 22]
>>> student(*leon)  # 对列表进行解包
'Leon is 22 years old, and Leon is male.'

>>> leon = ('Leon', 'male', 22)
>>> student(*leon)  # 对元组进行解包
'Leon is 22 years old, and Leon is male.'

>>> leon = {'name': 'Leon', 'gender': 'male', 'age': 22}
>>> student(*leon)  # 一个星号默认对字典的键进行解包
'name is age years old, and name is gender.'
>>> student(*leon.values())  # 对字典的值进行解包
'Leon is 22 years old, and Leon is male.'
>>> student(**leon)  # 对字典的元素进行解包
'Leon is 22 years old, and Leon is male.'

在调用函数时,如果对实参使用一个星号进行序列解包,那么这些解包后的实参将会被当作普通的位置参数来对待,并且会在关键字参数和使用两个星号进行序列解包的参数之前进行处理。

>>> def demo(a, b, c):
	return (a, b, c)

>>> demo(*(1, 2, 3))  # 正常序列解包
(1, 2, 3)
>>> demo(a=1, *(2, 3))  # 优先处理使用一个星号的序列解包(2 -> a,3 -> b),再处理关键字参数(1 -> a),所以会报错
TypeError: demo() got multiple values for argument 'a'
>>> demo(c=1, *(2, 3))  # 正常序列解包
(2, 3, 1)

变量作用域

变量作用域是指变量起作用的代码范围,不同作用域内的同名变量之间互不影响。在函数内部定义的变量一般为局部变量,在函数外部定义的变量为全局变量。不管是局部变量还是全局变量,在其被定义之前都无法访问。

当函数运行结束后,在函数内定义的局部变量将被自动删除而不可访问。在函数内使用 global 关键字声明的全局变量则仍然存在且可以访问。使用 global 声明的全局变量分为两种:

  1. 一个变量已在函数外定义,如果在函数内需要修改这个变量的值,并将修改的结果反映到函数之外,可以在函数内使用 global 明确声明要使用的变量。
  2. 在函数内直接使用 global 将一个变量声明为全局变量,如果在函数外没有定义该变量,在调用这个函数之后,会创建新的全局变量。
>>> def demo():
	global x  # 声明全局变量 x
	x = 3  # 全局变量
	y = 4  # 局部变量
	return (x, y)

>>> x = 5  # 在函数外定义全局变量 x
>>> x
5
>>> demo()  # 调用函数修改了全局变量 x 的值
(3, 4)
>>> x
3
>>> y  # 局部变量 y 在函数执行之后自动删除
NameError: name 'y' is not defined
>>> del x  # 删除全局变量 x
>>> x
NameError: name 'x' is not defined
>>> demo()  # 此次调用函数创建了全局变量 x
(3, 4)
>>> x
3

如果在某个作用域内有为变量赋值的操作,那么该变量将被认为是该作用域内的局部变量。如果局部变量与全局变量具有相同的名字,那么该局部变量会在自己的作用域内暂时隐藏同名的全局变量。

lambda 表达式

lambda 表达式用于声明匿名函数,即没有函数名字且临时使用。它可以包含多个参数,但只能有一个表达式。在表达式中可以调用其他函数,该表达式的计算结果相当于函数的返回值。其基本语法如下:

# 基本语法
lambda arguments: expression

# 等价于
def func(arguments):
	expression

# 也可以给 lambda 表达式起个名字
>>> f = lambda x, y, z: x + y + z
>>> f(3, 4, 5)
12

# 也支持默认参数
>>> g = lambda x, y=4, z=5: x + y + z
>>> g(1)
10
>>> g(1, z=3, y=2)  # 调用时使用关键字参数
6

lambda 表达式只能使用局部变量,若变量在外部作用域中定义,直接在表达式中使用时会出现错误,得不到预期的结果。

生成器函数

包含 yield 语句的函数可以用来创建生成器对象,这样的函数也称为生成器函数。yield 语句类似于 return 语句,都是用于从函数中返回值。return 语句一旦执行会立刻结束函数的运行,而 yield 语句执行后会返回一个值并暂停后续代码的执行,只有通过生成器对象的 __next__() 方法、内置函数 next()for 循环遍历等方式显式”索要“数据时才能恢复执行。

# 定义函数生成斐波那契数列
>>> def f():
	a, b = 1, 1
	while True:
		yield a  # 暂停执行,需要时再产生一个新元素
		a, b = b, a+b

>>> x = f()  # 创建生成器对象
>>> x
<generator object f at 0x00000293920A5890>
>>> for i in range(10):
	print(next(x), end=' ')
	
1 1 2 3 5 8 13 21 34 55

面向对象

创建类时用变量形式表示对象特征的称为数据成员,用函数形式表示对象行为的称为成员方法,数据成员和成员方法统称为类的成员。

Python 使用 class 关键字来定义类,类名的首字母习惯上大写。定义类之后,就可以用来实例化对象,并通过 对象名.成员 的方式来访问其中的数据成员或成员方法。

>>> class Car(object):
	def infor(self):
		return 'infor'

>>> car = Car()
>>> car.infor()
'infor'

可以使用内置函数 isinstance() 来测试一个对象是否是某个类的实例,或者使用 type() 查看对象类型。

>>> isinstance(car, Car)
True
>>> type(car)
<class '__main__.Car'>  # __main__ 表示 Car 类的来源为主程序,即 car 对象是主程序中 Car 类的实例

# 如果是用 from test import Car 导入Car类
>>> type(car)
<class 'test.Car'>

关键字 pass 用于表示空语句,常用于函数或类的定义、选择结构、循环结构和 with 块中,如果暂时没有确定如何实现某个功能,可以使用 pass 来占位。

类同样使用三引号来进行必要的注释。

数据成员与成员方法

私有成员与公有成员

私有成员在类的外部不能直接访问,一般是在类的内部进行访问和操作,或在类的外部通过调用对象的公有成员方法来访问。公有成员既可以在类的内部也可以在外部中使用。在定义类的成员时,如果成员名以两个(或更多)下划线开头但是不以两个(或更多)下划线结束表示是私有成员,否则就不是。不过,Python 并没有对私有成员提供严格的访问保护机制,通过 对象名._类名__私有成员名 也可以在外部访问私有成员,但是不推荐。

  1. _xxx 成员名以一个下划线开头,表示该成员是保护的,只有类对象和子类对象可以访问,应避免直接在外部访问,但 Python 不会阻止这个访问。使用一个或多个下划线开头的成员不能使用 from module import * 进行导入。
  2. __xxx 表示私有成员,一般只有父类的对象自己能访问。
  3. __xxx__ 表示用于实现某些特殊行为的特殊成员方法,详见特殊方法

数据成员

数据成员可以分为属于类的数据成员和属于对象(实例)的数据成员。属于类的数据成员是该类中所有对象共享的,不属于任何一个对象,在定义属于类的数据成员时一般不在任何一个成员方法中定义。属于对象的数据成员一般在构造方法 __init__() 中定义,同一个类的不同对象的数据成员之间互不影响。在主程序中或类的外部,类的数据成员属于类,可以通过类名或对象名进行访问;对象的数据成员属于实例(对象),只能通过对象名进行访问。

>>> class Student:
	address = 'ECUST'  # 类的数据成员
	
	def __init__(self, name):
		self.name = name  # 对象的数据成员

>>> s = Student('Leon')
>>> s.address  # 类的数据成员通过对象名访问
'ECUST'
>>> s.name  # 对象的数据成员通过对象名访问
'Leon'
>>> Student.address  # 类的数据成员通过类名访问
'ECUST'
>>> Student.name  # 对象的数据成员无法通过类名访问
AttributeError: type object 'Student' has no attribute 'name'

成员方法

在面向对象中,方法一般指与特定对象(实例)绑定的函数,通过对象调用方法时,对象本身将被作为第一个参数(即 self)自动传递过去。Python 类的成员方法可分为公有方法、私有方法、类方法、静态方法和抽象方法等。公有方法、私有方法和抽象方法属于对象的实例方法,实例方法的第一个形参总是 self 以代表当前对象(实例)。在实例方法中访问实例成员时需要以 self 为前缀,但在外部通过对象名调用方法时不需要传递这个参数。

类方法和静态方法都可以通过类名和对象名调用,但不能直接访问属于对象的成员,只能访问属于类的成员。并且这两种方法不属于任何实例,不会绑定的任何实例,也不依赖于任何实例的状态。类方法一般以 cls 作为第一个参数,表示该类本身。静态方法可以不接受任何参数(不需要用于代表类的实例的参数 self 和用于代表类的参数 cls)。

class Student:
	address = 'ECUST'

	def __init__(self, name='Leon', age=22):  # __init__() 是私有方法,当然也属于特殊方法
		self.s_id = None  # 这里在 self 后的 s_id、name 和 __age 是数据成员,前两者是公有成员可以外部访问,后者是私有成员  
		self.name = name
		self.__age = age

	def set_id(self, s_id):  # set_id() 是公有方法
		self.s_id = s_id
		return s_id

	def show_age(self):
		return self.__age  # 在实例方法中可以访问私有成员
		
	@classmethod  # 使用装饰器声明类方法
	def class_show_address(cls):
		return cls.address  # 只能访问类的成员
		
	@staticmethod  # 使用装饰器声明静态方法
	def static_show_address():  # 可以没有参数
		return Student.address

如果方法不需要访问或修改任何状态(不依赖于当前类的实例或类本身),声明为静态方法更合适;如果需要访问或修改类的状态,声明为类方法更合适;如果需要访问或修改实例的状态,则声明为实例方法更合适。

抽象方法一般在抽象类中定义,其在父类中声明但不实现,需要在继承的子类中实现,否则子类无法创建实例。可以通过 abc 模块来创建抽象类,抽象类不能用于创建实例,主要是作为其他类的父类而存在。

from abc import ABCMeta, abstractmethod

class Foo(metaclass=ABCMeta):  # or ABC,定义抽象类
	def f1(self):  # 普通实例方法
		pass
		
	@abstractmethod  # 抽象方法
	def f2(self):
		raise Exception('You must reimplement this method.')
	
class Bar(Foo):
	def f2(self):  # 必须在子类中重新实现父类中的抽象方法
		pass

属性

公开的数据成员可以在外部被随意的访问和修改,很难保证新数据的合法性,所以数据很容易被破坏,一般的解决方案为定义数据成员为私有,然后设计公开的成员方法来提供对私有成员进行访问和修改的操作。属性是一种特殊形式的成员方法,既可以像成员方法一样对值进行必要的检查,又可以像数据成员一样灵活地访问。通过使用装饰器 @property 可以创建只读属性。

>>> class Test:
	def __init__(self, value):
		self.__value = value  # 私有数据成员

	@property  # 定义只读属性
	def value(self):
		return self.__value

>>> t = Test(3)
>>> t.value  # value 被定义为属性后,访问的形式从成员方法(t.value())变为数据成员(t.value)
3
>>> t.value = 5  # 只读属性不允许修改值
AttributeError: can't set attribute
>>> del t.value  # 也不允许删除属性
AttributeError: can't delete attribute

通过 @property 的配套装饰器 @<attribute>.setter@<attribute>.deleter 可以修改和删除属性(<attribute> 是指所定义的属性的名称,即成员方法的名称)。

# 修改上面的类
>>> class Test:
	def __init__(self, value):
		self.__value = value  # 私有数据成员

	@property  # 定义只读属性
	def value(self):
		return self.__value

	@value.setter  # 定义 value 属性的 setter 方法
	def value(self, value):
		self.__value = value

	@value.deleter  # 定义 value 属性的 deleter 方法
	def value(self):
		del self.__value
		
>>> t = Test(3)
>>> t.value
3
>>> t.value = 5  # 调用 setter 修改属性的值
>>> t.value
5
>>> del t.value  # 调用 deleter 删除属性
>>> t.value  # 属性已删除,访问失败
AttributeError: 'Test' object has no attribute '_Test__value'
>>> t.value = 1  # 动态增加属性
>>> t.value
1

类与对象的动态性

在 Python 中可以动态地为自定义类和对象增加数据成员和成员方法。在为类和对象增加成员方法时,需使用 types.MethodType(func, class_name/instance_name) 将函数绑定到类或对象上,这样函数才能正确地接收 self 参数。

>>> class Car:
	price = 100  # 属于类的数据成员
	def __init__(self, c):
		self.color = c  # 属于对象的数据成员

>>> car = Car('red')
>>> Car.name = 'Benz'  # 动态增加类的数据成员
>>> print(Car.name, car.name)
Benz Benz
>>> car.speed = 50  # 动态增加对象的数据成员
>>> car.speed
50
>>> def set_length(self, l):
	self.length = l

>>> import types
>>> car.set_length = types.MethodType(set_length, car)  # 动态增加对象的成员方法
>>> car.set_length(150)
>>> car.length
150
>>> def set_width(self, w):
	self.width = w

>>> Car.set_width = types.MethodType(set_width, Car)  # 动态增加类的成员方法
>>> car.set_width(80)  # 对象调用类的新增加的方法
>>> car.width
20

可以通过内置函数 dir() 来查看一个对象的所有属性(即数据成员)和方法,会返回一个列表,其中包含了属性和方法的名称,也包含了一些以双下划线开头和结尾的特殊方法。若只想查看一个对象的自定义属性,可以使用内置函数 vars(),会返回一个字典,其中包含了属性的名称和对应的值,不过它只能返回对象的实例属性,不能返回对象的类属性。

>>> dir(car)  # 会返回定义的类属性 price 和动态增加的类属性 name 和 width,以及动态增加的类方法 set_width
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'color', 'length', 'name', 'price', 'set_speed', 'set_width', 'speed', 'width']
>>> vars(car)  # 只返回对象的实例属性
{'color': 'red', 'length': 150, 'set_speed': <bound method set_speed of <__main__.Car object at 0x00000293925A31C0>>, 'speed': 50}

继承与多态

继承

子类可以继承父类的公有成员,但不能继承其私有成员。如果需要在子类中调用父类的方法,可以使用内置函数 super().method(arguments)super(__class__, <first argument>) 具有两个参数,第一个参数通常是子类名,第二个参数通常是子类的一个实例。如果不提供任何参数,等价于 super(当前类, 当前实例对象(self))。Python 也支持多继承,即一个子类可以继承多个父类。

class Person(object):
	def __init__(self, name=''):
		self.__name = None
		self.set_name(name)

	def set_name(self, name):
		if not isinstance(name, str):
			raise TypeError('name must be a string.')
		self.__name = name

class Teacher(Person):
	def __init__(self, name, age=22):
		super().__init__(name)  # 调用父类的构造方法 __init__(),也可以用super(Teacher, self).__init__(name)
		self.__age = None
		self.set_age(age)

	def set_age(self, age):
		if not isinstance(age, int):
			raise TypeError('age must be an integer.')
		self.__age = age

多态

多态是指父类的同一个方法在不同子类的对象中具有不同的表现和行为。

特殊方法

Python 中最常用的特殊方法是构造方法 __init__(),用于初始化工作,在实例化对象时被自动调用和执行。如果没有设计构造方法,Python 会提供一个默认的构造方法用于必要的初始化。自定义的特殊方法可覆盖对应的运算符或内置函数的功能。如在自定义类时重写了 __add__() 方法,那么在对当前对象使用加号 + 时,Python 会自动调用 __add__() 方法所定义的功能,而不是原来 + 的功能。更多特殊方法详见 https://docs.python.org/3/reference/datamodel.html#special-method-names

>>> class Test:
	def __init__(self, value):
		self.value = value

	def __add__(self, other):
		if isinstance(other, Test):  # 如果 other 也属于 Test 类
			return Test(self.value - other.value)  # 重写加号 + 定义的方法
		else:
			return Test(self.value - other)

>>> x = Test(3)
>>> y = x + 2  # 实际运算为 3 - 2
>>> print(x.value, y.value)
3 1

文件操作

按照数据的组织形式,可以把文件分为文本文件二进制文件两大类。文本文件存储的是常规字符串,通常每行以换行符 \n 结尾。扩展名为 txt、log、ini 的文本都属于文本文件。二进制文件把信息以字节串的形式进行存储。

open()

内置函数 open() 可以用指定模式打开指定文件并创建文件对象,其语法为 open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True)

  • 参数 file 用于指定要打开或创建的文件的名称,如果文件不在当前目录,可以使用相对路径(同级目录:./;上级目录:../;上两级目录:../../)或绝对路径(示例:r'C:\Users\Username\Documents\file.txt')。

  • 参数 mode 用于指定打开文件后的处理方式。

模式 说明
'r' 只读模式(默认模式),如果文件不存在会抛出异常
'w' 写模式,如果文件已存在,自动清空原有内容
'x' 写模式,创建新文件,如果文件已存在会抛出异常
'a' 追加模式,从文件末尾追加新内容,不会覆盖文件中原有内容
'b' 二进制模式,一般搭配其他模式组合使用,如 'rb''wb'等,不允许指定 encoding 参数
't' 文本模式(默认模式,可省略)
'+' 同时读写模式,一般搭配其他模式组合使用,如 'r+''rb+'

'r+' 模式光标在开头,不会创建新文件,也不会覆盖文件原有内容,用于想要读取内容时也可以添加新内容;'w+''a+' 模式光标在末尾,文件不存在会创建新文件,不同处在于前者会覆盖文件原有内容,而后者不会。

  • 参数 encoding 用于指定文件的字符编码方式,默认为 utf-8

文件对象的属性与方法

open() 会返回一个可迭代的文件对象,通过该文件对象可以对文件进行读写操作。

常用属性 说明
closed 判断文件是否关闭,若文件已关闭则返回 True
mode 返回文件的打开模式
name 返回文件的名称
buffer 返回当前文件的缓冲区对象

文件读写操作相关的方法都会自动改变文件光标的位置。例如,读取一个文件的 10 个字符后,再次读取字符的时候会从第 11 个字符的位置开始。

常用方法 说明
close() 把缓冲区的内容写入文件,同时关闭文件,并释放文件对象
flush() 把缓冲区的内容写入文件,但不关闭文件
read([size]) 从文本文件中读取 size 个字符的内容作为结果返回,或从二进制文件中读取字节并返回。若省略 size 则表示读取所有内容,若 size 大于实际有效内容长度则在文件尾自动结束
readline() 读取一行内容作为结果返回
readlines() 把文本文件中的每行文本作为一个字符串存入列表中并返回,大文件不建议使用
seek(offset[, whence]) 把文件光标移动到新位置,offset 表示相对于 whence 的位置,即光标要移动的字符数(正数表示向文件尾方向移动,负数则相反)。whence 为 0 表示从文件头开始计算,whence 为 1 表示从当前位置开始计算,whence 为 2 表示从文件尾开始计算,默认为 0
seekable() 测试当前文件是否支持随机访问,若不支持则无法调用 seek()tell()
tell() 返回文件光标当前的位置
write(s) 把字符串 s 的内容写入文件中,若要换行,则需在字符串中添加换行符 xxx\n
writelines(s) 把字符串列表写入文件中

seek(0) 将光标移到文件头,seek(0, 2) 将光标移到文件尾。此外,seek() 后再 read() 可能得不到期望的结果,是因为缓存的原因。

上下文管理语句 with

关键字 with 可以自动管理资源,不论因为什么原因跳出 with 块,总能保证文件被正确关闭,不需要再用 close() 方法来关闭文件。

with open(filename, mode, encoding) as f:
	# 通过文件对象 f 进行文件的读写

JSON 格式文本文件的读写

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,一般用于提升网络传输速率。Python 的标准库 json 提供对 JSON 的支持。通过 json.dumps()json.loads() 将 Python 对象转换成 JSON 格式的字符串。通过 json.dump()json.load() 将 Python 对象转换成 JSON 格式的字符串并写入到文件中或从文件中读取 JSON 格式的字符串。

>>> import json
>>> x = [1, 2, 3]
>>> json.dumps(x)  # 对列表进行 JSON 格式的编码
'[1, 2, 3]'
>>> json.loads(_)  # 解码,单个下划线 _ 在交互式环境中表示上一次操作的结果
[1, 2, 3]
>>> with open('test.txt', 'w') as f:
	json.dump(x, f)  # 写入文件
>>> with open('test.txt', 'r') as f:
	print(json.load(f))  # 从文件中读取

二进制文件的读写

pass

  1. 本教程参考书目:Python 程序设计基础(第 2 版) - 清华大学出版社 - 董付国 编著。 

  2. [, start[, end]] 方括号中的参数表示该参数是可选的,而非必需的。其中 end 参数在 start 参数的方括号中,表示只有提供了 start 参数才能提供 end 参数。