在什么情况下你应该在python中实际使用生成器?

jan*_*n93 2 python iterator generator

我试图了解发电机并了解如何使用它们.我已经看过很多例子,并且他们一次只能产生一个结果,而不是像常规函数那样一次输出它们.但是我见过的所有例子都涉及到列表和打印通过函数生成的值.如果你想真正创建一个列表怎么办?

例如,我已经看到一个关于偶数生成偶数并打印出来的数字的例子,但如果我想要一个偶数数列表如下:

def even(k):
    for i in range(k):
        if (i%2):
           yield k

even_list = []
for i in even(100):
    even_list.append(i)
Run Code Online (Sandbox Code Playgroud)

这是否会破坏使用生成器的目的,因为它会在偶数列表中创建它.这种方法是否仍能节省一些内存/时间?

或者以下方法不使用生成器同样有效.

def even(k):
    evens_list = []
    for i in range(k):
        if (i%2):
           evens_list.append(i)
    return evens_list
Run Code Online (Sandbox Code Playgroud)

在这种情况下,生成器在哪些确切的情况下有用?

Wil*_*sem 5

这是否会破坏使用生成器的目的,因为它会在偶数列表中创建它.在这种情况下,生成器在哪些确切的情况下有用?

这有点基于意见,但在某些情况下,列表可能无法解决问题(例如,由于硬件限制).

节省CPU周期(时间)

想象一下,你有一个偶数列表,然后想要取前五个数字的总和.在Python中,我们可以使用islice,例如:

sumfirst5even = sum(islice(even(100), 5))
Run Code Online (Sandbox Code Playgroud)

如果我们首先生成一个包含100个偶数的列表(不知道我们稍后将对该列表做什么),那么我们在构建这样的列表时花费了大量的CPU周期,这是浪费的.

通过使用生成器,我们可以将其限制为仅我们真正需要的元素.所以我们只会yield前五个要素.该算法永远不会计算大于10的元素.是的,这里可能会产生任何(重大)影响.与生成列表相比," 生成器协议 " 甚至可能需要更多的CPU周期,因此对于小型列表,没有任何优势.但是现在假设我们使用了even(100000),那么我们花在生成整个列表上的"无用CPU周期"的数量可能是显着的.

节省内存

另一个潜在的好处是给我们节省了内存,并不需要产生的所有元素在内存中同时进行.

以下面的例子为例:

for x in even(1000):
    print(x)
Run Code Online (Sandbox Code Playgroud)

如果even(..)构造一个1000元素列表,那么这意味着所有这些数字需要同时成为内存中的对象.根据Python解释器,对象可能占用大量内存.例如,int接受CPython,28字节的内存.这意味着包含500个这样的ints 的列表可以占用大约14kB的内存(列表有一些额外的内存).是最Python解释维持一个"轻量级"的格局,以减少小整数的负担(这些是共用的,这样我们就不会为每个单独的对象int,我们在这个过程中构建),但仍然可以很容易积少成多.对于even(1000000),我们需要14 MB的内存.

如果我们使用生成器,而不是依赖于我们如何使用生成器,我们可能会节省内存.为什么?因为一旦我们不再需要该数字123456(因为for循环前进到下一个项目),对象"占用"的空间可以被回收,并被赋予int具有值的对象12348.所以这意味着 - 考虑到我们使用生成器的方式允许这样 - 内存使用保持不变,而对于列表,它会线性扩展.当然,生成器本身也需要进行适当的管理:如果在生成器代码中,我们构建了一个集合,那么内存当然也会增加.

在32位系统中,由于Python列表具有最大长度,因此甚至可能导致一些问题.列表最多可包含536'870'912个元素.是的,这是一个巨大的数字,但如果您想要生成给定列表的所有排列,该怎么办?如果我们将排列存储在列表中,那么这意味着对于32位系统,包含13个(或更多元素)的列表,我们将永远无法构造这样的列表.

"在线"计划

在理论计算机科学中,"在线算法"被一些研究人员定义为逐渐接收输入的算法,因此不能提前知道整个输入.

一个实际的例子可以是网络摄像头,每秒制作一个图像,并将其发送到Python网络服务器.我们当时不知道网络摄像头将在24小时内拍摄的照片将如何显示.但我们可能有兴趣发现一个旨在偷东西的窃贼.在这种情况下,帧列表将不包含所有图像.然而,生成器可以构建一个优雅的"协议",我们迭代地获取图像,检测窃贼,并发出警报,如:

for frame in from_webcam():
    if contains_burglar(frame):
        send_alarm_email('Maurice Moss')
Run Code Online (Sandbox Code Playgroud)

无限发电机

我们不需要网络摄像头或其他硬件来利用发电机的优雅.生成器可以产生"无限"序列.或者even发电机可以看起来像:

def even():
    i = 0
    while True:
        yield i
        i += 2
Run Code Online (Sandbox Code Playgroud)

这是一个最终会生成所有偶数的生成器.如果我们继续迭代它,最终我们将得到数字123'456'789'012'345'678(尽管可能需要很长时间).

如果我们想要实现一个程序,例如不断产生偶数的回文数,那么上述内容就很有用.这可能看起来像:

for i in even():
    if is_palindrome(i):
        print(i)
Run Code Online (Sandbox Code Playgroud)

因此,我们可以假设该程序将继续工作,并且不需要"更新"偶数列表.在一些使惰性编程透明的函数式语言中,程序的编写就像创建列表一样,但实际上它通常是一个生成器.

"丰富"的发电机:range(..)和朋友们

在Python很多类,当你在它们之间迭代,例如不构造列表range(1000)对象并没有先构造一个列表(它在,但不是在).该range(..)对象仅代表一个范围.一个range(..)对象是不是一台发电机,但它是一个可以产生一个迭代器对象,这就像一台发电机的类.

除了迭代之外,我们可以使用range(..)对象做各种事情,这可以通过列表实现,但不能以有效的方式实现.

如果我们想知道是否1000000000是元素range(400, 10000000000, 2),那么我们就可以写了1000000000 in range(400, 10000000000, 2).现在有一个算法可以在生成范围或构建列表的情况下检查它:它会看到元素是否intrange(..)对象的范围内(大于或等于400,小于10000000000),并且无论是产生(考虑步骤考虑),这并没有要求在这迭代.因此,会员检查可以立即完成.

如果我们生成了一个列表,这意味着Python必须枚举每个元素,直到它最终找到该元素(或到达列表的末尾).对于这样的数字1000000000,这可能很容易花费几分钟,几小时甚至几天.

我们还可以"切片"范围对象,从而产生另一个range(..)对象,例如:

>>> range(123, 456, 7)[1::4]
range(130, 459, 28)
Run Code Online (Sandbox Code Playgroud)

通过算法,我们可以立即将range(..)对象切片为新range对象.切片列表需要线性时间.这可以再次(对于大型列表)占用大量时间和内存.