我正在阅读Python烹饪书,目前正在研究生成器。我觉得很难理解。

由于我有Java背景,那么Java中是否有对等的语言?这本书讲的是“生产者/消费者”,但当我听到这个词时,我想到了线程。

什么是发电机,你为什么要用它?显然,没有引用任何书籍(除非你能直接从一本书中找到一个体面、简单的答案)。如果你慷慨的话,还可以举个例子!


当前回答

首先,术语生成器最初在Python中定义不清,导致了很多混乱。你可能指的是迭代器和可迭代对象(参见这里)。然后在Python中还有生成器函数(返回生成器对象)、生成器对象(迭代器)和生成器表达式(求值为生成器对象)。

根据generator的术语表条目,现在的官方术语似乎是generator是“generator function”的缩写。在过去,文档对术语的定义不一致,但幸运的是,这个问题已经得到了解决。

在没有进一步说明的情况下,精确地避免使用术语“生成器”可能仍然是一个好主意。

其他回答

它有助于明确区分函数foo和生成器foo(n):

def foo(n):
    yield n
    yield n+1

Foo是一个函数。 Foo(6)是一个生成器对象。

使用生成器对象的典型方式是在循环中:

for n in foo(6):
    print(n)

循环打印

# 6
# 7

可以将生成器视为可恢复函数。

Yield的行为类似于return,产生的值被生成器“返回”。然而,与return不同的是,下一次生成器被请求一个值时,生成器的函数foo将从它停止的地方恢复——在最后一个yield语句之后——并继续运行,直到遇到另一个yield语句。

在幕后,当您调用bar=foo(6)时,生成器对象bar为您定义了一个下一个属性。

你可以自己调用它来获取foo产生的值:

next(bar)    # Works in Python 2.6 or Python 3.x
bar.next()   # Works in Python 2.5+, but is deprecated. Use next() if possible.

当foo结束时(并且没有更多的输出值),调用next(bar)将抛出StopInteration错误。

性能差异:

macOS Big Sur 11.1
MacBook Pro (13-inch, M1, 2020)
Chip Apple M1
Memory 8gb

案例1

import random
import psutil # pip install psutil
import os
from datetime import datetime


def memory_usage_psutil():
    # return the memory usage in MB
    process = psutil.Process(os.getpid())
    mem = process.memory_info().rss / float(2 ** 20)
    return '{:.2f} MB'.format(mem)


names = ['John', 'Milovan', 'Adam', 'Steve', 'Rick', 'Thomas']
majors = ['Math', 'Engineering', 'CompSci', 'Arts', 'Business']

print('Memory (Before): {}'.format(memory_usage_psutil()))


def people_list(num_people):
    result = []
    for i in range(num_people):
        person = {
            'id': i,
            'name': random.choice(names),
            'major': random.choice(majors)
        }
        result.append(person)
    return result


t1 = datetime.now()
people = people_list(1000000)
t2 = datetime.now()


print('Memory (After) : {}'.format(memory_usage_psutil()))
print('Took {} Seconds'.format(t2 - t1))

输出:

Memory (Before): 50.38 MB
Memory (After) : 1140.41 MB
Took 0:00:01.056423 Seconds

函数,返回一个包含100万个结果的列表。 在底部,我打印出内存使用情况和总时间。 基本内存使用大约是50.38兆字节,在我创建了100万条记录的列表之后,你可以看到它增加了近1140.41兆字节,花了1.1秒。


案例2

import random
import psutil # pip install psutil
import os
from datetime import datetime

def memory_usage_psutil():
    # return the memory usage in MB
    process = psutil.Process(os.getpid())
    mem = process.memory_info().rss / float(2 ** 20)
    return '{:.2f} MB'.format(mem)


names = ['John', 'Milovan', 'Adam', 'Steve', 'Rick', 'Thomas']
majors = ['Math', 'Engineering', 'CompSci', 'Arts', 'Business']

print('Memory (Before): {}'.format(memory_usage_psutil()))

def people_generator(num_people):
    for i in range(num_people):
        person = {
            'id': i,
            'name': random.choice(names),
            'major': random.choice(majors)
        }
        yield person


t1 = datetime.now()
people = people_generator(1000000)
t2 = datetime.now()

print('Memory (After) : {}'.format(memory_usage_psutil()))
print('Took {} Seconds'.format(t2 - t1))

输出:

Memory (Before): 50.52 MB
Memory (After) : 50.73 MB
Took 0:00:00.000008 Seconds

After I ran this that the memory is almost exactly the same and that's because the generator hasn't actually done anything yet it's not holding those million values in memory it's waiting for me to grab the next one. Basically it didn't take any time because as soon as it gets to the first yield statement it stops. I think that it is generator a little bit more readable and it also gives you big performance boosts not only with execution time but with memory. As well and you can still use all of the comprehensions and this generator expression here so you don't lose anything in that area. So those are a few reasons why you would use generators and also some of the advantages that come along with that.

Java中没有对等的。

这里有一个有点做作的例子:

#! /usr/bin/python
def  mygen(n):
    x = 0
    while x < n:
        x = x + 1
        if x % 3 == 0:
            yield x

for a in mygen(100):
    print a

生成器中有一个从0到n运行的循环,如果循环变量是3的倍数,则生成该变量。

在for循环的每次迭代中,都会执行生成器。如果这是生成器第一次执行,它将从开始开始,否则它将从上一次生成的时间开始。

我给出了这段代码,解释了关于生成器的3个关键概念:

def numbers():
    for i in range(10):
            yield i

gen = numbers() #this line only returns a generator object, it does not run the code defined inside numbers

for i in gen: #we iterate over the generator and the values are printed
    print(i)

#the generator is now empty

for i in gen: #so this for block does not print anything
    print(i)

使用列表推导式的经验表明,它们在Python中具有广泛的实用性。然而,许多用例不需要在内存中创建一个完整的列表。相反,它们每次只需要迭代一个元素。

例如,下面的求和代码将在内存中构建一个完整的方块列表,遍历这些值,当引用不再需要时,删除列表:

Sum ([x*x for x in range(10)])

通过使用生成器表达式来节省内存:

求和(x*x for x in range(10))

容器对象的构造函数也有类似的好处:

s = Set(word  for line in page  for word in line.split())
d = dict( (k, func(k)) for k in keylist)

生成器表达式对于sum(), min()和max()这样的函数特别有用,它们将可迭代输入减少为单个值:

max(len(line)  for line in file  if line.strip())

more