Python生成器索引优化

3 投票
4 回答
816 浏览
提问于 2025-04-17 05:03

假设我有一个生成器,我想从中提取第10个元素,但忽略前面的9个。这个生成器是我写的一个函数,大概长这样:

def myGenerator(arg1, arg2):
    for i in arg1:
        myState = doSomeWork(i, arg2)
        yield expensiveOperation(myState)

现在我可以这样使用它,抓取第10个索引:

myGen = myGenerator(list1, list2)
tenthElement = next(itertools.islice(myGen,10,11))

我在想,这样做会不会让expensiveOperation运行10次,还是只运行一次呢? (编辑:它确实调用了10次,但我接下来想讨论的就是这个。) 有没有办法优化掉其他9次对expensiveOperation的调用,因为它们都是被丢弃的?(编辑以便更清楚)

我能想到几种其他解决方案,不用生成器函数也能得到我想要的结果,但语法没有把一个迭代函数变成生成器那么简洁,只需把return换成yield

编辑: 我并不是特别想解决这个具体的问题,而是想找一种便宜的方法来“滚动”生成器。在我现在正在处理的实际案例中,我第一次调用myGenerator时,其实并不知道我想要哪个索引。我可能先抓取第15个索引,然后是第27个,再然后是第82个。我可能可以想办法在第一次调用时切片到第15个,但接下来我还需要再滚动12个。

4 个回答

1

让我们看看会发生什么:

def expensive_operation(x):
    print 'expensive operation', x
    return x

def myGenerator():
    for i in xrange(1000):
        yield expensive_operation(i)

myGen = myGenerator()
tenthElement = next(itertools.islice(myGen,10,11))
print 'tenthElement', tenthElement

打印

expensive operation 0
expensive operation 1
expensive operation 2
expensive operation 3
expensive operation 4
expensive operation 5
expensive operation 6
expensive operation 7
expensive operation 8
expensive operation 9
expensive operation 10
tenthElement 10

最好把 expensiveOperationmyGenerator 分开,因为你的代码显示 expensiveOperation 并不影响 myState

def myGenerator(arg1, arg2):
    for i in arg1:
        myState = doSomeWork(i, arg2)
        yield myState

然后只在需要的时候再执行 expensiveOperation

4

Python无法知道某个耗时的操作是否可以跳过。比如,这个操作可能会产生一些需要执行的副作用。所以,不能直接快进一个生成器。

有一个选择:

def myGenerator(arg1, arg2):
    for i in arg1:
        myState = doSomeWork(i, arg2)
        yield functools.partial(expensiveOperation, myState)

这样做会返回一个可调用的对象,而不是直接返回实际的值。要得到实际的值,你需要调用这个生成的值。只有这样,耗时的操作才会被执行。

5

这个生成器和使用它的地方是分开的——它不知道哪些东西被丢掉了。所以,是的,它在每一步都进行那些耗时的操作。

我建议把那些耗时的操作放到生成器外面去做:

def myGenerator(arg1, arg2):
    for i in arg1:
        myState = doSomeWork(i, arg2)
        yield myState

myGen = myGenerator(list1, list2)
tenthElement = expensiveOperation(next(itertools.islice(myGen,10,11)))

撰写回答