【问题标题】:Index and Slice a Generator in Python在 Python 中对生成器进行索引和切片
【发布时间】:2011-01-20 07:51:22
【问题描述】:

假设我有一个如下所示的生成器函数:

def fib():
    x,y = 1,1
    while True:
        x, y = y, x+y
        yield x

理想情况下,我可以只使用 fib()[10]fib()[2:12:2] 来获取索引和切片,但目前我有为这些事情使用 itertools。我不能使用生成器来替换列表。

我相信解决方案是将 fib() 包装在一个类中:

class Indexable(object):
    ....

fib_seq = Indexable(fib())

Indexable 应该是什么样子才能完成这项工作?

【问题讨论】:

    标签: python generator


    【解决方案1】:
    import itertools
    
    class Indexable(object):
        def __init__(self,it):
            self.it = iter(it)
        def __iter__(self):
            return self.it
        def __getitem__(self,index):
            try:
                return next(itertools.islice(self.it,index,index+1))
            except TypeError:
                return list(itertools.islice(self.it,index.start,index.stop,index.step))
    

    你可以这样使用它:

    it = Indexable(fib())
    print(it[10])
    #144
    print(it[2:12:2])
    #[610, 1597, 4181, 10946, 28657]
    

    请注意,it[2:12:2] 不会返回 [3, 8, 21, 55, 144],因为由于调用 it[10],迭代器已经推进了 11 个元素。

    编辑:如果您希望 it[2:12:2] 返回 [3, 8, 21, 55, 144],则可以改用它:

    class Indexable(object):
    
        def __init__(self, it):
            self.it = iter(it)
            self.already_computed = []
    
        def __iter__(self):
            for elt in self.it:
                self.already_computed.append(elt)
                yield elt
    
        def __getitem__(self, index):
            try:
                max_idx = index.stop
            except AttributeError:
                max_idx = index
            n = max_idx - len(self.already_computed) + 1
            if n > 0:
                self.already_computed.extend(itertools.islice(self.it, n))
            return self.already_computed[index]
    

    此版本将结果保存在self.already_computed 并使用这些结果 如果可能的话。否则,它会计算更多的结果,直到它有足够多的结果 返回索引元素或切片。

    【讨论】:

    • 为了表现出与列表相同的行为,getitem 需要倒带生成器。有没有简单的方法可以做到这一点?
    • 我不知道是否有任何方法可以做到这一点,简单与否,但 Indexable 类可以将所有已经生成的项目存储在一个列表中。然后__getitem__ 将直接从列表中提取数字,在必要时先推进生成器之后。
    • 感谢 MatrixFrog;这就是我最终所做的。
    • 这是一个方便的解决方案。看起来有两种方法可以完成我的问题,使用像你一样的结果缓存或复制迭代器。这只是使用更多内存或 cpu 之间的权衡。
    【解决方案2】:

    所以基于 ~unutbu 的代码并添加一点 itertools.tee:

    import itertools
    
    class Indexable(object):
        def __init__(self, it):
            self.it = it
    
        def __iter__(self):
            self.it, cpy = itertools.tee(self.it)
            return cpy
    
        def __getitem__(self, index):
            self.it, cpy = itertools.tee(self.it)
            if type(index) is slice:
                return list(itertools.islice(cpy, index.start, index.stop, index.step))
            else:
                return next(itertools.islice(cpy, index, index+1))
    

    【讨论】:

    • 对,但是这个选项每次使用都会烧CPU。对于太长而无法存储在 ~unutbu 的 already_computed 中的列表,此处所需的 CPU 时间也可能过多。
    • 我认为这也可能归结为生成器的复杂性,如果生成器内的代码占用大量 CPU,使用 already_computed 是最好的选择,但如果它像 xrange 这样简单,则重新计算可能会更好.
    • itertools.tee 内部不只是存储结果以便以后可以重播吗?它也无法知道它是否可以复制迭代器;考虑传入网络请求的迭代器或其他东西。
    【解决方案3】:

    如果它是 1 次使用的切片,则可以简单地使用 ~unutbu 编写的方法。如果您需要多次切片,则必须存储所有中间值,以便可以“倒回”迭代器。由于迭代器可以迭代任何东西,所以默认情况下它没有 rewind 方法。

    此外,由于倒带迭代器必须存储每个中间结果,它(在大多数情况下)与简单地执行 list(iterator) 相比没有任何好处

    基本上...您要么不需要迭代器,要么您对情况不够具体。

    【讨论】:

    • list(fib()) 最终会给你一个 MemoryError
    • 我想要第 6,000 个斐波那契数的情况怎么样。表达这一点最清晰的方法是 fib[6000],尽管有几种方法可以得到它,但它们表达得并不优雅。
    • 我的观点是,在几乎所有现实生活中,您都会让对象本身可切片,而不是在生成器上实现切片。如果不这样做,就不可能正确优化函数调用。对于任何大的斐波那契数,您不想生成整个列表只是为了得到一个数字,您可以使用一种方法来“猜测”该数字。
    【解决方案4】:

    这是 ~unutbu 修改为子类列表的答案。显然appendinsert等滥用会产生奇怪的结果!

    您确实可以免费获得 __str____repr__ 方法

    import itertools
    class Indexable(list):
        def __init__(self,it):
            self.it=it
        def __iter__(self):
            for elt in self.it:
                yield elt
        def __getitem__(self,index):
            try:
                max_idx=index.stop
            except AttributeError:
                max_idx=index
            while max_idx>=len(self):
                self.append(next(self.it))
            return list.__getitem__(self,index)
    

    【讨论】:

    • 列表与我们想要的太不一样了,所以没有用。不要这样做。
    • 我想有这个问题的人。
    【解决方案5】:

    你可以使用itertools中的islice函数来切片生成器

    from itertools import islice
    
    for i in islice(generator, 5):
        # Will be taken first 5 elems
    
    for i in islice(generator, 5, None):
        # Will be taken everything starting at 5th
    

    【讨论】:

      猜你喜欢
      • 2017-06-22
      • 2015-06-14
      • 1970-01-01
      • 2018-12-25
      • 2019-03-17
      • 2018-10-05
      • 1970-01-01
      • 1970-01-01
      • 2017-06-02
      相关资源
      最近更新 更多