【问题标题】:Do iterators save memory in Python?迭代器是否在 Python 中节省内存?
【发布时间】:2016-02-01 13:24:46
【问题描述】:

我不太明白 Python 中迭代器是如何获得内存的。

>>> l1 = [1, 2, 3, 4, 5, 6]
>>> l2 = [2, 3, 4, 5, 6, 7]
>>> iz = izip(l1, l2)

我们仍然需要O(min(l1, l2)) 内存,因为我们需要将列表l1l2 加载到内存中。

我认为迭代器的主要用途之一是节省内存——但它在这里似乎没有用。

同样,下面的代码我也不清楚:

>>> l1 = ( n for n in [1, 2, 3, 4, 5, 6] )
>>> l2 = ( n for n in [2, 3, 4, 5, 6, 7] )
>>> iz = izip(l1, l2)

我们需要在将列表转换为生成器之前加载它们,对吧?这意味着我们会浪费内存。那么 - 这里的生成器还有什么意义。

这是唯一对我有意义的案例:

def build_l1():
    for n in xrange(1, 6):
       yield n

def build_l2:
    for n in xrange(2, 7):
       yield n

l1 = build_l1()
l2 = build_l2()
iz = izip(l1, l2)

没有数组被加载到内存中。因此我们在 O(1) 内存中。

Python 中迭代器函数的内存使用是如何工作的?前两种情况似乎使用O(min(l1, l2)) 内存。我认为迭代器的主要目的是节省内存,这使得前两种情况看起来毫无用处。

【问题讨论】:

  • 如果你遍历一个列表,它不会节省内存。关键是,您通常可以避免首先创建该列表。此外,当您可以渐近地保存内存时,节省内存不仅有意义。
  • 你的 build_l1build_l2 没有多大意义,xrange 已经存储了 (from, to, step)

标签: python memory


【解决方案1】:

你的例子太简单了。考虑一下:

nums = [1, 2, 3, 4, 5, 6]
nums_it = (n for n in nums)

nums_it 是一个生成器,它返回来自nums 的所有未修改的项目。显然你没有任何优势。但是考虑一下:

squares_it = (n ** 2 for n in nums)

并将其与:

squares_lst = [n ** 2 for n in nums]

使用squares_it,我们仅在请求时即时生成nums 的正方形。使用squares_lst,我们可以一次生成所有这些并将它们存储在一个新列表中。

所以,当你这样做时:

for n in squares_it:
    print(n)

就像你在做:

for n in nums:
    print(n ** 2)

但是当你这样做时:

for n in squares_lst:
    print(n)

就像你在做:

squares_lst = []
for n in nums:
    squares_lst.append(n ** 2)
for n in squares_lst:
    print(n)

如果您不需要(或没有)列表nums,则可以使用以下方法节省更多空间:

squares_it = (n ** 2 for n in xrange(1, 7))

生成器和迭代器还提供了另一个显着优势(这实际上可能是一个劣势,具体取决于具体情况):它们的评估是惰性的。

此外,生成器和迭代器可能产生无限数量的元素。一个例子是itertools.count(),它会产生 0、1、2、3,... 并且没有结束。

【讨论】:

  • 感谢您的回复。 squares_lst 的生成器版本在内存中是 O(n)。我确实同意常数因子被最小化。有没有办法使用迭代器和预定义列表来实现O(1) 内存?
  • @zero: squares_itO(1) 内存。或者你也在考虑nums的内存吗?
  • 对这些数字求平方的整个算法将是 O(n),如果我是正确的?
  • @zero:是的。但是把它改成(n ** 2 for n in xrange(1, 7))然后你就有了O(1)空间算法
  • @zero:在考虑空间复杂度时,您考虑内存除了输入。
【解决方案2】:
>>> l1 = [1, 2, 3, 4, 5, 6]
>>> l2 = [2, 3, 4, 5, 6, 7]
>>> iz = izip(l1, l2)

我们仍然需要 O(min(l1, l2)) 内存,因为我们需要将列表 l1 和 l2 加载到内存中。

使用zip,您需要存储两个原始列表和压缩列表。使用izip,您不会存储压缩列表。

如果您必须使用真实的物理机器而不是机器的一些抽象概念,那么大 O 符号在这里并不是特别有用。您的 O(n) 计算中有一个隐藏的常数乘数,它可能会在 n 趋于无穷大之前影响代码的实用性。

>>> l1 = ( n for n in [1, 2, 3, 4, 5, 6] )
>>> l2 = ( n for n in [2, 3, 4, 5, 6, 7] )
>>> iz = izip(l1, l2)

我们需要先加载列表,然后再将它们转换为生成器,对吧?这意味着我们会浪费内存。那么 - 这里的生成器还有什么意义。

这里没有生成器。每当您看到n for n in <expr>for 之前没有更复杂的表达式或在它之后没有if <expr> 过滤器时,这就是代码异味,因为您可以直接使用原始序列。只有当您将输入值转换为其他值或过滤它们时,生成器才会有用。

【讨论】:

  • 我应该注意,我正在练习编码面试,并且正在尝试使用迭代器获得 O(1) 内存的解决方案。
  • 生成器还可以过滤项目,因此n for n in sequence if predicate(n) 可能很有用,无需修改任何单独的输入值。
猜你喜欢
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多