本节主要内容:

  • 列表推导式和生成器表达式
  • 元组的两种用法——记录和不可变列表
  • 序列拆包和序列模式
  • 读写切片
  • 专门的序列类型,例如数组和队列

1.1 内置序列类型概览

可简单分为:

  • 容器序列:可存放不同类型的项,其中包括嵌套容器。例如:list、tuple和collections.deque。
  • 扁平序列:可存放一种简单类型的项。例如:str、bytes和array.array。
    另外,还可按可变性对序列分类。
  • 可变序列:例如list、bytearray、array.array和collections.deque。
  • 不可变序列:例如tuple、str和bytes。

1.2列表推导式和生成器推导式

1.2.1可读性

示例一

1
2
3
4
5
6
7
8
9
symbols = '$¢£¥€¤'
code = []
for symbol in symbols:
code.append(ord(symbol))

print(code)
'''
output:[36, 162, 163, 165, 8364, 164]
'''

示例二

1
2
3
4
5
6
7
symbols = '$¢£¥€¤'
code = [ord(symbol) for symbol in symbols]

print(code)
'''
output:[36, 162, 163, 165, 8364, 164]
'''

如果不打算使用生成的列表,最好不要使用列表推导式。另外,列表表达式应保持简短。
列表表达式筛选和转换序列或其他可迭代类型的项,以此构建列表。综合使用filter和map两个内置函数也能达到这样的效果。

1.2.2 列表表达式与map和filter比较

示例 使用列表表达式和map/filter组合构建同一个列表

1
2
3
4
5
symbols = '$¢£¥€¤'
beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
print(beyond_ascii)
beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))
print(beyond_ascii)

性能比较如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import timeit


TIMES = 10000


SETUP = """
symbols = '$¢£¥€¤'
def non_ascii(c):
return c > 127

"""

def clock(label, cmd):
res = timeit.repeat(cmd, setup=SETUP, number=TIMES)
print(label, *(f'{x:.3f}' for x in res))


clock('listcomp :', '[ord(s) for s in symbols if ord(s) > 127]')
clock('listcomp + func :', '[ord(s) for s in symbols if non_ascii(ord(s))]')
clock('filter + lambda :', 'list(filter(lambda c: c > 127, map(ord, symbols)))')
clock('filter + func :', 'list(filter(non_ascii, map(ord, symbols)))')
'''
output: listcomp : 0.009 0.010 0.008 0.008 0.009
listcomp + func : 0.014 0.013 0.011 0.014 0.011
filter + lambda : 0.013 0.014 0.012 0.010 0.010
filter + func : 0.010 0.009 0.010 0.009 0.009
'''

1.2.3笛卡尔积

示例 使用列表表达式计算笛卡尔积

先按颜色再按尺寸排列,生成一个元组列表

1
2
3
4
5
6
7
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
tshirts = [(color, size) for color in colors for size in sizes]
tshirts
'''
output:[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]
'''

双层循环打印

1
2
3
4
5
6
for color in colors:
for size in sizes:
print((color, size))
'''
output:('black', 'S') ('black', 'M') ('black', 'L') ('white', 'S') ('white', 'M') ('white', 'L')
'''

先按尺寸再按颜色排列,只需调整for子句的顺序。

1
2
3
4
5
6
shirts = [(color, size) for size in sizes
for color in colors]
tshirts
'''
output:[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]
'''

列表表达式的作用很单一,就是构建列表。如果想使用其它类型的序列,则应当使用生成器表达式。

1.3.4 生成器表达式

生成器表达式的句法跟列表表达式几乎一样,只不过把方括号换为圆括号而已。
示例

1
2
3
4
5
symbols = '$¢£¥€¤'
tuple(ord(symbol) for symbol in symbols)
'''
output:(36, 162, 163, 165, 8364, 164)
'''
1
2
3
4
5
import array
array.array('I', (ord(symbol) for symbol in symbols))
'''
output:array('I', [36, 162, 163, 165, 8364, 164])
'''

示例 使用生成器表达式计算笛卡尔积

1
2
3
4
5
6
7
8
9
10
11
12
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
for tshirt in (f'{s} {c}' for c in colors for s in sizes):
print(tshirt)
'''
output:S black
M black
L black
S white
M white
L white
'''

注意,生成器表达式逐个产出项。这个示例不会构建包含六个T恤组合的列表。

1.3 元组不仅仅是不可变列表

1.3.1 用作记录

用元组存放记录,元组中的一项对应一个字段的数据,项的位置决定数据的意义。
示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
lax_coordinates = (33.9425, -118.408056)
city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)
traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]
for passport in sorted(traveler_ids):
print('%s/%s' % passport)
for country, _ in traveler_ids:
print(country)
'''
output:
BRA/CE342567
ESP/XDA205856
USA/31195855
USA
BRA
ESP
'''

1.3.2 用作不可变列表

有如下两个好处

  1. 意图清晰:见到元组就知道他的长度永不可变
  2. 性能优越:相较于列表,元组占用的内存更小

1.4 序列和可迭代对象拆包

最典型的是并行赋值。

1
2
3
4
5
6
7
lax_coordinates = (33.9425, -118.408056)
latitude, longitude = lax_coordinates # unpacking
latitude
'''
output:
33.9425
'''

