Генераторы клонирования в Python без тройника - PullRequest
0 голосов
/ 13 ноября 2018

Я пытаюсь клонировать объект генератора питона.Я привязан к дизайну, так что я не могу вносить другие изменения, кроме как получить копию генератора, возвращенную функцией, и снова использовать ее.Мне известен метод Itertools.tee (), но документация гласит:

В общем случае, если один итератор использует большинство или все данные до запуска другого итератора,быстрее использовать list () вместо tee ().

Как мне это реализовать?Вы хотите преобразовать генератор в список, скопировать его и преобразовать оба списка в итераторы / генераторы?

Ответы [ 2 ]

0 голосов
/ 13 ноября 2018

Да, это так, как вы говорите (за исключением того, что вы не копируете список):

Это говорит о том, чтобы преобразовать генератор в список, скопировать его и преобразовать оба списка в итераторы/ генераторы?

Допустим, у вас есть генератор и вы хотите сделать пять его копий.Каждая из этих копий должна давать те же значения, что и ваш оригинальный генератор.Простым решением было бы получить все значения из вашего генератора, например, преобразовав его в список, а затем используя этот список для создания новых генераторов:

def orig(n):
    yield from range(n)

orig_gen = orig(100)

for i in range(90):
    next(orig_gen)

# now we have 10 values left in gen
values_left = list(orig_gen)

def copy():
    yield from values_left

copy_gen1 = copy()
copy_gen2 = copy()
print(next(copy_gen1))
print(next(copy_gen2))

Хотя это может стать очень дорогим.Целью генератора является создание новых значений динамически.Если вы преобразуете генератор в список, вы должны выполнить все вычисления, необходимые для получения этих значений.Кроме того, если генератор выдает много значений, вы в конечном итоге будете использовать огромное количество памяти.

Именно поэтому tee() предлагает буферизованный подход.Вы должны указать, сколько копий генератора вы хотите, а затем tee() устанавливает deque (список с быстрыми добавлениями и всплывающими окнами) для каждой копии.Когда вы запрашиваете новое значение у одного из скопированных генераторов, оно берется из буфера.Только если буфер пуст, новые значения генерируются из исходного генератора.Источник указан в документах .Допустим, вы хотите получить 5 копий, используя tee(), это может выглядеть так:

  • создайте оригинальный генератор и используйте его немного
  • создайте 5 копий с tee(). каждаякопия имеет пустой буфер
  • вызов next() для копии 1. Поскольку буфер пуст, мы вызываем next() в исходном генераторе.Значение добавляется ко всем буферам.Он немедленно извлекается из буфера 1 и возвращает
  • call next() для копии 2. Буфер для копии 2 уже содержит это значение, мы извлекаем его из буфера и возвращаем его.Не нужно использовать оригинальный генератор.
  • вызов next() для копии 1. На предыдущем шаге нам не нужно было использовать оригинальный генератор, поэтому наш буфер все еще пуст.Мы называем next() на оригинальном генераторе.Значение добавляется ко всем буферам.Он немедленно вынимается из буфера 1 и возвращается
  • , вызывая next() в копии 3 дважды.Оба раза мы можем просто прочитать значение из буфера.

Если вы вызываете next() для копий с примерно одинаковой частотой, это очень эффективно.Буферы не растут, так как значения удаляются из них равномерно.Таким образом, вам не нужно хранить много значений в памяти.

Но если вы используете только одну из копий, то буферы для других копий становятся все больше и больше.В крайнем случае, если вы исчерпаете копию 1, прежде чем вы коснетесь других копий, их буферы станут списками со всеми значениями из генератора.Теперь у вас есть 4 списка со всеми значениями.Вместо простого списка со всеми значениями в простом подходе.

0 голосов
/ 13 ноября 2018

В документах говорится, что вы можете достичь того же, материализуя свою итерируемую в список, а затем "скопировать" итерируемую, используя список в качестве вспомогательного хранилища, что-то вроде:

>>> def tee_two(iterable):
...     mem = list(iterable)
...     return iter(mem), iter(mem)
...
>>> en = enumerate('abc')
>>> next(en)
(0, 'a')
>>> it1, it2 = tee_two(en)
>>> for i, x in it1:
...     print(i, x)
...
1 b
2 c
>>> for i, x in it2:
...     print(i, x)
...
1 b
2 c

Конечно, это требует материализации остальной части вашего итератора, и это не эффективно для памяти - что, если у вас есть бесконечный итератор? - но если в качестве документа указано, что «один итератор использует большую часть или все данные перед запуском другого итератора, то быстрее использовать list () вместо tee ()» и, возможно, не намного хуже в отношении памяти.

...