Python 生成器函数和普通函数有什么区别?从执行机制到工程实战全面解析
Python 生成器函数和普通函数有什么区别?从执行机制到工程实战全面解析
在 Python 编程中,函数是最基本、也最重要的代码组织单元之一。
对于刚接触 Python 的开发者来说,普通函数通常很好理解:调用函数,执行代码,返回结果。但当代码中出现 yield 关键字时,事情就开始变得不一样了。
def normal_function():
return 1
def generator_function():
yield 1
这两个函数看起来只差一个关键字,但它们的执行方式、返回值类型、内存占用、适用场景以及内部状态管理机制,都存在本质区别。
理解生成器函数,不只是掌握一个 Python 语法点。它关系到如何处理大数据、如何构建流式任务、如何降低内存消耗,以及如何设计更优雅的数据处理程序。
本文将从基础语法、执行机制、内存模型、实际案例和最佳实践等多个角度,系统讲清楚:
生成器函数和普通函数的区别究竟是什么?
一、什么是普通函数?
普通函数是我们在 Python 开发中最常见的函数形式。
它通常使用 def 定义,通过 return 返回执行结果。
def add(a, b):
result = a + b
return result
value = add(10, 20)
print(value)
运行结果:
30
普通函数的执行过程可以概括为:
调用函数
↓
从第一行开始执行
↓
执行函数内部代码
↓
遇到 return 返回结果
↓
函数结束
普通函数一旦被调用,就会立即执行函数体中的代码。
当遇到 return 时,函数会返回结果并结束运行。如果没有显式编写 return,Python 会默认返回 None。
def say_hello():
print("Hello, Python!")
result = say_hello()
print(result)
运行结果:
Hello, Python!
None
从执行模型来看,普通函数属于“一次性执行”。
函数开始运行后,要么正常执行完毕,要么因为异常而终止。函数结束后,其内部局部状态通常也随之释放。
二、什么是生成器函数?
生成器函数同样使用 def 定义,但函数体中包含 yield 关键字。
def generate_numbers():
yield 1
yield 2
yield 3
需要注意的是,调用生成器函数时,函数体并不会立即执行。
generator = generate_numbers()
print(generator)
输出结果类似:
<generator object generate_numbers at 0x...>
这里得到的不是数字 1、2、3,而是一个生成器对象。
只有在使用 next() 获取数据时,生成器函数才真正开始执行。
generator = generate_numbers()
print(next(generator))
print(next(generator))
print(next(generator))
输出:
1
2
3
再次调用:
print(next(generator))
程序会抛出异常:
StopIteration
因为生成器已经没有更多数据可以产生。
生成器函数的执行过程可以表示为:
调用生成器函数
↓
返回生成器对象
↓
函数体暂不执行
↓
调用 next()
↓
执行到 yield
↓
返回当前值并暂停
↓
再次调用 next()
↓
从上次暂停位置继续执行
生成器函数最大的特点不是“返回多个值”,而是:
它可以在执行过程中暂停,并在下一次请求数据时从原位置继续运行。
三、核心区别一:return 和 yield 的行为不同
普通函数通常通过 return 返回结果,而生成器函数通过 yield 产生结果。
1. return 会终止函数
def normal_demo():
print("开始执行")
return 100
print("这一行不会执行")
调用:
result = normal_demo()
print(result)
输出:
开始执行
100
执行到 return 后,函数立即结束。
return 后面的代码不会继续执行。
2. yield 会暂停函数
def generator_demo():
print("第一次执行")
yield 100
print("第二次执行")
yield 200
print("执行结束")
调用:
generator = generator_demo()
print(next(generator))
print(next(generator))
输出:
第一次执行
100
第二次执行
200
每次执行到 yield,函数都会暂停,并保存当前执行状态。
下一次调用 next() 时,函数不会从头执行,而是从上一个 yield 后面继续。
这也是生成器能够实现“惰性计算”的基础。
四、核心区别二:普通函数立即执行,生成器函数延迟执行
来看一个直观的例子。
普通函数
def normal_function():
print("普通函数开始运行")
return 10
result = normal_function()
print("函数调用完成")
print(result)
输出:
普通函数开始运行
函数调用完成
10
调用普通函数时,函数体立即执行。
生成器函数
def generator_function():
print("生成器开始运行")
yield 10
generator = generator_function()
print("生成器已经创建")
print(next(generator))
输出:
生成器已经创建
生成器开始运行
10
可以看到:
generator = generator_function()
这一行只是创建了生成器对象,并没有真正执行函数体。
直到:
next(generator)
函数内部代码才开始运行。
这种“需要数据时再执行”的方式被称为:
惰性求值
英文通常称为:
Lazy Evaluation
五、核心区别三:返回值类型不同
普通函数调用后,返回的是实际计算结果。
def get_numbers():
return [1, 2, 3]
result = get_numbers()
print(type(result))
输出:
<class 'list'>
生成器函数调用后,返回的是生成器对象。
def generate_numbers():
yield 1
yield 2
yield 3
result = generate_numbers()
print(type(result))
输出:
<class 'generator'>
生成器对象同时也是迭代器。
因此,它支持:
next(generator)
也支持:
for value in generator:
print(value)
示例:
def generate_numbers():
yield 1
yield 2
yield 3
for number in generate_numbers():
print(number)
输出:
1
2
3
六、核心区别四:内存占用不同
生成器最重要的实际价值之一,就是节省内存。
假设我们需要生成一百万个整数。
使用普通函数和列表
def create_numbers(n):
numbers = []
for i in range(n):
numbers.append(i)
return numbers
data = create_numbers(1_000_000)
这个函数会先创建一个包含一百万个整数的列表,然后一次性返回。
所有数据都要保存在内存中。
使用生成器函数
def generate_numbers(n):
for i in range(n):
yield i
data = generate_numbers(1_000_000)
生成器不会一次性创建一百万个整数。
它每次只生成一个值。
for number in data:
print(number)
每次循环只处理当前需要的数据。
可以使用 sys.getsizeof() 做一个简单对比:
import sys
number_list = [i for i in range(1_000_000)]
number_generator = (i for i in range(1_000_000))
print(sys.getsizeof(number_list))
print(sys.getsizeof(number_generator))
在不同 Python 版本和运行环境中,具体数值会有所不同,但通常可以观察到:
列表占用数 MB 甚至更多
生成器对象只占用几百字节
这是因为列表保存了全部元素,而生成器主要保存的是:
- 当前执行位置
- 局部变量
- 运行状态
- 生成逻辑
生成器并不提前保存所有数据。
七、核心区别五:普通函数没有暂停状态,生成器会保存上下文
生成器函数暂停时,会保存当前上下文。
例如:
def counter():
current = 0
while current < 3:
print(f"当前状态:{current}")
yield current
current += 1
调用:
generator = counter()
print(next(generator))
print(next(generator))
print(next(generator))
输出:
当前状态:0
0
当前状态:1
1
当前状态:2
2
变量 current 的值没有在每次调用 next() 时重新变成 0。
这是因为生成器保存了函数运行现场,包括:
局部变量
执行位置
异常处理状态
循环状态
普通函数执行完成后,调用栈通常会被释放。
生成器则可以在暂停和恢复之间保留运行环境。
八、普通函数和生成器函数对比表
| 对比维度 | 普通函数 | 生成器函数 |
|---|---|---|
| 关键字 | return |
yield |
| 调用后是否立即执行 | 是 | 否 |
| 返回结果 | 实际值 | 生成器对象 |
| 是否可以暂停 | 不可以 | 可以 |
| 是否保存执行状态 | 通常不保存 | 自动保存 |
| 数据生成方式 | 一次性计算 | 按需生成 |
| 内存占用 | 可能较高 | 通常较低 |
是否可直接使用 next() |
通常不可以 | 可以 |
| 是否适合大数据 | 视情况而定 | 非常适合 |
| 是否可重复遍历 | 取决于返回对象 | 通常只能消费一次 |
| 典型场景 | 常规计算、业务逻辑 | 流式处理、大文件、数据管道 |
九、实战案例一:读取超大日志文件
假设服务器每天产生一个数十 GB 的日志文件。
如果使用普通函数一次性读取:
def read_log_file(file_path):
with open(file_path, "r", encoding="utf-8") as file:
return file.readlines()
调用:
logs = read_log_file("server.log")
for line in logs:
print(line)
readlines() 会将全部内容读入内存。
当日志文件非常大时,程序可能出现:
内存占用过高
程序响应变慢
系统开始交换内存
进程被操作系统终止
更合理的方式是使用生成器。
def read_log_file(file_path):
with open(file_path, "r", encoding="utf-8") as file:
for line in file:
yield line.rstrip("\n")
调用:
for log_line in read_log_file("server.log"):
print(log_line)
这种方式一次只读取一行。
进一步过滤错误日志:
def read_log_file(file_path):
with open(file_path, "r", encoding="utf-8") as file:
for line in file:
yield line.rstrip("\n")
def filter_error_logs(lines):
for line in lines:
if "ERROR" in line:
yield line
logs = read_log_file("server.log")
error_logs = filter_error_logs(logs)
for error in error_logs:
print(error)
这段代码形成了一个流式数据管道:
日志文件
↓
逐行读取
↓
筛选 ERROR
↓
逐条输出
整个过程不需要把全部日志同时放入内存。
十、实战案例二:分批处理数据库数据
在实际项目中,一张数据库表可能有几百万条记录。
如果一次性查询全部数据:
rows = database.query("SELECT * FROM orders")
可能带来很大的内存压力。
可以使用生成器分批获取。
def fetch_orders_in_batches(database, batch_size=1000):
offset = 0
while True:
rows = database.query(
"SELECT * FROM orders LIMIT %s OFFSET %s",
(batch_size, offset),
)
if not rows:
break
yield rows
offset += batch_size
使用方式:
for batch in fetch_orders_in_batches(database):
for order in batch:
process_order(order)
这样每次只处理一批数据。
更进一步,还可以逐条产生记录:
def fetch_orders(database, batch_size=1000):
for batch in fetch_orders_in_batches(database, batch_size):
for order in batch:
yield order
调用:
for order in fetch_orders(database):
process_order(order)
上层业务代码不需要关心分页细节。
生成器将复杂的数据获取过程封装成了一个连续的数据流。
不过在真实数据库场景中,处理超大表时应优先考虑主键游标分页,而不是大量使用 OFFSET,因为偏移量越大,数据库查询成本往往越高。
例如:
def fetch_orders(database, batch_size=1000):
last_id = 0
while True:
rows = database.query(
"""
SELECT *
FROM orders
WHERE id > %s
ORDER BY id
LIMIT %s
""",
(last_id, batch_size),
)
if not rows:
break
for row in rows:
yield row
last_id = rows[-1]["id"]
这种方式通常更适合大规模数据处理。
十一、实战案例三:构建数据处理流水线
生成器非常适合构建模块化的数据管道。
假设我们需要处理一批用户数据:
- 读取原始数据;
- 删除无效数据;
- 标准化姓名;
- 筛选成年用户;
- 输出结果。
可以使用多个生成器逐层处理。
def read_users(users):
for user in users:
yield user
def remove_invalid_users(users):
for user in users:
if user.get("name") and user.get("age") is not None:
yield user
def normalize_names(users):
for user in users:
normalized = user.copy()
normalized["name"] = normalized["name"].strip().title()
yield normalized
def filter_adults(users):
for user in users:
if user["age"] >= 18:
yield user
准备数据:
raw_users = [
{"name": " alice ", "age": 20},
{"name": "", "age": 25},
{"name": "bob", "age": 16},
{"name": " charlie", "age": 32},
]
组合处理流程:
users = read_users(raw_users)
users = remove_invalid_users(users)
users = normalize_names(users)
users = filter_adults(users)
for user in users:
print(user)
输出:
{'name': 'Alice', 'age': 20}
{'name': 'Charlie', 'age': 32}
这种设计的优势在于:
- 每个函数职责单一;
- 每个处理阶段可以单独测试;
- 数据按需流动;
- 中间结果不必全部存储;
- 新增处理步骤非常方便。
例如加入邮箱标准化:
def normalize_emails(users):
for user in users:
normalized = user.copy()
if "email" in normalized:
normalized["email"] = normalized["email"].strip().lower()
yield normalized
只需要把它插入流水线即可。
十二、生成器只能遍历一次
这是初学者经常踩到的坑。
def generate_numbers():
yield 1
yield 2
yield 3
numbers = generate_numbers()
print(list(numbers))
print(list(numbers))
输出:
[1, 2, 3]
[]
第一次调用 list(numbers) 时,生成器已经被完全消费。
第二次再遍历时,它已经到达终点,因此得到空列表。
如果需要再次遍历,应重新创建生成器:
print(list(generate_numbers()))
print(list(generate_numbers()))
输出:
[1, 2, 3]
[1, 2, 3]
也可以使用生成器工厂:
def number_factory():
return (number for number in range(1, 4))
first = number_factory()
second = number_factory()
每次调用工厂函数都会创建一个新的生成器对象。
十三、生成器中的 return 有什么作用?
生成器函数中也可以使用 return。
但这里的 return 不是像普通函数一样直接把值交给 for 循环,而是表示生成器结束。
def generate_numbers():
yield 1
yield 2
return
yield 3
遍历:
for number in generate_numbers():
print(number)
输出:
1
2
yield 3 不会执行。
生成器中的:
return value
实际上会触发:
StopIteration(value)
例如:
def generator():
yield 1
return "生成完成"
g = generator()
print(next(g))
try:
next(g)
except StopIteration as error:
print(error.value)
输出:
1
生成完成
在普通 for 循环中,StopIteration 会被自动处理,因此通常看不到这个返回值。
十四、yield from:把子生成器交给外层生成器
当一个生成器需要遍历另一个可迭代对象时,可以使用 yield from。
普通写法:
def generate_all():
for number in [1, 2, 3]:
yield number
for letter in ["A", "B", "C"]:
yield letter
使用 yield from:
def generate_all():
yield from [1, 2, 3]
yield from ["A", "B", "C"]
调用:
print(list(generate_all()))
输出:
[1, 2, 3, 'A', 'B', 'C']
yield from 不只是语法缩写。
它还可以在生成器之间传递:
- 数据;
- 异常;
send()发送的值;- 子生成器的返回值。
递归展开嵌套列表时,yield from 非常实用。
def flatten(items):
for item in items:
if isinstance(item, list):
yield from flatten(item)
else:
yield item
调用:
nested = [1, [2, 3], [4, [5, 6]]]
print(list(flatten(nested)))
输出:
[1, 2, 3, 4, 5, 6]
十五、生成器表达式和生成器函数有什么区别?
除了生成器函数,Python 还支持生成器表达式。
列表推导式:
numbers = [x * x for x in range(10)]
生成器表达式:
numbers = (x * x for x in range(10))
两者最大的区别是括号和执行方式。
print(type([x for x in range(3)]))
print(type(x for x in range(3)))
输出:
<class 'list'>
<class 'generator'>
生成器表达式适合简单逻辑:
even_numbers = (x for x in range(100) if x % 2 == 0)
生成器函数更适合复杂流程:
def generate_even_numbers(limit):
for number in range(limit):
if number % 2 == 0:
yield number
一般来说:
逻辑简单:使用生成器表达式
逻辑复杂:使用生成器函数
十六、生成器的 send 方法
生成器不仅可以向外产生数据,还可以通过 send() 接收数据。
def receiver():
while True:
value = yield
print(f"接收到:{value}")
使用:
generator = receiver()
next(generator)
generator.send("Python")
generator.send("Generator")
输出:
接收到:Python
接收到:Generator
第一次必须调用:
next(generator)
或:
generator.send(None)
把生成器推进到第一个 yield。
这是因为生成器启动前,还没有暂停在某个可以接收数据的位置。
再看一个简单的累计器:
def accumulator():
total = 0
while True:
value = yield total
if value is None:
break
total += value
调用:
generator = accumulator()
print(next(generator))
print(generator.send(10))
print(generator.send(20))
print(generator.send(5))
输出:
0
10
30
35
虽然现代 Python 异步开发更多使用 async 和 await,但理解 send() 有助于深入理解协程的历史与底层思想。
十七、如何正确关闭生成器?
生成器对象支持 close() 方法。
def generate_data():
try:
yield 1
yield 2
yield 3
finally:
print("释放资源")
调用:
generator = generate_data()
print(next(generator))
generator.close()
输出:
1
释放资源
调用 close() 后,Python 会在生成器内部触发 GeneratorExit。
这意味着生成器可以通过 try...finally 释放资源。
但在处理文件时,更推荐直接将资源管理放在 with 语句中。
def read_lines(file_path):
with open(file_path, "r", encoding="utf-8") as file:
for line in file:
yield line
当生成器正常结束或被正确关闭时,文件会被释放。
十八、性能测试:生成器一定更快吗?
很多人会把“生成器更省内存”误解为“生成器一定更快”。
实际上,两者不能简单画等号。
生成器的主要优势是:
降低峰值内存
支持流式处理
减少无效计算
缩短首个结果等待时间
但在某些需要反复访问全部数据的场景中,列表可能更快。
可以进行简单测试:
import time
def list_version(n):
return [x * x for x in range(n)]
def generator_version(n):
return (x * x for x in range(n))
n = 1_000_000
start = time.perf_counter()
numbers = list_version(n)
result = sum(numbers)
print("列表版本:", time.perf_counter() - start)
start = time.perf_counter()
numbers = generator_version(n)
result = sum(numbers)
print("生成器版本:", time.perf_counter() - start)
测试结果会受到 Python 版本、硬件和运行环境影响。
通常应该从业务需求出发:
- 数据量大,优先考虑生成器;
- 只遍历一次,适合生成器;
- 需要快速获得第一个结果,适合生成器;
- 需要索引、切片和重复访问,列表更方便;
- 数据量很小,不必为了生成器增加复杂度。
生成器不是列表的替代品,而是面向不同问题的数据处理工具。
十九、什么时候应该使用普通函数?
以下场景更适合普通函数。
1. 返回单个明确结果
def calculate_total(price, quantity):
return price * quantity
2. 数据规模较小
def get_weekdays():
return [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
]
3. 结果需要多次遍历
users = load_users()
for user in users:
validate(user)
for user in users:
save(user)
如果 load_users() 返回列表,就可以方便地重复遍历。
4. 需要随机访问
numbers = get_numbers()
print(numbers[100])
print(numbers[-1])
print(numbers[10:20])
生成器不支持直接索引和切片。
二十、什么时候应该使用生成器函数?
以下场景通常非常适合生成器。
1. 处理大文件
def read_large_file(file_path):
with open(file_path, encoding="utf-8") as file:
for line in file:
yield line
2. 处理无限序列
def infinite_counter(start=0):
current = start
while True:
yield current
current += 1
调用:
counter = infinite_counter(100)
print(next(counter))
print(next(counter))
print(next(counter))
输出:
100
101
102
无限序列无法一次性存入列表,但可以由生成器持续产生。
3. 流式数据处理
def parse_events(event_stream):
for event in event_stream:
if event.is_valid():
yield event.to_dict()
4. 分页接口处理
def fetch_all_pages(client):
page = 1
while True:
response = client.fetch(page=page)
items = response["items"]
if not items:
break
yield from items
page += 1
5. 提前终止的数据搜索
def generate_candidates():
for number in range(10_000_000):
yield number
查找第一个符合条件的值:
result = next(
number
for number in generate_candidates()
if number > 1000 and number % 97 == 0
)
print(result)
生成器只计算到找到目标为止,后续数据不会被生成。
二十一、常见错误与解决方案
错误一:忘记生成器不会立即执行
def task():
print("任务执行")
yield 1
task()
这段代码不会输出任何内容。
正确方式:
generator = task()
next(generator)
或者:
for value in task():
print(value)
错误二:重复消费同一个生成器
numbers = (x for x in range(5))
print(sum(numbers))
print(sum(numbers))
输出:
10
0
解决方式是重新创建:
def create_numbers():
return (x for x in range(5))
print(sum(create_numbers()))
print(sum(create_numbers()))
错误三:需要列表操作却返回生成器
numbers = (x for x in range(10))
print(numbers[0])
会报错:
TypeError: 'generator' object is not subscriptable
需要随机访问时,应转成列表:
numbers = list(numbers)
print(numbers[0])
错误四:生成器内部资源未及时释放
def read_file(path):
file = open(path, encoding="utf-8")
for line in file:
yield line
这种写法在生成器未完全消费时,文件资源可能不能及时释放。
推荐使用:
def read_file(path):
with open(path, encoding="utf-8") as file:
for line in file:
yield line
使用方也可以显式关闭:
generator = read_file("data.txt")
try:
print(next(generator))
finally:
generator.close()
二十二、生成器最佳实践
1. 保持单一职责
不要让一个生成器承担太多业务逻辑。
不推荐:
def process_everything(data):
for item in data:
# 校验
# 清洗
# 转换
# 保存
# 记录日志
yield item
推荐拆分:
def validate(items):
for item in items:
if item:
yield item
def transform(items):
for item in items:
yield item.strip().lower()
def save(items):
for item in items:
save_to_database(item)
yield item
2. 明确生成器是一次性对象
函数命名和文档中应说明结果只能消费一次。
def iter_active_users(users):
"""逐个产生活跃用户。
返回一次性生成器对象。
"""
for user in users:
if user.is_active:
yield user
使用 iter_ 作为命名前缀,是一种常见且清晰的表达方式。
3. 使用 yield from 简化嵌套逻辑
def iter_records(files):
for file_path in files:
yield from read_records(file_path)
比下面的写法更简洁:
def iter_records(files):
for file_path in files:
for record in read_records(file_path):
yield record
4. 不要为了使用生成器而使用生成器
下面这种代码没有明显收益:
def get_status():
yield "success"
如果永远只返回一个简单值,普通函数通常更合适:
def get_status():
return "success"
5. 配合 itertools 使用
Python 标准库中的 itertools 为迭代器和生成器提供了大量高效工具。
from itertools import islice
numbers = (x * x for x in range(1_000_000))
first_ten = islice(numbers, 10)
print(list(first_ten))
还可以使用:
from itertools import chain, count, cycle, repeat
例如合并多个数据流:
from itertools import chain
users_a = ["Alice", "Bob"]
users_b = ["Charlie", "David"]
all_users = chain(users_a, users_b)
print(list(all_users))
二十三、普通函数与生成器的选择原则
在工程开发中,可以通过下面几个问题快速判断。
问题一:数据量是否很大?
如果数据可能达到数十万、数百万甚至更多,优先考虑生成器。
问题二:是否只需要遍历一次?
如果数据只需要消费一次,生成器通常很合适。
问题三:是否需要索引和切片?
如果需要:
data[0]
data[-1]
data[10:20]
更适合使用列表、元组等容器。
问题四:是否需要尽快得到第一个结果?
生成器可以边计算边返回,更适合低延迟场景。
问题五:是否需要重复遍历?
如果需要重复遍历,应返回容器,或者提供一个每次都能创建新生成器的工厂函数。
可以总结为:
返回完整结果:普通函数
逐步产生结果:生成器函数
数据规模较小:优先保证代码清晰
数据规模巨大:优先考虑流式处理
需要重复访问:使用列表或重新创建生成器
只消费一次:使用生成器
二十四、一个综合实战:统计大型日志中的接口错误
下面通过一个完整案例,展示生成器在真实项目中的应用。
需求如下:
- 逐行读取日志;
- 只处理 JSON 格式日志;
- 过滤错误请求;
- 按接口统计错误次数;
- 输出错误最多的接口。
示例日志:
{"path": "/api/users", "status": 200}
{"path": "/api/orders", "status": 500}
{"path": "/api/users", "status": 404}
{"path": "/api/orders", "status": 503}
第一步,逐行读取:
def read_lines(file_path):
with open(file_path, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if line:
yield line
第二步,解析 JSON:
import json
def parse_json_lines(lines):
for line in lines:
try:
yield json.loads(line)
except json.JSONDecodeError:
continue
第三步,过滤错误请求:
def filter_error_requests(records):
for record in records:
status = record.get("status", 0)
if status >= 400:
yield record
第四步,统计接口错误:
from collections import Counter
def count_errors(records):
counter = Counter()
for record in records:
path = record.get("path", "unknown")
counter[path] += 1
return counter
组合流程:
def analyze_log(file_path):
lines = read_lines(file_path)
records = parse_json_lines(lines)
errors = filter_error_requests(records)
return count_errors(errors)
调用:
result = analyze_log("access.log")
for path, count in result.most_common():
print(f"{path}: {count}")
整个处理过程可表示为:
access.log
↓
read_lines
↓
parse_json_lines
↓
filter_error_requests
↓
count_errors
↓
统计结果
前面三个步骤全部采用生成器,因此无论日志文件多大,都不需要一次性加载全部内容。
只有最终的统计字典需要保存在内存中。
这正是生成器在工程中的典型价值:
把庞大的数据集变成持续流动的小数据片段。
二十五、总结:生成器不仅是一种语法,更是一种思维方式
普通函数和生成器函数最本质的区别,可以归纳为一句话:
普通函数负责计算并返回结果,生成器函数负责按需产生结果。
普通函数通过 return 一次性结束执行,而生成器函数通过 yield 暂停执行并保存状态。
普通函数更适合:
- 常规业务逻辑;
- 返回单个结果;
- 返回小规模集合;
- 需要重复访问的数据;
- 需要索引和切片的场景。
生成器函数更适合:
- 大文件读取;
- 海量数据处理;
- 流式计算;
- 分页接口;
- 无限序列;
- 数据处理管道;
- 按需计算;
- 提前终止的搜索任务。
学习生成器时,不必一开始就深入研究所有内部细节。
可以先记住三个关键点:
调用生成器函数不会立即执行
yield 会返回数据并暂停
生成器通常只能遍历一次
当你开始面对大日志、数据库批处理、接口分页和实时数据流时,就会真正体会到生成器的价值。
它不会让所有数据同时涌入内存,而是让数据在程序中有节奏地流动。
这种“只在需要时工作,只产生当前需要的数据”的设计思想,也体现了 Python 编程中简洁、克制而高效的一面。
最后,也欢迎你思考两个问题:
你在实际项目中,是否遇到过因为一次性加载大量数据而导致内存占用过高的问题?
在你的业务场景中,还有哪些普通函数可以改造成生成器,从而让程序更加高效和优雅?
更多推荐


所有评论(0)