这篇文章介绍生成器的一些使用技巧,同时用它来替代传统容器实现一些好用的功能。
生成器是python里的一种特殊的数据类型,他是一个不断给调用方生成内容的类型。定义一个生成器需要用到生成器函数与yield关键字。
# 生成器
def generate_even(max_number):
# 一个简单的生成器,返回0到max_number之间所有的偶数
for i in range(0, max_number):
if i % 2 == 0:
yield i
# 遍历生成器对象,输出结果
for i in generate_even(100):
print(i)
# 调用next()函数可以逐步从生成器里拿到结果
g = generate_even(10)
print(next(g))
print(next(g))
print(next(g))
# 因为生成器是可迭代对象,所以可以使用容器内置函数将其转为其他容器类型。例如将它转为list容器
mylist = list(generate_even(10))
print(f'将生成器转换为list容器:type({mylist})'
在Python2时代,如果用range()生成一个非常大的数字序列,速度会非常慢。这是因为range()需要组装并返回一个巨大的列表。整个计算与内存分配会耗费大量时间。
# 在python2中的range()会将结果组装的一个列表,一次性返回所有数字。
>>> range(4)
[0, 1, 2, 3]
在python3时代,调用range()瞬间就会返回结果,因为它不在返回列表,而是返回一个类型为range的惰性计算对象。
当序列过大时,新的range()函数不再会一次性耗费大量内存和时间,生成一个巨大的列表,而是仅在迭代时按需返回数字。
range()的进化虽然简单,但它代表了一个重要的编程思维——按需生成,而不是一次性返回。
虽然都是返回结果,但yield和return的最大不同之处在于,return的返回是一次性的,使用它会直接终止整个函数执行。而yield可以逐步给调用方生成结果。
# r 是range对象,而非装满数字的列表
>>> r = range(10000000)
# 只有在迭代range对象时,他才会不断生成新的数字
>>> for i in r:
>>> print(i)
在写代码过程中,我们经常要处理一些数据,假如在批处理时数据量过大或者处理逻辑复杂导致处理速度很慢,例如下面的示例存在两点问题:
def batch_process(items):
'''
批量多个items对象
:param items:
:return:
'''
result = []
for item in range(0, items):
# 假如处理item需要花费大量时间
if item % 2 == 0:
result.append(item)
# 处理的结果拼接到列表中返回
return result
process = batch_process(10)
print(process)
为了解决上面的问题,我们可以使用生成器改写它,用yield替代列表。
def batch_yield(items):
'''
批量多个items对象
:param items:
:return:
'''
for item in range(0, items):
# 假如处理item需要花费大量时间
if item % 2 == 0:
yield item
for i in batch_yield(10):
# 满足特定条件后不在处理后面的数据
if i == 4:
break
print(i)