Python高级特性:切片、迭代、列表生成式、生成器与迭代器

在掌握了 Python 的基础语法、容器类型和流程控制之后,下一步就要进入 Python 非常有代表性的“高级特性”部分。这些特性并不是“高深到难以理解”的黑魔法,而是 Python 在表达能力、代码简洁性和运行效率上的核心优势:你可以用更少的代码完成更清晰的逻辑,也能以更优雅的方式处理数据流。

本文将把廖雪峰教程第七部分之后的核心内容统一整理到一篇文章中,系统讲清楚以下 5 个主题:切片(slice)迭代(iterate)列表生成式(list comprehension)生成器(generator)迭代器(iterator)。如果你能真正理解这五块内容,就已经开始具备写出“Python味”代码的能力了。

一、为什么这些特性重要?

很多初学者在学习 Python 时,会把“高级特性”理解成“可学可不学的语法糖”。实际上并不是。

这些特性的价值主要体现在三个方面:

  1. 写法更简洁:很多本来需要 5~10 行的循环或判断,可能 1~2 行就能表达清楚。
  2. 语义更贴近问题本身:代码更像是在描述“我要什么”,而不是机械地写“如何一步步做”。
  3. 性能与内存更合理:比如生成器和迭代器支持按需计算,避免一次性把大量数据全部放进内存。

你可以把这一篇理解为:从“会写 Python”走向“写得像 Python”的关键一步。


二、切片(Slice):优雅地截取序列

切片是 Python 中非常高频、非常实用的特性。它可以让你快速从一个序列中取出一部分内容,而不需要自己写循环。

常见支持切片的类型包括:

  • list
  • tuple
  • str
  • 某些自定义序列对象
2.1 基本语法

切片的基本形式是:

1
sequence[start:end]

它表示:

  • 从下标 start 开始取
  • 取到下标 end 之前结束
  • 左闭右开,即包含 start,不包含 end

示例:

1
2
3
4
5
6
7
numbers = [0, 1, 2, 3, 4, 5]

print(numbers[0:3]) # [0, 1, 2]
print(numbers[2:5]) # [2, 3, 4]
print(numbers[:3]) # [0, 1, 2]
print(numbers[3:]) # [3, 4, 5]
print(numbers[:]) # [0, 1, 2, 3, 4, 5]

其中:

  • [:3] 表示“从开头取到第 3 个元素之前”
  • [3:] 表示“从第 3 个元素一直取到末尾”
  • [:] 表示“完整复制一个浅层的新序列”
2.2 负数索引与倒序截取

Python 允许使用负数索引从尾部定位元素:

1
2
3
4
5
6
letters = ['a', 'b', 'c', 'd', 'e']

print(letters[-1]) # e
print(letters[-2]) # d
print(letters[-3:]) # ['c', 'd', 'e']
print(letters[:-1]) # ['a', 'b', 'c', 'd']

这在处理“最后几个元素”时非常方便。

例如:

1
2
recent_logs = ['log1', 'log2', 'log3', 'log4', 'log5']
print(recent_logs[-2:]) # ['log4', 'log5']
2.3 步长切片

切片还可以带第三个参数:

1
sequence[start:end:step]

其中 step 表示步长。

1
2
3
4
5
6
nums = list(range(10))

