【流畅的Python】第二章:丰富的序列——《Fluent Python》学习笔记

发布于:2024-12-18 ⋅ 阅读:(105) ⋅ 点赞:(0)

丰富的序列

GitHub仓库链接,同步更新。

1 内置序列类型概览

Python 标准库提供了丰富的序列类型,可以大致分为以下几类:

  • 按序列类型分
    • 容器序列:可存放不同类型的项,例如列表(list)、元组(tuple)和双端队列(deque)。
    • 扁平序列:可存放一种简单类型的项,例如字符串(str)、字节序列(bytes)和数组(array.array)。
  • 按可变性来分:
    • 可变序列:可修改其内容,例如列表(list)和字节缓冲区(bytearray)。
    • 不可变序列:内容不可修改,例如元组(tuple)和字符串(str)。

1.1 容器序列

# 容器序列
# 列表(list)
my_list = [1, "hello", 3.14]
print(my_list)  # 输出: [1, "hello", 3.14]

# 元组(tuple)
my_tuple = (1, "hello", 3.14)
print(my_tuple)  # 输出: (1, "hello", 3.14)

# 双端队列(deque)
from collections import deque
my_deque = deque([1, "hello", 3.14])
print(my_deque)  # 输出: deque([1, "hello", 3.14])

1.2 扁平序列

# 字符串(str)
my_string = "hello"
print(my_string)  # 输出: "hello"

# 字节序列(bytes)
my_bytes = b"hello"
print(my_bytes)  # 输出: b"hello"

# 数组(array.array)
import array
my_array = array.array('i', [1, 2, 3])
print(my_array)  # 输出: array('i', [1, 2, 3])

1.3 可变序列

# 列表(list)
my_list = [1, 2, 3]
my_list.append(4)
print(my_list)  # 输出: [1, 2, 3, 4]

# 字节缓冲区(bytearray)
my_bytearray = bytearray(b"hello")
my_bytearray.append(ord('!'))
print(my_bytearray)  # 输出: bytearray(b"hello!")

# 双端队列(deque)
from collections import deque
my_deque = deque([1, 2, 3])
my_deque.appendleft(0)
print(my_deque)  # 输出: deque([0, 1, 2, 3])

1.4 不可变序列

# 元组(tuple)
my_tuple = (1, 2, 3)
# my_tuple[0] = 0  # 这行代码会引发 TypeError,因为元组是不可变的

# 字符串(str)
my_string = "hello"
# my_string[0] = 'H'  # 这行代码会引发 TypeError,因为字符串是不可变的

# 字节序列(bytes)
my_bytes = b"hello"
# my_bytes[0] = ord('H')  # 这行代码会引发 TypeError,因为字节序列是不可变的

了解这些类型之间的区别和联系,有助于我们根据实际需求选择合适的序列类型。

2 列表推导式和生成器表达式

2.1 列表推导式对可读性的影响

列表推导式提供了一种简洁的遍历和生成列表的方法。例如,生成一个平方数列表:

squares = [x**2 for x in range(5)]
print(squares)  # 输出: [0, 1, 4, 9, 16]

2.2 与map和filter的比较

列表推导式可以替代map和filter函数,提供更清晰的代码结构。例如,使用列表推导式筛选和转换序列中的项:

# 使用列表推导式
beyond_ascii = [ord(s) for s in '$¢£¥€¤' if ord(s) > 127]

# 使用map和filter
beyond_ascii = list(filter(lambda c: ord(c) > 127, map(ord, '$¢£¥€¤')))

且列表推导式在代码执行时间上也更快。

2.3 笛卡尔积

列表推导式可以根据两个或更多可迭代对象的笛卡儿积构建列表。例如,生成2种颜色和3种尺寸的T恤衫组合:

# 示例:使用列表推导式计算笛卡儿积
colors = ['red', 'green']
sizes = ['S', 'M']
cartesian_product = [(color, size) for color in colors for size in sizes]
print(cartesian_product)  # 输出: [('red', 'S'), ('red', 'M'), ('green', 'S'), ('green', 'M')]

2.4 生成器表达式

生成器表达式占用的内存更少,因为它逐个产出项而不是构建整个列表。例如,使用生成器表达式构建一个元组:

symbols = '$¢£¥€¤'
tuple(ord(symbol) for symbol in symbols)

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

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

3.1 用作记录

元组可以用作记录,其中元组中的一项对应一个字段的数据。例如,存储一个学生的相关信息:

# 示例:元组用作记录
student = ('Alice', 21, 'Physics')
print(f"{student[0]} studies {student[2]} and is {student[1]} years old.")

3.2 用作不可变列表

元组作为不可变列表使用时,具有意图清晰和性能优越的优点。例如,元组在内存中占用的空间比列表小:

a = (10, 'alpha', [1, 2])
b = a
b[-1].append(99)

3.3 列表和元组方法的比较

元组支持所有不涉及增删项的列表方法。例如,元组不支持__reversed__方法,但reversed(my_tuple)依然可以工作。

4 序列和可迭代对象拆包

4.1 使用*获取余下的项

在并行赋值中,可以使用*来捕获余下的项。例如:

# 示例:使用 * 操作符拆包
first, *middle, last = range(10)
print(first, middle, last)  # 输出: 0 [1, 2, 3, 4, 5, 6, 7, 8] 9

4.2 在函数调用和序列字面量中使用*拆包

fun(*[1, 2], 3, *range(4, 7))

4.3 嵌套拆包

metro_areas = [
    ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
    # 其他城市
]
for name, _, _, (lat, lon) in metro_areas:
    print(f'{name} - Latitude: {lat}, Longitude: {lon}')

5 序列模式匹配

Python 3.10引入了模式匹配,允许使用match/case语句对序列进行结构化匹配。例如:

# 示例:匹配嵌套序列
def match_sequence(data):
    match data:
        case [1, 2, *rest]:
            return f"Starts with 1, 2 and the rest is {rest}"
        case _:
            return "No match"

print(match_sequence([1, 2, 3, 4]))  # 输出: Starts with 1, 2 and the rest is [3, 4]

6 切片

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

  • 在仅指定停止位置时,容易判断切片或区间的长度。例如,range(3) my_list[:3] 都只产生 3 项
  • 同时指定起始和停止位置时,容易计算切片或区间的长度,做个减法即可:stop - start
  • 方便在索引 x 处把一个序列拆分成两部分而不产生重叠,直接使用my_list[:x] my_list[x:] 即可。

6.2 切片对象

切片操作使得子序列提取更加简单直观


# 示例:切片和切片赋值
nums = list(range(10))
print(nums[2:8:2])  # 输出: [2, 4, 6]
nums[2:5] = [20, 30]
print(nums)  # 输出: [0, 1, 20, 30, 5, 6, 7, 8, 9]

6.3 多维切片和省略号

# 多维切片

# 创建一个3x3的二维数组
array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 使用多维切片获取子数组
sub_array = array[0:2, 1:3]

# 创建一个3x3x3的三维数组
array = np.array([[[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]]])

# 使用省略号切片获取所有维度的最后一个元素
last_elements = array[..., -1]

6.4 切片赋值

切片不仅可以从序列中提取信息,还可以就地更改可变序列,即不重新构建序列。

# 创建一个列表
my_list = [1, 2, 3, 4, 5]

# 使用切片赋值替换列表的一部分
my_list[1:4] = [20, 30, 40]

print(my_list)  # 输出: [1, 20, 30, 40, 5]

# 使用切片赋值插入元素
my_list[2:2] = [25, 35]

print(my_list)  # 输出: [1, 20, 25, 35, 30, 40, 5]

# 使用切片赋值删除元素
my_list[3:5] = []

print(my_list)  # 输出: [1, 20, 25, 40, 5]

7 使用+和*处理序列

  • 拼接:使用 + 运算符将两个序列拼接成新的序列。
  • 重复:使用 * 运算符将序列重复指定次数。
  • 增量赋值:使用 += 和 *= 运算符修改序列。

# 拼接序列
result = a + b

# 重复序列
result = a * 3

# 增量赋值
l += [20, 30]

8 list.sort与内置函数sorted

list.sort 就地排序,返回None,而 sorted 返回一个新列表,不会修改原始序列。

# 示例:list.sort 和 sorted
data = [3, 1, 4, 1, 5]
sorted_data = sorted(data)
print(sorted_data)  # 输出: [1, 1, 3, 4, 5]
data.sort(reverse=True)
print(data)  # 输出: [5, 4, 3, 1, 1]

二者都接受两个可输入参数:key和reverse:

  • key:为一个函数,指定排序依据;
  • reverse:默认按从小到大,如果为true,代表从大到小

9 当列表不适用时

  • 数组(array.array):高效地存储和处理数值列表。
  • memoryview:共享内存的序列类型,可以处理数组切片;
  • Numpy:用于科学计算的高级数值处理;
  • 双端队列(collections.deque):快速在两端插入和删除数据;
  • 集合set和frozenset:储存唯一的元素,并支持集合运算。

# 使用数组
floats = array('d', (random() for i in range(10**7)))

# 使用 memoryview
memoryview(numbers)

# 使用 NumPy
a = np.arange(12)

# 使用双端队列
dq = deque(range(10), maxlen=10)

# 使用集合
unique_elements = set(elements)

10 本章小结

本章介绍了 Python 中丰富的序列类型和它们的应用,并探讨了如何利用这些类型构建高效、简洁的代码。