Оптимизация индексации генератора Python - PullRequest
3 голосов
/ 26 октября 2011

Допустим, у меня есть генератор, из которого я хочу извлечь 10-й элемент, но игнорировать первый 9. Генератор - это написанная мной функция, которая выглядит примерно так:

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

Теперь яможно использовать его и извлечь из него десятый индекс, например:

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

Мне интересно, работает ли он expensiveOperation десять раз или только один раз?(РЕДАКТИРОВАТЬ: он вызывает его 10 раз, но эта следующая часть меня интересует.) Есть ли способ оптимизировать остальные 9 вызовов до expensiveOperation, поскольку они отбрасываются?(отредактировано для ясности)

Я могу подумать о нескольких других решениях, которые не включают использование функции генератора и дали бы именно то, что я хочу, но синтаксис не так чист, как просто превращение итеративной функции вгенератор, заменив return на yield.

РЕДАКТИРОВАТЬ: я не обязательно пытаюсь решить эту конкретную проблему, а не искать способ недорогой "прокрутить" генератор.В реальном случае, с которым я сейчас работаю, я не знаю, какой индекс мне нужен, когда я впервые вызываю myGenerator.Я могу взять 15-й индекс, затем 27-й, а затем 82-й.Я мог бы, вероятно, найти способ нарезать до 15-го числа при первом вызове, но затем мне нужно прокрутить еще 12 в следующий раз.

Ответы [ 4 ]

5 голосов
/ 26 октября 2011

Генератор изолирован от своего потребителя - он не знает, что выбрасывается.Так что, да, он выполняет дорогостоящую операцию на каждом шаге.

Я бы просто перенес дорогую операцию за пределы генератора:

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)))
4 голосов
/ 26 октября 2011

У python нет возможности узнать, что дорогая операция может быть пропущена. Например, это может иметь побочные эффекты, которые должны произойти. Таким образом, нет возможности быстрой пересылки генератора.

Один вариант:

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

Это вернет вызываемый объект вместо фактического значения. Чтобы получить фактическое значение, вы называете полученное значение. Только тогда будут выполнены дорогостоящие операции.

1 голос
/ 26 октября 2011

Посмотрим, что получится:

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

Лучше всего было бы отделить expensiveOperation от myGenerator, поскольку ваш код предполагает, что expensiveOperation не влияет на myState.

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

Тогда применяйте expensiveOperation только тогда, когда вы этого хотите.

0 голосов
/ 26 октября 2011

Генераторы предназначены для потребления по одному предмету за раз. Хотя для создания требуется больше работы, то, что вы должны использовать в вашем случае, является итеративным:

class myIterable():
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2
    def __getitem__(self, index):
        myState = doSomeWork(self.arg1[index], self.arg2)
        return expensiveOperation(myState)

myIter = myIterable(list1, list2)
tenthElement = myIter[10]

Вам нужно будет добавить больше кода к __getitem__, если вы хотите поддерживать фрагменты и отрицательную индексацию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...