print(nums[::2]) # [0, 2, 4, 6, 8]
print(nums[1::2]) # [1, 3, 5, 7, 9]
print(nums[:8:3]) # [0, 3, 6]
print(nums[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

其中 [::-1] 是非常经典的写法,表示把序列反转。

2.4 字符串和 tuple 也支持切片

切片并不只属于列表。

1
2
3
4
5
6
7
name = 'Python'
print(name[:2]) # Py
print(name[2:]) # thon
print(name[::-1]) # nohtyP

point = (10, 20, 30, 40)
print(point[1:3]) # (20, 30)

只要对象是序列类型,通常都支持这种操作。

2.5 切片的常见应用场景

1. 取前 N 个元素

1
top3 = scores[:3]

2. 取后 N 个元素

1
last5 = records[-5:]

3. 复制序列

1
copied = original[:]

4. 快速反转

1
reversed_name = name[::-1]

5. 间隔取样

1
odd_positions = nums[::2]
2.6 切片的注意事项

1. 左闭右开一定要牢记

1
2
nums = [10, 20, 30, 40]
print(nums[1:3]) # [20, 30],不包含下标3对应的40

2. 越界不会报错

1
2
print(nums[:100])   # [10, 20, 30, 40]
print(nums[100:]) # []

这比手动下标访问更安全。

3. 切片返回的是新对象

1
2
3
a = [1, 2, 3]
b = a[:]
print(a is b) # False

不过这里要注意:对于嵌套结构,它仍然是浅拷贝

1
2
3
4
a = [[1], [2], [3]]
b = a[:]
b[0].append(99)
print(a) # [[1, 99], [2], [3]]

如果你需要真正彻底复制嵌套对象,应考虑 copy.deepcopy()

2.7 小结:什么时候优先用切片?

当你的目标是“取一段”“取前几个”“取后几个”“反转”“按步长抽样”时,优先想到切片,而不是先写循环。它更简洁,也更符合 Python 风格。


三、迭代(Iterate):不仅能遍历 list

在 Python 中,只要对象是可迭代的,就可以放进 for 循环中处理。很多初学者最开始只会遍历 list,但其实远不止如此。

3.1 最常见的迭代写法
1
2
3
names = ['Alice', 'Bob', 'Carol']
for name in names:
print(name)

这里我们遍历的是列表。

3.2 dict 的迭代

字典默认迭代的是 key

1
2
3
4
d = {'a': 1, 'b': 2, 'c': 3}

for key in d:
print(key)

如果你只想迭代 value:

1
2
for value in d.values():
print(value)

如果你想同时拿到 key 和 value:

1
2
for key, value in d.items():
print(key, value)

这是一种非常常见的写法,在实际开发中经常用于配置处理、数据映射和 JSON 解析。

3.3 字符串也是可迭代对象
1
2
for ch in 'Python':
print(ch)

输出的是每一个字符。

这说明 for 循环关注的不是“是否有下标”,而是“能不能一个一个地取出元素”。

3.4 如何判断对象能不能迭代?

可以使用 collections.abc 里的 Iterable 来判断:

1
2
3
4
5
6
from collections.abc import Iterable

print(isinstance([1, 2, 3], Iterable)) # True
print(isinstance('abc', Iterable)) # True
print(isinstance({'x': 1}, Iterable)) # True
print(isinstance(123, Iterable)) # False
3.5 enumerate:同时拿下标和元素

如果你既想拿到元素,又想拿到当前索引,不要自己维护计数器,优先使用 enumerate()

1
2
3
4
letters = ['A', 'B', 'C']

for index, value in enumerate(letters):
print(index, value)

输出:

1
2
3
0 A
1 B
2 C

你也可以指定起始下标:

1
2
for index, value in enumerate(letters, start=1):
print(index, value)
3.6 zip:并行迭代多个序列

虽然参考章节重点在 enumerate(),但在实际开发中,zip() 也非常常见。

1
2
3
4
5
names = ['Alice', 'Bob', 'Carol']
scores = [95, 88, 92]

for name, score in zip(names, scores):
print(name, score)

这在“把多份相关数据同步遍历”时特别好用。

3.7 实战示例:找出列表中的最小值和最大值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def find_min_and_max(items):
if not items:
return None, None

min_value = max_value = items[0]

for item in items[1:]:
if item < min_value:
min_value = item
if item > max_value:
max_value = item

return min_value, max_value

print(find_min_and_max([7, 1, 3, 9, 5])) # (1, 9)

这个例子很适合训练“通过迭代处理数据”的思维。

3.8 小结:迭代的核心理解

Python 中的 for 本质是“逐个取元素”。

因此:

  • 你不必执着于“有没有下标”
  • 只要对象可迭代,就可以遍历
  • 字典、字符串、集合、生成器都可以迭代
  • 现代 Python 中,enumerate() 通常比手写索引更自然

四、列表生成式(List Comprehension):把“构造列表”写得更像表达式

列表生成式是 Python 的一大特色。它可以把“循环 + 条件 + append”压缩成非常简洁的表达式。

4.1 最基本的列表生成式

先看普通写法:

1
2
3
4
5
result = []
for x in range(1, 11):
result.append(x * x)

print(result)

用列表生成式可以写成:

1
2
result = [x * x for x in range(1, 11)]
print(result)

两者含义完全一样,但后者更紧凑。

4.2 带条件过滤的列表生成式
1
2
even_squares = [x * x for x in range(1, 11) if x % 2 == 0]
print(even_squares) # [4, 16, 36, 64, 100]

这里 for 后面的 if过滤条件,表示“只保留满足条件的元素”。

4.3 双层循环的列表生成式
1
2
pairs = [m + n for m in 'ABC' for n in 'XYZ']
print(pairs)

输出:

1
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

这相当于两层嵌套循环。

如果超过两层,一般就要开始考虑可读性问题了,不建议为了“炫技”写太复杂的推导式。

4.4 遍历目录和字典

列出当前目录文件名:

1
2
3
4
import os

items = [name for name in os.listdir('.')]
print(items)

遍历字典并格式化输出:

1
2
3
d = {'x': 'A', 'y': 'B', 'z': 'C'}
result = [f'{k}={v}' for k, v in d.items()]
print(result)
4.5 统一处理字符串
1
2
3
words = ['Hello', 'World', 'IBM', 'Apple']
lower_words = [word.lower() for word in words]
print(lower_words)
4.6 if 在前和 if 在后的区别

这是列表生成式最容易混淆的地方之一。

1. for 后面的 if 是过滤条件

1
[x for x in range(1, 11) if x % 2 == 0]

表示“只保留偶数”。

2. 前面的 if ... else 是表达式的一部分

1
[x if x % 2 == 0 else -x for x in range(1, 11)]

表示“偶数保留原值,奇数变成负数”。

两者不是一回事:

  • for 后面的 if:控制要不要这个元素
  • 前面的 if ... else:控制这个元素生成成什么值
4.7 处理混合类型数据

实际开发中,数据往往没那么“干净”。例如:

1
2
3
items = ['Hello', 'World', 18, 'Apple', None]
result = [x.lower() for x in items if isinstance(x, str)]
print(result) # ['hello', 'world', 'apple']

这里通过 isinstance(x, str) 过滤掉了非字符串元素,避免调用 lower() 报错。

4.8 列表生成式的延伸:set 和 dict 推导式

虽然本节重点是列表生成式,但你也应该知道,类似语法还可以用于集合和字典。

1
2
3
4
5
square_set = {x * x for x in range(5)}
print(square_set)

square_dict = {x: x * x for x in range(5)}
print(square_dict)
4.9 什么时候该用列表生成式?

适合使用的场景:

  • 从一个序列推导出另一个序列
  • 逻辑清晰、表达简单
  • 一行就能看懂

不太适合的场景:

  • 嵌套过深
  • 条件过于复杂
  • 需要很多副作用操作

如果一行推导式需要你读三遍才能明白,那通常不如拆成普通循环更清晰。

4.10 小结:生成式的本质

列表生成式的本质是:用声明式方式快速构造一个新列表。它是 Python 简洁性的重要体现,但始终要把可读性放在第一位。


五、生成器(Generator):按需产生数据,而不是一次性全部准备好

前面我们用列表生成式可以快速创建列表,但列表有一个天然问题:它会一次性把所有结果都放进内存

如果数据量非常大,这会浪费大量内存。于是 Python 提供了生成器。

5.1 生成器和列表的区别
1
2
3
4
5
list_result = [x * x for x in range(10)]
generator_result = (x * x for x in range(10))

print(list_result)
print(generator_result)

输出类似:

1
2
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at ...>

区别在于:

  • 列表:所有值已经算好并存储好了
  • 生成器:只保存“如何生成下一个值”的规则,用到时才计算
5.2 如何获取生成器里的值

可以用 next()

1
2
3
4
5
g = (x * x for x in range(5))

print(next(g)) # 0
print(next(g)) # 1
print(next(g)) # 4

当元素取完后,再调用会抛出 StopIteration

但在日常开发中,更推荐使用 for 循环:

1
2
for value in (x * x for x in range(5)):
print(value)
5.3 用 yield 定义生成器函数

如果生成逻辑比较复杂,仅靠一个生成器表达式不够,这时就可以使用 yield

例如斐波那契数列:

1
2
3
4
5
6
def fib(max_count):
n, a, b = 0, 0, 1
while n < max_count:
yield b
a, b = b, a + b
n += 1

调用时:

1
2
for x in fib(6):
print(x)

输出:

1
2
3
4
5
6
1
1
2
3
5
8
5.4 yield 和 return 的区别

这是理解生成器的关键。

普通函数:

  • 遇到 return 就结束
  • 返回一个最终结果

生成器函数:

  • 遇到 yield 暂停执行,并返回一个值
  • 下一次继续从上次暂停的位置往下执行

看这个例子:

1
2
3
4
5
6
7
def odd():
print('step 1')
yield 1
print('step 2')
yield 3
print('step 3')
yield 5

执行过程:

1
2
3
4
g = odd()
print(next(g))
print(next(g))
print(next(g))

输出:

1
2
3
4
5
6
step 1
1
step 2
3
step 3
5

你会发现它不是一次性全部执行完,而是每次取一个值,就向前推进一步

5.5 多次调用生成器函数,会创建多个独立对象
1
2
3
print(next(odd()))
print(next(odd()))
print(next(odd()))

这三次每次都会重新创建新的生成器,因此都只会得到第一个值。

正确写法应该是:

1
2
3
4
g = odd()
print(next(g))
print(next(g))
print(next(g))
5.6 获取生成器的 return 值

生成器也可以写 return,但这个返回值不会在 for 循环中直接拿到。

1
2
3
4
def demo():
yield 1
yield 2
return 'done'

如果你要拿到这个返回值,需要手动捕获异常:

1
2
3
4
5
6
7
g = demo()
while True:
try:
print(next(g))
except StopIteration as e:
print('返回值:', e.value)
break

不过在业务开发里,大多数场景并不需要刻意依赖生成器的 return 值。

5.7 生成器适合什么场景?

很适合:

  • 大文件逐行读取
  • 大量数据流式处理
  • 管道式数据转换
  • 惰性计算
  • 可以无限延展的数据序列

例如:

1
2
3
4
def read_large_file(path):
with open(path, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip()

这样你就不需要一次性把整个文件全部读入内存。

5.8 小结:为什么生成器强大?

生成器最大的优势是:

  • 节省内存
  • 按需计算
  • 天然适合流式处理

很多 Python 高级库、异步框架和数据处理逻辑,都离不开这种“惰性求值”的思想。


六、迭代器(Iterator):真正负责一个一个吐出元素的对象

学到这里,很多人会开始混淆两个概念:

  • 可迭代对象(Iterable)
  • 迭代器(Iterator)

它们有关联,但并不相同。

6.1 什么是 Iterable?

凡是可以作用于 for 循环的对象,都可以看作可迭代对象。

例如:

  • list
  • tuple
  • dict
  • set
  • str
  • generator

可以用 Iterable 来判断:

1
2
3
4
5
6
7
from collections.abc import Iterable

print(isinstance([], Iterable))
print(isinstance({}, Iterable))
print(isinstance('abc', Iterable))
print(isinstance((x for x in range(3)), Iterable))
print(isinstance(100, Iterable))
6.2 什么是 Iterator?

迭代器是可以被 next() 不断调用,并持续返回下一个值的对象。

可以用 Iterator 判断:

1
2
3
4
5
6
from collections.abc import Iterator

print(isinstance((x for x in range(3)), Iterator)) # True
print(isinstance([], Iterator)) # False
print(isinstance({}, Iterator)) # False
print(isinstance('abc', Iterator)) # False
6.3 生成器一定是迭代器

这一点很重要:

  • 所有生成器都是 Iterator
  • 但不是所有 Iterable 都是 Iterator

例如:

  • list 是可迭代对象,但不是迭代器
  • 生成器既是可迭代对象,也是迭代器
6.4 iter():把 Iterable 变成 Iterator

如果你有一个列表、字符串或字典,想把它变成迭代器,可以用 iter()

1
2
3
4
5
6
7
8
from collections.abc import Iterator

it = iter([1, 2, 3])
print(isinstance(it, Iterator)) # True

print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3

字符串同样可以:

1
2
3
4
it = iter('abc')
print(next(it)) # a
print(next(it)) # b
print(next(it)) # c
6.5 为什么 list 不是 Iterator?

因为 list 更像是一个完整的数据容器,而 Iterator 更像是一个“数据流出口”。

Iterator 的特征是:

  • 只能一个一个往后取
  • 通常不能回退
  • 不一定知道整体长度
  • 支持惰性计算

这使得迭代器非常适合处理:

  • 超大数据流
  • 无限序列
  • 一次性消费的数据

例如自然数序列就很适合迭代器思维,但不适合一次性放进列表。

6.6 for 循环本质上就是在不断调用 next()

这段代码:

1
2
for x in [1, 2, 3, 4, 5]:
print(x)

本质上近似于:

1
2
3
4
5
6
7
it = iter([1, 2, 3, 4, 5])
while True:
try:
x = next(it)
print(x)
except StopIteration:
break

也就是说,Python 的 for 循环帮你隐藏了迭代器协议的细节。

6.7 小结:Iterable 与 Iterator 的区别

你可以这样记:

  • Iterable:可以“被遍历”
  • Iterator:可以“一个个产出下一个值”

更通俗一点:

  • list 像一个“仓库”
  • iterator 像一个“传送带”

仓库里有很多货物,但传送带负责一个一个把货送出来。


七、五个高级特性的关系梳理

到这里,我们把这五个概念串起来看:

flowchart TD A[序列对象 list tuple str] --> B[切片 Slice] A --> C[可迭代 Iterable] C --> D[for 循环] C --> E[iter() 转成 Iterator] E --> F[next() 逐个取值] G[列表生成式] --> H[快速构造 list] H --> C I[生成器表达式 / yield] --> J[Generator] J --> C J --> F

你会发现:

  • 切片主要解决“如何优雅地取出一段数据”
  • 迭代主要解决“如何逐个处理数据”
  • 列表生成式主要解决“如何快速构造新列表”
  • 生成器主要解决“如何按需产出数据”
  • 迭代器主要解释“Python 底层是如何一项一项取值的”

理解了它们之间的关系,你就不会把这些知识点割裂开来记忆。


八、综合示例:从列表到流式处理

下面用一个小例子,把这些高级特性串起来。

需求:

  • 有一批成绩
  • 先筛选及格分
  • 再平方处理
  • 最后逐个输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
scores = [58, 91, 76, 45, 88, 60, 99]

# 1. 切片:取前6个成绩
partial_scores = scores[:6]

# 2. 列表生成式:筛选及格分
passed_scores = [x for x in partial_scores if x >= 60]

# 3. 生成器:按需计算平方值
squared_generator = (x * x for x in passed_scores)

# 4. 迭代输出
for value in squared_generator:
print(value)

如果数据量更大,你甚至可以省掉中间列表,直接流式处理:

1
2
3
4
5
scores = [58, 91, 76, 45, 88, 60, 99]
result = (x * x for x in scores if x >= 60)

for value in result:
print(value)

这就是 Python 高级特性的威力:代码短,但表达清晰。


九、学习建议:别死记语法,要理解“数据如何流动”

学习这一部分时,我建议你重点建立下面这套思维:

9.1 先问自己:我是要“取一段”,还是要“逐个处理”?
  • 取一段数据:先想切片
  • 逐个处理数据:先想迭代
  • 构造新列表:先想列表生成式
  • 大量数据、按需处理:先想生成器
  • 研究底层行为:再理解迭代器
9.2 不要为了“高级”而牺牲可读性

Python 的高级特性是为了让代码更清楚,而不是更晦涩。

如果一行推导式太复杂,宁可拆开写:

1
2
3
4
result = []
for item in items:
if isinstance(item, str):
result.append(item.lower())

这种写法很多时候比复杂推导式更适合团队协作。

9.3 生成器和迭代器是后续很多框架的基础

后面你学习:

  • 文件处理
  • 协程
  • 异步编程
  • 框架源码
  • 数据处理管道

都会反复遇到生成器与迭代器。现在把基础打牢,以后会轻松很多。


十、总结

这一篇我们把 Python 高级特性的核心内容集中串了一遍:

  1. 切片让你可以优雅地截取序列的一部分;
  2. 迭代让你理解 for 并不只服务于 list
  3. 列表生成式让你可以用简洁表达式快速构造新列表;
  4. 生成器让数据支持按需计算、节省内存;
  5. 迭代器解释了 Python 逐个取值的底层机制。

如果说基础语法解决的是“代码能运行”,那么这些高级特性解决的是“代码是否足够 Python、足够简洁、足够高效”。

你不必一开始就把所有写法都背下来,但一定要反复练习下面几个能力:

  • 能看懂切片范围
  • 能熟练遍历常见容器
  • 能区分列表生成式中的两种 if
  • 能理解 yield 的暂停与恢复
  • 能说清楚 IterableIterator 的差异

掌握这些后,你写出来的 Python 代码会明显更自然,也更接近真实开发中的写法。

参考资料