python中的生成器提供了一种有效的方法,可以在需要时生成数字或对象,而不必事先将所有的值存储在内存中。
简介
你可以把生成器看作是一种创建迭代器的简单方法,而不需要创建一个带有__iter__()
和__next__()
方法的类。
那么如何创建一个生成器呢?
有多种方法,但最常见的方法是用yield
而不是return
语句来声明一个函数。这样,你就可以通过for-loop来迭代它。
# Define a Generator function: squares.
def squares(numbers):
for i in numbers:
yield i*i
创建生成器并进行迭代:
# Create generator and iterate
sq_gen = squares([1,2,3,4])
for i in sq_gen:
print(i)
#> 1
#> 4
#> 9
#> 16
生成器基础知识:使用生成器的优势
现在让我们来了解一下生成器的细节。但首先让我们了解一些基础知识。
考虑一下以下两种打印0到4的数值的平方的方法。
方法一:使用列表
# Approach 1: Using list
L = [0, 1, 2, 3, 4]
for i in L:
print(i*i)
#> 0
#> 1
#> 4
#> 9
#> 16
方法2:使用范围发生器
# Approach 2: Using range
for i in range(5):
print(i*i)
#> 0
#> 1
#> 4
#> 9
#> 16
第一种方法使用一个列表,而第二种方法使用range
,它是一个生成器。虽然,两种方法的输出是一样的,但当你想迭代的对象数量大量增加时,你可以注意到两者的区别。
因为,列表对象在内存中占据了实际空间。随着列表大小的增加,比如你想迭代到5000个,所需的系统内存就会成比例地增加。
然而,生成器range
,情况就不是这样了。无论迭代多少次,生成器本身的大小都不会改变。这很有意义!
# Check size of List vs Generator.
import sys
print(sys.getsizeof(L))
print(sys.getsizeof(range(6)))
#> 120
#> 48
然而,由于range
是一个生成器,range
迭代5000个数字的内存需求并没有增加。因为,这些数值只是在需要时才生成,而不是实际存储。
# check size of a larger range
print(sys.getsizeof(range(5000)))
#> 48
这仍然是与range(6)
相同的字节数。
现在,这就是使用生成器的好处。
好的部分是,Python允许你根据你的自定义逻辑创建你自己的生成器。不过有多种方法可以做到这一点。让我们看看一些例子。
方法 1.使用屈服关键字
我们已经看到了这一点。让我们用yield
关键字来创建同样的创建数字的平方的逻辑,这次我们用一个函数来定义它。
- 定义生成器函数
def squares(numbers):
for i in numbers:
yield i*i
- 创建生成器对象
nums_gen = squares([1,2,3,4])
nums_gen
#>
注意,它只创建了一个生成器对象,而不是我们想要的值。然而。要真正生成这些值,你需要进行迭代,把它弄出来。
print(next(nums_gen))
print(next(nums_gen))
print(next(nums_gen))
print(next(nums_gen))
#> 1
#> 4
#> 9
#> 16
yield
是做什么的?
yield语句基本上负责创建可以被迭代的生成器。
现在,当你使用Yield
时会发生什么?
主要有两件事:
-
因为你在func定义中使用了
yield
语句,一个dunder__next__()
方法已经被自动添加到nums_gen
,使其成为一个可迭代的对象。所以,现在你可以调用next(nums_gen)
。 -
一旦你调用
next(nums_gen)
,它就开始执行在squares()
中定义的逻辑,直到它碰到yield
的关键字。然后,它发送产生的值,并将函数暂时暂停在该状态下而不退出。当函数在下一次被调用时,它最后被暂停的状态会被记住,并从这一点开始继续执行。这种情况一直持续到发生器耗尽为止。
这个过程的神奇之处在于,你在函数的局部名称空间内创建的所有局部变量都将在下一次迭代中可用,也就是在再次明确调用next
,或者在for循环中迭代的时候。
如果我们使用return
,函数就会退出,杀死它的本地命名空间中的所有变量。
yield
基本上使该函数记住了它的 "状态"。这个函数可以用来按照自定义的逻辑生成数值,从根本上说成为一个 "生成器"。
用完所有的值后会发生什么?
一旦值被用完,就会出现一个StopIteration
错误。你需要重新创建生成器,以便再次使用它来生成数值。
# Once exhausted it raises StopIteration error
print(next(nums_gen))
你需要重新创建它并再次运行它:
nums_gen = squares([1,2,3,4])
这一次,让我们用for-loop进行迭代:
for i in nums_gen:
print(i)
#> 1
#> 4
#> 9
#> 16
很好。
另外,你可以让生成器无休止地生成,而不至于耗尽。这可以通过把它创建为一个类来实现,这个类用一个yield
语句定义了一个__iter__()
方法。
方法2.创建使用类作为一个可迭代的对象
# Approach 3: Convert it to an class that implements a `__iter__()` method.
class Iterable(object):
def __init__(self, numbers):
self.numbers = numbers
def __iter__(self):
n = self.numbers
for i in range(n):
yield i*i
iterable = Iterable(4)
for i in iterable: # iterator created here
print(i)
#> 0
#> 1
#> 4
#> 9
现在它已经完全迭代了。
在不重新创建可迭代的情况下运行增益。
for i in iterable: # iterator again created here
print(i)
#> 0
#> 1
#> 4
#> 9
方法3.创建生成器而不使用产量
gen = (i*i for i in range(5))
gen
#> at 0x000002372CA82E40>
for i in gen:
print(i)
#> 0
#> 1
#> 4
#> 9
#> 16
再试一次,它可以被重复使用。
for i in gen:
print(i)
这个例子似乎是多余的,因为它可以很容易地用range
。
让我们看看另一个阅读文本文件的例子。让我们把句子分成一个单词列表。
gen = (i.split() for i in open("textfile.txt", "r", encoding="utf8"))
gen
#> at 0x000002372CA84190>
再次创建生成器
for i in gen:
print(i)
OUTPUT
#> ['Amid', 'controversy', 'over', '‘motivated’', 'arrest', 'in', 'sand', 'mining', 'case,']
#> ['Punjab', 'Congress', 'chief', 'Navjot', 'Singh', 'Sidhu', 'calls', 'for', '‘honest', 'CM', 'candidate’.']
#> ['Amid', 'the', 'intense', 'campaign', 'for', 'the', 'Assembly', 'election', 'in', 'Punjab,']
#> ['due', 'less', 'than', 'three', 'weeks', 'from', 'now', 'on', 'February', '20,', 'the', 'Enforcement', 'Directorate', '(ED)']
#> ['on', 'Friday', 'arrested', 'Bhupinder', 'Singh', '‘Honey’,', 'Punjab', 'Chief', 'Minister']
#> ['Charanjit', 'Singh', 'Channi’s', 'nephew,', 'in', 'connection', 'with', 'an', 'illegal', 'sand', 'mining', 'case.']
让我们再试一次,但只提取每行的前3个词。
gen = (i.split()[:3] for i in open("textfile.txt", "r", encoding="utf8"))
for i in gen:
print(i)
OUTPUT
#> ['Amid', 'controversy', 'over']
#> ['Punjab', 'Congress', 'chief']
#> ['Amid', 'the', 'intense']
#> ['due', 'less', 'than']
#> ['on', 'Friday', 'arrested']
#> ['Charanjit', 'Singh', 'Channi’s']
很好。我们已经涵盖了使用生成器工作的所有方面。希望现在对生成器的概念有所了解。