【问题标题】:How much memory does a function with yield use?yield 函数使用多少内存?
【发布时间】:2015-12-03 10:24:49
【问题描述】:

我无法理解 yield 关键字。
我了解程序执行时会发生什么影响,但我并不真正了解它使用了多少内存。

我将尝试使用示例来解释我的疑问。
假设我们有三个函数:

HUGE_NUMBER = 9223372036854775807

def function1():
    for i in range(0, HUGE_NUMBER):
        yield i

def function2():
    x = range(0, HUGE_NUMBER)
    for i in x:
        yield i

def function3(file):
    with open(file, 'r') as f:
        dictionary = dict(csv.reader(f, delimiter = ' '))
    for k,v in dictionary.iteritems():
        yield k,v

如果我遍历第一个函数返回的生成器,那么巨大的范围是否真的存储在内存中?

第二个函数呢?

如果我对第三个函数返回的生成器进行迭代(而不是只创建该字典并直接对其进行迭代),我的程序会使用更少的内存吗?

【问题讨论】:

  • 我觉得我的标题不太好。如果有人对如何改进它有任何想法,请随时这样做。
  • 是的,只要您第一次调用next(),它就会存储在内存中。如果您使用的是 Python 2,请使用 xrange(),否则在 Python 3 中就可以了。相关:stackoverflow.com/a/25457580/846892
  • 哪个 Python 版本?
  • yield 视为在函数产生后冻结函数的执行,直到从生成器中提取下一个值(通过调用next(gen)。因为此时您已经构建了巨大的范围并将其绑定到该函数范围内的x,您的内存已经消失,yield 不会改变这一事实。使用像xrange() 这样的惰性迭代器(或imap()izip(),等)结合它但是会。
  • @user2864740:他们使用dict.iteritems(),所以Python 2.x。

标签: python memory generator yield


【解决方案1】:

需要存储由 Python 2 range() 函数生成的巨大列表,是的,并且会在生成器函数的整个生命周期内占用内存。

生成器函数可以节省内存,前提是它生成的结果是根据需要计算的,但 range() 函数会预先生成所有结果

你可以只计算下一个数字:

def function1():
    i = 0
    while i < HUGE_NUMBER:
        yield i
        i += 1

您会得到相同的结果,但您不会一次性存储整个范围的所有数字。这基本上就是循环 xrange() object 所做的;它根据要求计算数字。 (在 Python 3 中 xrange() 替换为 range())。

这同样适用于您的function3;您首先将整个文件读入字典,因此在您迭代时仍会为您存储在内存中。无需将整个文件读入内存,然后再生成每个元素。你可以遍历文件并让行:

def function3(file):
    seen = set()
    with open(file, 'r') as f:
        reader = csv.reader(f, delimiter = ' ')
        for k, v in reader:
            if k in seen:
                # already seen
                continue
            seen.add(k)
            yield k, v

这仅存储避免重复的键(就像字典一样),但不存储值。迭代生成器时内存会增加。如果重复不是问题,您可以完全省略跟踪看到的键:

def function3(file):
    with open(file, 'r') as f:
        reader = csv.reader(f, delimiter = ' ')
        for k, v in reader:
            yield k, v

甚至

def function3(file):
    with open(file, 'r') as f:
        reader = csv.reader(f, delimiter = ' ')
        return reader

毕竟阅读器是可迭代的。

【讨论】:

  • @zch: 当然,但这不是 Python 3。range() 对象与 Python 2 中的 xrange() 相同,但支持大整数(大于 sys.maxint ) 并添加了其他一些细节。它基本上像生成器一样计算数字。
  • 哦,那么,我是否正确理解 yield 的全部意义在于使代码更漂亮,因为它是对如何生成下一项的抽象级别(您可以在不调用函数的情况下使用产量,但可能会使您的代码可读性降低)?
  • @iCanLearn:yield 只是让您构建一个生成器函数。该函数如何产生值以及该函数使用什么内存仍然取决于程序员。您可以使用它们构建内存效率更高的程序,但这不是给定的,就像给某人专业的建筑商工具并不意味着他们可以建造坚固实用的房子。 :-)
  • @iCanLearn:这里的代码也不一定非要漂亮才能提高内存效率。
  • @iCanLearn:top answer to this question 或许能帮上忙?
【解决方案2】:

生成器对象包含对函数作用域的引用,并在其中包含扩展所有本地对象。减少内存使用的方法是在每个可能的级别使用迭代器,而不仅仅是在顶层。

【讨论】:

    【解决方案3】:

    如果你想检查一个对象使用了多少内存,你可以关注this post作为代理。我发现它很有帮助。

    “试试这个:

    sys.getsizeof(object)
    

    getsizeof() 调用对象的 sizeof 方法,如果对象由垃圾收集器管理,则会增加额外的垃圾收集器开销。”

    A recursive recipe

    【讨论】:

    • 不要发布只是链接到其他页面的答案。另外,这个问题已经 3 年了,已经得到了回答。
    • 反之,回答老问题没问题。问题不是在这里发布解决方案的内容,链接很容易失效,您的答案将失效。
    猜你喜欢
    • 1970-01-01
    • 2012-01-07
    • 2010-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-04
    • 2011-04-17
    • 1970-01-01
    相关资源
    最近更新 更多