概要
python中的生成器是一种特殊的迭代器,如果按照c语言的说法,就是一种特殊的指针,但是python语言的一个语言特性是兼容了函数化编程,类似lambda匿名函数机制。
本文重点介绍生成器表达式的使用,是一种很快捷,节约内存的写法。
生成器(Generator)是一种特殊的迭代器,它能让你用一种非常高效的方式遍历序列。它的核心思想是:按需生成数据,而不是一次性创建所有数据。
想象一下你有一百万个数字要处理。如果你把它们全部加载到内存中,可能会导致程序变慢甚至崩溃。生成器就像一个“懒惰”的工厂,它只在你需要下一个数字时,才生产并提供给你,用完就丢弃,因此它能大大节省内存。
生成器和普通函数的区别
生成器看起来像一个普通的函数,但它最大的不同是使用 yield
关键字而不是 return
。
return
:当你调用一个普通函数时,它会执行到return
语句,然后返回一个值,并彻底结束。下次再调用这个函数时,它会从头开始执行。yield
:当你调用一个生成器函数时,它会执行到yield
语句,然后返回一个值并暂停。它的状态(包括所有局部变量和执行位置)会被保存下来。下次你再次要求它提供值时,它会从上次暂停的地方继续执行,直到遇到下一个yield
。
怎么创建生成器?
有两种主要方式创建生成器:
1. 生成器函数
这是最常见的方式,和定义普通函数一样,只是将 return
换成 yield
。
def simple_generator():yield 1yield 2yield 3# 调用生成器函数,会返回一个生成器对象
gen = simple_generator()# 每次调用 next(),它都会继续执行到下一个 yield
print(next(gen)) # 输出: 1
print(next(gen)) # 输出: 2
print(next(gen)) # 输出: 3# 当没有更多的 yield 语句时,会引发 StopIteration 异常
# print(next(gen)) # 这行会报错
通常,我们会用 for
循环来遍历生成器,因为 for
循环会自动处理 StopIteration
异常。
for item in simple_generator():print(item)
# 输出:
# 1
# 2
# 3
2. 生成器表达式
伪代码演示:
(你想生成什么 for 你要迭代什么 in 哪个序列 if 你的条件)
这是一种更简洁的写法,类似于列表推导式,但用圆括号 ()
而不是方括号 []
。
列表推导式(List Comprehension):一次性生成所有数据,并存入列表。
squares_list = [x*x for x in range(10)]
print(squares_list) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
生成器表达式(Generator Expression):按需生成数据,返回一个生成器对象。
squares_gen = (x*x for x in range(10))
print(squares_gen) # <generator object <genexpr> at ...># 只有当你遍历它时,数据才会被生成
for item in squares_gen:print(item)
生成器与列表的区别
从整体来看,区别最大的就是生成器的内存占用是很小的,下面看一个例子:
列表 (List) 的内存消耗
当你使用列表推导式或手动构建一个列表时,Python 会立即分配足够的内存来存储所有的元素。
例如:
all_squares = [x*x for x in range(1, 1000001)]
要执行这行代码,Python 会一次性计算 100 万个数字的平方,并将它们全部存储在一个列表中。如果每个整数占用 4 字节,那么这个列表至少需要 1,000,000 * 4 = 4 MB
的内存,再加上列表本身的一些开销。
这个过程是立即的、一次性的。这很方便,但如果数据量非常大,它可能会耗尽你的系统内存,导致程序崩溃或运行缓慢。
生成器 (Generator) 的内存消耗
与列表不同,生成器是惰性求值的。它不会一次性计算并存储所有元素。它只在需要时才计算下一个值。
例如:
all_squares_gen = (x*x for x in range(1, 1000001))
当你执行这行代码时,Python 并没有做任何计算。它只是创建了一个生成器对象。这个对象只存储了如何计算下一个值的指令(即 x*x
)以及当前的状态(即 x
的值)。
当你开始迭代这个生成器时,比如在一个 for
循环中,它会一个接一个地生成值,并且只在内存中保留当前正在使用的那个值。
类型 | 工作原理 | 内存使用 |
列表 | 一次性创建并存储所有元素。 | 消耗大量内存,与元素数量成正比。 |
生成器 | 逐个按需生成元素。 | 消耗极少内存,与元素数量无关。 |
生成器有这样的优点,归功于python这样高级语言的自动垃圾回收机制
生成器:即时分配,即时销毁
当生成器在 for
循环中被调用时,它会:
计算下一个值。
返回这个值。
立即释放与这个值相关的内存。
上面的例子里,生成器每计算一次,就会把上一次的计算好的数据删除回收,节约了空间。
生成器具体应用例子
下面是两端程序,实现的内容是一样的,但是第一个使用一般的函数定义写的,第二个是用匿名函数lambda和生成器写的,比较两者的区别
def count_fives(n):"""Return the number of values i from 1 to n (including n)where sum_digits(n * i) is 5.>>> count_fives(10) # Among 10, 20, 30, ..., 100, only 50 (10 * 5) has digit sum 51>>> count_fives(50) # 50 (50 * 1), 500 (50 * 10), 1400 (50 * 28), 2300 (50 * 46)4"""i = 1count = 0while i <= n:if sum_digits(n * i) == 5:count += 1i += 1return countdef count_primes(n):"""Return the number of prime numbers up to and including n.>>> count_primes(6) # 2, 3, 53>>> count_primes(13) # 2, 3, 5, 7, 11, 136"""i = 1count = 0while i <= n:if is_prime(i):count += 1i += 1return count
第二段
def sum_digits(y):"""Return the sum of the digits of non-negative integer y."""total = 0while y > 0:total, y = total + y % 10, y // 10return totaldef is_prime(n):"""Return whether positive integer n is prime."""if n == 1:return Falsek = 2while k < n:if n % k == 0:return Falsek += 1return Truedef count_cond(condition):"""Returns a function with one parameter N that counts all the numbers from1 to N that satisfy the two-argument predicate function Condition, wherethe first argument for Condition is N and the second argument is thenumber from 1 to N.>>> count_fives = count_cond(lambda n, i: sum_digits(n * i) == 5)>>> count_fives(10) # 50 (10 * 5)1>>> count_fives(50) # 50 (50 * 1), 500 (50 * 10), 1400 (50 * 28), 2300 (50 * 46)4>>> is_i_prime = lambda n, i: is_prime(i) # need to pass 2-argument function into count_cond>>> count_primes = count_cond(is_i_prime)>>> count_primes(2) # 21>>> count_primes(3) # 2, 32>>> count_primes(4) # 2, 32>>> count_primes(5) # 2, 3, 53>>> count_primes(20) # 2, 3, 5, 7, 11, 13, 17, 198""""*** YOUR CODE HERE ***"return lambda n: sum(1 for i in range(1,n+1) if condition(n,i))
套用生成器表达式:
(你想生成什么 for 你要迭代什么 in 哪个序列 if 你的条件)
这个代码意味着遍历1到n的序列,如果条件满足condition,则生成1,而外部的sum()函数就会把1加起来,实现计数。
一行就解决了这个问题,函数式编程的泛用性可见一斑
为什么使用生成器?
节省内存:这是最大的优势。它不会一次性把所有数据都存到内存里,特别适合处理大数据集。
延迟计算:数据只在需要时才被生成,这对于某些计算密集型任务非常有用。
可读性高:生成器函数比编写一个自定义迭代器类要简单得多。
总结
生成器是按需生成数据的迭代器。
用
yield
关键字创建生成器函数。用圆括号
()
创建生成器表达式。它们的主要优点是内存高效和延迟计算。
如果你需要处理大量数据或者进行无限序列的操作,生成器是 Python 中一个非常有用的工具。