在 Python 中索引和切片生成器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2322642/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-04 00:22:21  来源:igfitidea点击:

Index and Slice a Generator in Python

pythongenerator

提问by Jason Christa

Lets say I have a generator function that looks like this:

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

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

Ideally, I could just use fib()[10]or fib()[2:12:2]to get indexes and slices, but currently I have to use itertools for these things. I can't use a generator for a drop in replacement for lists.

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

I believe the solution will be to wrap fib()in a class:

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

class Indexable(object):
    ....

fib_seq = Indexable(fib())

What should Indexablelook like to make this work?

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

回答by unutbu

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))

You could use it like this:

你可以这样使用它:

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

Notice that it[2:12:2]does not return [3, 8, 21, 55, 144]since the iterator had already advanced 11 elements because of the call to it[10].

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

Edit:If you'd like it[2:12:2]to return [3, 8, 21, 55, 144]then perhaps use this instead:

编辑:如果您想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]

This version saves the results in self.already_computedand uses those results if possible. Otherwise, it computes more results until it has sufficiently many to return the indexed element or slice.

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

回答by M. Alekseev

To slice generator you can use islicefunction from itertools

要切片生成器,您可以使用以下islice函数itertools

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

回答by Jason Christa

So based off the code from ~unutbu and adding in a little itertools.tee:

所以基于 ~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))

回答by Wolph

If it's a 1-use slice than you could simply use the method written by ~unutbu. If you need to slice multiple times you would have to store all the intermediate values so you can "rewind" the iterator. Since iterators can iterate anything it would not have a rewind method by default.

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

Also, since a rewinding iterator has to store every intermediate result it would (in most cases) have no benefit over simply doing list(iterator)

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

Basically... you either don't need an iterator, or you're not specific enough about the situation.

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

回答by John La Rooy

Here is ~unutbu's answer modified to subclass list. Obviously abuse such as append, insertetc. will produce weird results!

这是修改为子类列表的 ~unutbu 的答案。显然,诸如append, 之类的滥用insert会产生奇怪的结果!

you do get __str__and __repr__methods for free though

你确实免费获得__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)