在Python中对生成器进行索引和切片
假设我有一个生成器函数,长得像这样:
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 应该长什么样,才能让它工作呢?
6 个回答
1
如果你只用一次切片,那你可以直接用~unutbu写的方法。如果你需要多次切片,就得把所有中间的值都存起来,这样才能“倒回去”使用迭代器。因为迭代器可以遍历任何东西,所以默认情况下是没有倒回功能的。
而且,由于一个可以倒回的迭代器需要存储每一个中间结果,通常情况下,它并没有比直接用list(iterator)
更有优势。
总的来说... 要么你根本不需要迭代器,要么你对情况的描述不够具体。
2
要对生成器进行切片,你可以使用来自 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
38
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
中,并在可能的情况下使用这些结果。否则,它会继续计算更多的结果,直到有足够的结果可以返回指定的元素或切片。