1.4.1 使用*获取余下的项

1
2
3
4
5
6
a, b, *rest = range(5)
a, b, rest
'''
output:
(0, 1, [2, 3, 4])
'''

1.4.2 在函数调用和序列文本中使用 * 解包

在函数调用过程中可以多次使用*。

1
2
3
4
5
6
7
8
def fun(a, b, c, d, *rest):
return a, b, c, d, rest

fun(*[1, 2], 3, *range(4, 7))
'''
output:
(1, 2, 3, 4, (5, 6))
'''

解释:
在调用fun时,使用了星号*操作符来展开列表和范围对象为位置参数:

  • *[1, 2]将列表[1, 2]展开为两个位置参数,分别赋值给ab
  • 3直接作为位置参数,赋值给c
  • *range(4, 7)range(4, 7)对象展开为三个位置参数,分别是4, 5, 6。其中4赋值给d,而56作为额外的位置参数,被包装进rest元组。
    因此,函数调用fun(*[1, 2], 3, *range(4, 7))的返回值将是(1, 2, 3, 4, (5, 6))。这里a=1, b=2, c=3, d=4,而所有额外的位置参数56被包装在rest元组中。

1.4.3 嵌套拆包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
metro_areas = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('São Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]

def main():
print(f'{"":15} | {"latitude":>9} | {"longitude":>9}')
for name, _, _, (lat, lon) in metro_areas:
if lon <= 0:
print(f'{name:15} | {lat:9.4f} | {lon:9.4f}')

if __name__ == '__main__':
main()
'''
output:
| latitude | longitude
Mexico City | 19.4333 | -99.1333
New York-Newark | 40.8086 | -74.0204
São Paulo | -23.5478 | -46.6358
'''

1.5 序列模式匹配

match/case处理序列示例

1
2
3
4
5
6
7
8
9
10
11
12
# def handle_command(self, message):
# match message:
# case ['BEEPER', frequency, times]:
# self.beep(times, frequency)
# case ['NECK', angle]:
# self.rotate_neck(angle)
# case ['LED', ident, intensity]:
# self.leds[ident].set_brightness(ident, intensity)
# case ['LED', ident, red, green, blue]:
# self.leds[ident].set_color(ident, red, green, blue)
# case _:
# raise InvalidCommand(message)

match关键字后面的表达式是匹配对象,即各个case子句中的模式尝试匹配的数据。

示例:使用match/case重写前面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
metro_areas = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('São Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]

def main():
print(f'{"":15} | {"latitude":>9} | {"longitude":>9}')
for record in metro_areas:
match record:
case [name, _, _, (lat, lon)] if lon <= 0:
print(f'{name:15} | {lat:9.4f} | {lon:9.4f}')
main()
'''
output:
| latitude | longitude
Mexico City | 19.4333 | -99.1333
New York-Newark | 40.8086 | -74.0204
São Paulo | -23.5478 | -46.6358
'''

1.6 切片

1.6.1 为什么切片和区间排除最后一项

  1. 在仅指定停止位置时,容易判断切片或区间的长度;
  2. 同时指定起始和终止位置时,容易计算切片或区间的长度,做个减法即可:stop-start;
  3. 方便在索引x处把一个序列拆分成两个部分而不重叠。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
l = [10, 20, 30, 40, 50, 60]
l[:2] # split at 2
'''
output:[10, 20]
'''
l[2:]
'''
output:[30, 40, 50, 60]
'''
l[:3] # split at 3
'''
output:[10, 20, 30]
'''
l[3:]
'''
output:[40, 50, 60]
'''

1.6.2 切片对象

我们可以使用s[a:b:c]句法指定步距c,让切片操作跳过部分项。

1
2
3
4
5
6
7
8
9
10
11
12
13
s = 'bicycle'
s[::3]
'''
output:'bye'
'''
s[::-1]
'''
output:'elcycib'
'''
s[::-2]
'''
output:'eccb'
'''

a:b:c表示法只在[]内部有效,得到的结果是一个切片对象:slice(a,b,c)。

1.6.3 多维切片和省略号

[]运算符可以接受多个索引或切片,以逗号分隔。负责处理[]运算符的特殊方法__getitem__和__setitem__把接收到的a[i,j]中的索引当作元组。
省略号写作三个句点(…)。你可以将省略号当作参数传给函数,也可以写在切片规范里,例如f(a,…,z)或a[i,…]。

1.6.4 为切片赋值

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
l = list(range(10))
l
'''
output:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
'''
l[2:5] = [20, 30]
l
'''
output:[0, 1, 20, 30, 5, 6, 7, 8, 9]
'''
del l[5:7]
l
'''
output:[0, 1, 20, 30, 5, 8, 9]
'''
l[3::2] = [11, 22]
l
'''
output:[0, 1, 20, 11, 5, 22, 9]
'''

1.7 使用+和*处理序列

+的两个运算对象必须是同一种序列,而且都不可修改,拼接的结果是一个同类型的新序列。
如果想多次拼接,可以乘一个整数。

1
2
3
4
5
6
7
8
9
l = [1, 2, 3]
l * 5
'''
output:[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
'''
5 * 'abcd'
'''
output:'abcdabcdabcdabcdabcd'
'''