353 lines
8.4 KiB
Markdown
353 lines
8.4 KiB
Markdown
---
|
||
doc_type: short
|
||
full_text: sources/04_Sequences.md
|
||
---
|
||
|
||
# 04_Sequences 总结
|
||
|
||
本文介绍 Python 中的Python序列及其常见操作,包括字符串、列表、元组、切片、循环遍历、`range()`、`enumerate()`、元组解包与 `zip()`。重点是如何用更 Pythonic 的方式处理有序数据,尤其是在 CSV 数据处理中利用表头构造字典,从而写出更通用的程序。
|
||
|
||
## 序列数据类型
|
||
|
||
Python 有三种常见的序列类型:
|
||
|
||
- 字符串:如 `'Hello'`,是字符序列。
|
||
- 列表:如 `[1, 4, 5]`。
|
||
- 元组:如 `('GOOG', 100, 490.1)`。
|
||
|
||
所有序列都具有以下共同特征:
|
||
|
||
- 有序。
|
||
- 可用整数索引访问元素。
|
||
- 可用 `len()` 获取长度。
|
||
- 支持负索引,例如 `b[-1]` 表示最后一个元素。
|
||
|
||
序列还支持复制与连接:
|
||
|
||
- `s * n`:复制序列。
|
||
- `s + t`:连接同类型序列。
|
||
|
||
需要注意,序列连接要求两边类型相同。例如元组只能与元组连接,不能直接与列表连接。
|
||
|
||
## 切片
|
||
|
||
Python切片用于从序列中提取子序列,语法为:
|
||
|
||
```python
|
||
s[start:end]
|
||
```
|
||
|
||
关键规则:
|
||
|
||
- `start` 和 `end` 是整数索引。
|
||
- 切片包含 `start`,不包含 `end`,类似数学中的半开区间。
|
||
- 省略 `start` 时默认为序列开头。
|
||
- 省略 `end` 时默认为序列结尾。
|
||
|
||
示例:
|
||
|
||
```python
|
||
a = [0,1,2,3,4,5,6,7,8]
|
||
|
||
a[2:5] # [2,3,4]
|
||
a[-5:] # [4,5,6,7,8]
|
||
a[:3] # [0,1,2]
|
||
```
|
||
|
||
## 列表的切片赋值与删除
|
||
|
||
列表支持对切片重新赋值或删除:
|
||
|
||
```python
|
||
a = [0,1,2,3,4,5,6,7,8]
|
||
a[2:4] = [10,11,12]
|
||
```
|
||
|
||
切片赋值不要求新旧片段长度相同,因此可以改变列表长度。
|
||
|
||
删除切片:
|
||
|
||
```python
|
||
del a[2:4]
|
||
```
|
||
|
||
这体现了列表作为可变序列的特点,而字符串和元组则不可变。
|
||
|
||
## 序列归约操作
|
||
|
||
一些内置函数可以把序列归约为单个值:
|
||
|
||
- `sum(s)`:求和。
|
||
- `min(s)`:最小值。
|
||
- `max(s)`:最大值。
|
||
|
||
这些函数不仅可用于数字列表,也可用于字符串列表等可比较对象。例如字符串会按字典序比较。
|
||
|
||
## 遍历序列
|
||
|
||
`for` 循环可以直接遍历序列中的元素:
|
||
|
||
```python
|
||
for x in s:
|
||
...
|
||
```
|
||
|
||
每次迭代时,当前元素会赋给迭代变量。循环结束后,迭代变量仍保留最后一次的值。
|
||
|
||
本文强调:如果只是遍历元素,应直接使用 `for x in data`,不要写成:
|
||
|
||
```python
|
||
for n in range(len(data)):
|
||
print(data[n])
|
||
```
|
||
|
||
这种写法不够 Pythonic,效率较低,也更难阅读。若需要索引,应使用 `enumerate()`。
|
||
|
||
## break 与 continue
|
||
|
||
`break` 用于提前退出循环:
|
||
|
||
```python
|
||
for name in namelist:
|
||
if name == 'Jake':
|
||
break
|
||
```
|
||
|
||
`break` 只退出最内层循环。
|
||
|
||
`continue` 用于跳过当前元素,进入下一次迭代:
|
||
|
||
```python
|
||
for line in lines:
|
||
if line == '\n':
|
||
continue
|
||
```
|
||
|
||
这常用于忽略空行、无效数据或不需要处理的元素。
|
||
|
||
## range():整数序列迭代
|
||
|
||
如果需要计数,应使用 `range()`:
|
||
|
||
```python
|
||
for i in range(100):
|
||
...
|
||
```
|
||
|
||
语法:
|
||
|
||
```python
|
||
range([start,] end [,step])
|
||
```
|
||
|
||
规则:
|
||
|
||
- `end` 不包含在结果中,与切片规则一致。
|
||
- `start` 可选,默认是 `0`。
|
||
- `step` 可选,默认是 `1`。
|
||
- `range()` 按需生成值,不会实际存储完整的大范围数字。
|
||
|
||
示例:
|
||
|
||
```python
|
||
range(100) # 0 到 99
|
||
range(10, 20) # 10 到 19
|
||
range(10, 50, 2) # 10, 12, ..., 48
|
||
```
|
||
|
||
## enumerate():带计数器的遍历
|
||
|
||
enumerate函数用于在遍历序列时同时获得索引和值:
|
||
|
||
```python
|
||
names = ['Elwood', 'Jake', 'Curtis']
|
||
for i, name in enumerate(names):
|
||
...
|
||
```
|
||
|
||
通用形式:
|
||
|
||
```python
|
||
enumerate(sequence, start=0)
|
||
```
|
||
|
||
`start` 可指定计数起点。典型用途是在读取文件时跟踪行号:
|
||
|
||
```python
|
||
with open(filename) as f:
|
||
for lineno, line in enumerate(f, start=1):
|
||
...
|
||
```
|
||
|
||
这比手动维护计数器更简洁,也略快。
|
||
|
||
## 多变量迭代与元组解包
|
||
|
||
如果序列中的元素是元组,可以在 `for` 循环中直接解包:
|
||
|
||
```python
|
||
points = [(1, 4), (10, 40), (23, 14)]
|
||
|
||
for x, y in points:
|
||
...
|
||
```
|
||
|
||
每个元组会被拆分到多个迭代变量中。变量数量必须与元组元素数量一致。
|
||
|
||
这属于Python解包的重要应用。
|
||
|
||
## zip():组合多个序列
|
||
|
||
zip函数用于把多个序列按位置组合成元组迭代器:
|
||
|
||
```python
|
||
columns = ['name', 'shares', 'price']
|
||
values = ['GOOG', 100, 490.1]
|
||
pairs = zip(columns, values)
|
||
```
|
||
|
||
得到的逻辑结果类似:
|
||
|
||
```python
|
||
('name', 'GOOG'), ('shares', 100), ('price', 490.1)
|
||
```
|
||
|
||
`zip()` 返回迭代器,需要通过循环或 `list()` 消费。
|
||
|
||
常见用法是把列名和值组合起来构造字典:
|
||
|
||
```python
|
||
d = dict(zip(columns, values))
|
||
```
|
||
|
||
这是处理 CSV 数据时非常重要的技巧。
|
||
|
||
## 使用 zip() 处理 CSV 表头
|
||
|
||
在 `Data/portfolio.csv` 中,第一行是列名:
|
||
|
||
```python
|
||
headers = next(rows)
|
||
```
|
||
|
||
如果某一行数据为:
|
||
|
||
```python
|
||
row = ['AA', '100', '32.20']
|
||
```
|
||
|
||
则可使用:
|
||
|
||
```python
|
||
record = dict(zip(headers, row))
|
||
```
|
||
|
||
得到:
|
||
|
||
```python
|
||
{'name': 'AA', 'shares': '100', 'price': '32.20'}
|
||
```
|
||
|
||
这样,程序就不再依赖固定列号,而是通过字段名读取数据:
|
||
|
||
```python
|
||
nshares = int(record['shares'])
|
||
price = float(record['price'])
|
||
```
|
||
|
||
这一改动让 `portfolio_cost()` 能处理不同列顺序、甚至额外包含日期和时间列的 CSV 文件,只要文件中存在所需字段即可。这是从“固定格式解析”走向“基于字段名解析”的重要改进,属于CSV数据处理中的核心技巧。
|
||
|
||
## 用 enumerate() 改进错误报告
|
||
|
||
在处理包含缺失值的 CSV 文件时,可以用 `enumerate(rows, start=1)` 记录行号,并在转换失败时打印更有用的错误信息:
|
||
|
||
```python
|
||
for rowno, row in enumerate(rows, start=1):
|
||
try:
|
||
...
|
||
except ValueError:
|
||
print(f'Row {rowno}: Bad row: {row}')
|
||
```
|
||
|
||
这样可以定位坏数据所在行,例如:
|
||
|
||
```text
|
||
Row 4: Couldn't convert: ['MSFT', '', '51.23']
|
||
Row 7: Couldn't convert: ['IBM', '', '70.44']
|
||
```
|
||
|
||
这体现了[[concepts/异常处理]]与迭代工具结合后在数据清洗中的实用价值。
|
||
|
||
## 反转字典数据
|
||
|
||
字典的 `items()` 返回 `(key, value)` 对。如果想得到 `(value, key)` 对,可以结合 `values()`、`keys()` 和 `zip()`:
|
||
|
||
```python
|
||
pricelist = list(zip(prices.values(), prices.keys()))
|
||
```
|
||
|
||
这样可以按价格进行比较、排序或求最大最小值:
|
||
|
||
```python
|
||
min(pricelist)
|
||
max(pricelist)
|
||
sorted(pricelist)
|
||
```
|
||
|
||
这也说明了元组比较规则:元组按元素从左到右逐项比较。因此 `(price, name)` 会优先按价格比较,价格相同再比较名称。
|
||
|
||
## zip() 的更多特性
|
||
|
||
`zip()` 不限于两个序列,可以组合任意多个序列:
|
||
|
||
```python
|
||
list(zip(a, b, c))
|
||
```
|
||
|
||
如果输入序列长度不同,`zip()` 会在最短序列耗尽时停止:
|
||
|
||
```python
|
||
a = [1, 2, 3, 4, 5, 6]
|
||
b = ['x', 'y', 'z']
|
||
list(zip(a, b))
|
||
# [(1, 'x'), (2, 'y'), (3, 'z')]
|
||
```
|
||
|
||
## 练习要点
|
||
|
||
本文练习围绕以下目标展开:
|
||
|
||
1. 使用 `range()` 进行正向、反向、步进计数。
|
||
2. 使用 `min()`、`max()`、`sum()` 对序列进行归约。
|
||
3. 用普通 `for` 循环和 `enumerate()` 遍历数据。
|
||
4. 避免 `range(len(data))` 这类低效且不清晰的写法。
|
||
5. 用 `enumerate()` 在错误提示中加入行号。
|
||
6. 用 `zip(headers, row)` 构造记录字典,提高 CSV 处理代码的通用性。
|
||
7. 用 `zip(values, keys)` 生成反转的字典视图,以便按值排序或比较。
|
||
|
||
## 核心思想
|
||
|
||
本文的核心贡献是展示 Python 序列生态中的几种基础但强大的模式:
|
||
|
||
- 用切片表达子序列。
|
||
- 用 `for` 直接遍历数据,而不是模仿 C 风格索引循环。
|
||
- 用 `enumerate()` 在需要索引时保持代码清晰。
|
||
- 用元组解包简化结构化数据遍历。
|
||
- 用 `zip()` 把来自不同位置的数据配对,尤其是把 CSV 表头和值组合成字典。
|
||
|
||
这些技术共同构成了 Python 数据处理的基础风格:更少依赖位置编号,更多依赖清晰的数据结构和可读的迭代模式。
|
||
|
||
## Related Concepts
|
||
- [[concepts/Python-切片]]
|
||
- [[concepts/列表与序列]]
|
||
- [[concepts/元组与解包]]
|
||
- [[concepts/迭代协议与生成器]]
|
||
- [[concepts/CSV-数据处理]]
|
||
- [[concepts/字典与数据建模]]
|
||
- [[concepts/Python-可变对象]]
|
||
- [[concepts/Python-不可变对象]]
|
||
- [[concepts/字符串处理]]
|
||
- [[concepts/文件读写]]
|
||
- [[concepts/Python-控制流与缩进]]
|
||
- [[concepts/表格化输出]]
|