【问题标题】:Python generator making a referenccePython生成器做参考
【发布时间】:2016-01-24 00:06:38
【问题描述】:

我试图使用生成器来节省一些内存,但遇到了问题。我对结果有点惊讶,因为我的理解是整数是不可变的,所以有人可以解释发生了什么吗?

>>>> a = []
>>>> for i in range(10):
....     a.append((i for _ in [0]))
....     
>>>> list(a[0])
[9]

当我使用列表推导来代替它时,它会做我想做的事:

>>>> a = []
>>>> for i in range(10):
....     a.append([i for _ in [0]])
....     
>>>> a
[[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]]

我可以有理由认为,发生的事情是生成器以某种方式获得了对i 值的“引用”,在最后一次循环之后是 9,但这似乎是反 Python 的python 没有这样的引用(至少据我所知)。

问题:

  1. 这是怎么回事?这怎么可能?我们将不胜感激提供一些 Python 文档的链接,这些文档可以准确解释正在发生的事情。
  2. 我怎样才能做我想做的事(使用带有变量的生成器,该变量将来会发生变化,但我需要当前值来做某事)?

更新:

现实用例:

def get_some_iter(a, b):
    iters = []
    for i in a:
        m = do_something(i, len(b))
        iters.append((SomeObj(j, m) for j in itertools.combinations(b, i))

    return itertools.chain(*iters)

【问题讨论】:

  • 在循环结束之前不要在 gen exp 中使用 i,因此 i 指向 9,列表 comp 将每个 i 附加到循环中,这正是您所期望的。你到底想做什么?
  • 生成器表达式创建了一个生成器对象。我假设该对象将引用我使用的所有内容,而不是引用它似乎正在做的引用。例如,如果我在一个函数中完成了这一切,然后我超出了范围,那么我将如何使用生成器?
  • 您正在附加一个引用 i 的生成器,当循环完成时 i 为 9,即当您消耗生成器时,您得到 9,它距离如何一个 lambda 在循环中工作,lambda i=i:lambda i: 之间的区别。你能添加一个小的现实用例吗?
  • 你真的想做yield from (SomeObj(j, m) for j in itertools.combinations(b, i)这样的事情吗?
  • 我猜是这样,除了我使用的是 python2.7 :(

标签: python reference generator


【解决方案1】:

生成器是惰性求值的。在您将其发送到list() 之前,该生成器没有执行任何操作。当您确实将其发送到list() 时,它就会执行。那时i 是什么?最后分配的值:9

list 理解被热切地评估。每次遇到该表达式时,都会立即评估其内容。第一次通过循环,i0,然后立即检索并存储该值。这会为每次循环迭代产生不同的值。

【讨论】:

  • 正确;谢谢。我在交互式 shell 中工作,它将返回的值保存到 _。 >_
【解决方案2】:

是的,生成器保留对 i 的引用,因为在内部实际上没有任何其他解决方案。

最好的解决方案是将它全部用作生成器:

a = ((i for _ in [0]) for i in range(10))
for g in a:
    print(list(g))
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]

如果你需要直接访问n+1个值而不获取第一个n当然也不能解决问题。如果您需要这样做,我认为最 Pythonic 的方法是构建自己的迭代器,如下所示:

class ListIter(object):
    def __init__(self, i):
        self.i= i
        self.iter = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.iter > 0:
            raise StopIteration
        else:
            self.iter += 1
            return self.i

a = [ListIter(i) for i in range(10)]
list(a[0])
[0]

【讨论】:

  • 我假设它保留了 i 引用的值,而不是对 i 本身的引用。就像我做a=[]; b=a时一样,我没有复制列表,但我复制了对列表的引用。
  • 问题是内部存储在哪里?这将是一个有点黑魔法。这就是为什么您应该构建自己的迭代器,在那里您可以明确地将 i 存储为属性。无论如何,生成器都是迭代器,所以我的 iterable 示例不会占用比您最初打算做的更多的内存,它只需要多几行但它可以工作;-)
  • @CrazyCasta, i 仅在您的生成器被评估时被评估,i 只是一个您不断指向新对象的名称,如果python将所有这些数据保存在内存中,它将失败生成器的用途。
  • @PadraicCunningham 不,我认为您误解了生成器的意义。 i 在生成器的上下文之外。生成器的要点是不保留对它一次生成的多个项目的引用,而不是不保留对上下文提供的值的引用。另外,你能告诉我它在哪里说 generate 指的是名字吗?
  • 你是对的 CrazyCasta,i 在生成器的上下文之外,但 python 无法知道在哪里存储生成器的所有 i 值。这就是为什么这里需要一个自定义类来明确告诉 Python 在哪里存储 i。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-04
相关资源
最近更新 更多