目录
一、列表推导式和生成器表达式
列表推导式(目标是列表)和生成器表达式(目标是其他序列类型)都可以快速构建一个序列。
1 列表推导式
1.1 对可读性的影响
举例说明利用列表推导式写的代码更易于理解、意图明确
# 基于一个字符串构建一个Unicode码点列表
symbols = '@#$%^&*'
codes = []
for symbol in symbols:
codes.append(ord(symbol))
print(codes)
# output: [64, 35, 36, 37, 94, 38, 42]
# 使用列表推导式基于一个字符串构建一个Unicode码点列表
codes = [ord(symbol) for symbol in symbols]
print(codes)
# output: [64, 35, 36, 37, 94, 38, 42]
注意:滥用列表推导式写出的代码也不一定易于理解,如果并不打算使用生成的列表,那就不要使用列表推导式句式。
1.2 应用
使用列表推导式构建一个列表,其中包含两个或更多列表中的所有项组成的元组。
举例:使用列表推导式计算笛卡尔积
# 生成一个列表,其中包含2种颜色和3种尺寸
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
# 先按颜色再按尺寸排列
tshirts = [(color, size) for color in colors
for size in sizes]
print(tshirts)
# output: [('black', 'S'), ('black', 'M'), ('black', 'L'),
# ('white', 'S'), ('white', 'M'), ('white', 'L')]
for color in colors:
for size in sizes:
print((color, size))
# output:
# ('black', 'S')
# ('black', 'M')
# ...
# 先按尺寸再按颜色排列
tshirts = [(color, size) for size in sizes
for color in colors]
print(tshirts)
# output:[('black', 'S'), ('white', 'S'), ('black', 'M'),
# ('white', 'M'), ('black', 'L'), ('white', 'L')]
1.3 与map、filter比较
列表表达式涵盖了map和filter两个函数的功能,相比lambda表达式更易理解
补充:map函数和filter函数的用法
1) filter(function,iterable):返回一个迭代器,接收两个参数,第一个为函数,第二个为序列。序列的每个元素作为参数传递给函数进行判断,函数返回True/False,最后将返回True的元素放入新列表。
2) map(function,iterable,...):返回一个迭代器,可以传入一个或多个序列,第一个参数function以参数序列中的每一个元素调用function函数,返回包含每次function函数返回值的新列表
举例:使用列表推导式和map、filter组合构建同一个列表
symbols = '@#$%^&*'
beyond_ascii = [ord(s) for s in symbols if ord(s) < 50]
print(beyond_ascii)
# output: [35, 36, 37, 38, 42]
beyond_ascii = list(filter(lambda c: c < 50, map(ord, symbols)))
print(beyond_ascii)
# output: [35, 36, 37, 38, 42]
2 生成式表达式
如果需要生成其他类型的序列应该使用生成式表达式。虽然列表推导式也可以生成元组、数组或其他类型的序列,但生成器表达式占用内存更少,因为生成器表达式使用迭代器逐个产出项,而不是构建整个列表。
生成器表达式的句法与列表推导式几乎一样,只是将方括号换成圆括号了。
举例:使用生成器表达式构建一个元组和一个数组
symbols = '@#$%^&*'
print(tuple(ord(s) for s in symbols))
# output: (64, 35, 36, 37, 94, 38, 42)
import array
print(array.array('I', (ord(s) for s in symbols)))
# output: array('I', [64, 35, 36, 37, 94, 38, 42])
3 局部作用域
python3中的列表推导式、生成器表达式、以及类似的集合推导式、字典推导式,for子句中赋值的变量在局部作用域内。但是,使用海象运算符 := 赋值的变量,其作用域可以扩宽到函数内。
x = 'ABC'
codes = [ord(x) for x in x]
print(x)
# output: ABC
print(codes)
# output: [65, 66, 67]
codes = [last := ord(c) for c in x]
print(last)
# output: 67 ->last依然可以访问
print(c)
# output: NameError: name 'c' is not defined
# ->c消失了,因为它只存在列表推导式的内部
二、元组的两种用法
三、拆包
拆包的特点是不用自己动手通过索引从序列中提取元素,这样减少了出错的可能,拆包的目标可以是任何可迭代对象
1 并行赋值
最明显的拆包形式是并行赋值--把可迭代对象中的项赋值给变量元组
lax_coordinate = (33.9425, -118.408056)
latitude, longitude = lax_coordinate # 拆包
print(latitude)
# output: 33.9425
print(longitude)
# output: -118.408056
2 变量值对调
利用拆包还可以轻松对调两个变量的值,省掉中间临时值
b, a = a, b
3 序列字面量中使用*拆包
print((*range(4), 4))
# output: (0, 1, 2, 3, 4)
print([*range(4), 4])
# output: [0, 1, 2, 3, 4]
print({*range(4), 4, *(5, 6, 7)})
# output: {0, 1, 2, 3, 4, 5, 6, 7}
4 函数调用
定义函数时可以使用*args捕获余下的任意数量的参数
def fun(a, b, c, d, *rest):
return a, b, c, d, rest
print(fun(*[1, 2], 3, *range(4, 7)))
# output: (1, 2, 3, 4, (5, 6))
5 嵌套拆包
拆包的对象可以嵌套,如:(a, b, (c, d)),如果值的嵌套结构是相同,则python能正确处理。
四、切片
python中,列表、元组、字符串等所有序列类型都支持切片操作。
1 切片和区间排除最后一页
这是一种python分隔约定,这样做的好处有:
1) 当只指定停止位置时,容易判断切片或区间的长度,如:range(3)和my_list[:3]的长度均产生3项;
2)同时指定起始和停止位置时,容易计算长度,做减法即可:stop-start;
3)方便在索引x处把一个序列拆分成两部分而不产生重合,如:my_list[:x]和my_list[x:]
2 切片对象
我们可以使用s[a:b:c]来指定步距c,让切片跳过部分项,a:b:c表示法,只在[]内部有效,得到的结果是一个切片对象:slice(a, b, c)
使用:从纯文本形式的发票中提取商品信息
invoice = '''
0 6 40 52 55
1909 pimoroni piBrlla $17.50 3 $52.50
1489 6mm Tactille switch x20 $4.95 2 $9.90
1510 Panavise Jr. -PV-201 $28.00 1 $28.00
1601 piTFT Mini Kit 320x240 $34.95 1 $34.95
'''
SKU = slice(0, 6)
DESCRIPTION = slice(6, 40)
UNIT_PRICE = slice(40, 52)
QUANTITY = slice(52, 55)
ITEM_TOTAL = slice(55, None)
line_item = invoice.split('\n',)[2:]
for item in line_item:
print(item[UNIT_PRICE], item[DESCRIPTION])
# output:
# $17.50 pimoroni piBrlla
# $4.95 6mm Tactille switch x20
# $28.00 Panavise Jr. -PV-201
# $34.95 piTFT Mini Kit 320x240
3 省略号
省略号是一个内置常量,写作三个点(...),python解释器把它识别为一个记号。省略号是Ellipsis对象的别名,而Ellipsis对象是ellipsis类的单例。
print(type(...))
# output: <class 'ellipsis'>
使用:可以作为参数传给函数,也可以写在切片规范中,如:f(a, ..., z)或a[i:...]
4 为切片赋值
在赋值语句左侧使用切片表示法,或作为del语句的目标,可以就地移植、切除、修改可变序列
>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
>>> l
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11, 22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = 100
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: can only assign an iterable
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9]
注意:如果赋值目标是一个切片,那么右边必须是一个可迭代对象,即使只有一项
五、使用+和*处理序列
使用+和*实现拼接操作,其中+的运算对象必须是同一种序列。+和*始终创建一个新对象,绝不更改操作数
1 构建嵌套列表
一个列表中嵌套3个长度为3的列表,可以表示井字游戏棋盘
board = [['_'] * 3 for i in range(3)]
print(board)
# output: [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
board[1][2] = 'X'
print(board)
# output: [['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
注意:不能在一个列表中3次引用同一个列表
weird_board = [['_'] * 3] * 3
print(weird_board)
# output: [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
weird_board[1][2] = 'O'
print(weird_board)
# output: [['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]
在第2行第3列放‘O’是就会发现所有行引用的都是同一个对象
2 增值运算符处理序列
根据第一个操作数而定,增值运算符+=和*=的行为差异较大。
背后支持 += 运算符的是特殊方法__iadd__(就地相加),以简单的表达式来说明:
a += b
如果a实现了__iadd__,那就会调用它,如果a是可变序列,则就地修改a(行为类似a后面追加b);如果a没有实现__iadd__方法,那么表达式的作用就等同于a = a + b,先求解a + b,再把得到的新对象绑定到a上。因此a绑定的对象可能变了,也可能没变。
对于可变序列最好实现__iadd__方法,实现就地修改;对于不可变序列,显然不能就地修改。
以上内容也适用于通过__imul__实现的*=运算符。下面使用*=运算符分别处理一个可变序列和一个不可变序列:
>>> l = [1, 2, 3]
>>> id(l)
1285878263232
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l) # 乘法运算之后,列表还是同一个对象
1285878263232
>>> t = (1, 2, 3)
>>> id(t)
1285878263424
>>> t *= 2
>>> t
(1, 2, 3, 1, 2, 3)
>>> id(t) # 乘法运算之后,创建了一个新元组
1285878203584
对不可变序列重复拼接效率低下,因为解释器必须复制整个目标序列,创建一个新序列,包含拼接的项,而不是简单追加新项。
注意:增量赋值不是原子操作
六、排序函数list.sort与sorted
list.sort方法就地排序列表,不创建副本,返回值为None。它更改了接收者,没有创建新列表。
内置函数sorted与之相反,返回创建的新列表。该函数接收任何可迭代对象作为参数。包括不可变序列和生成器。
list.sort和sorted均接收两个可选的关键字参数:
1)reverse:值为True时,降序返回项;默认值为False
2)key:一个值接受一个参数的函数,应用到每一项上,作为排序的依据。如:排序字符串时,key = len表示按字符长度排除各个字符串,key = str.lower表示执行不区分大小写的排序。默认为恒等函数。
举例:
>>> fruits = ['grade', 'raspberry', 'apple', 'banana']
>>> sorted(fruits)
['apple', 'banana', 'grade', 'raspberry']
>>> fruits # 查看原来的列表发现并没有改动
['grade', 'raspberry', 'apple', 'banana']
>>> sorted(fruits, reverse=True)
['raspberry', 'grade', 'banana', 'apple']
>>> sorted(fruits, key=len)
['grade', 'apple', 'banana', 'raspberry']
>>> sorted(fruits, key=len, reverse=True)
['raspberry', 'banana', 'grade', 'apple'] #grade与apple的相对位置表明了排序算法很稳定
>>> fruits
['grade', 'raspberry', 'apple', 'banana']
>>> fruits.sort() # 就地排序,返回None,被控制台省略了
>>> fruits # 原来的列表有序了
['apple', 'banana', 'grade', 'raspberry']
这个例子还表明了Python的排序算法是稳定的(即能保留比较时相等两项的相对顺序)