Как минимизировать стоимость пространства при использовании itertools.tee для проверки следующего элемента? - PullRequest
0 голосов
/ 30 мая 2018

Я пытаюсь использовать itertools.tee, чтобы узнать, пуст ли итератор, не потребляя его полностью:

from itertools import tee
def get_iterator(i):
    i1, i2 = tee(i, 2)
    if next(i1, None) is None:
       # iterator is empty - raises some error
       pass
    return i2 # return not empty iterator to caller

Как документы состояний тройника:

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

, поэтому ясно, что когда я не пуст,i2 использует большую часть данных раньше, чем i1.Может ли простой дель преодолеть это?:

from itertools import tee
def get_iterator(i):
    i1, i2 = tee(i, 2)
    if next(i1, None) is None:
       # iterator is empty - raises some error
       pass
    del i1  # Does this overcome storage issue?
    return i2  # return not empty iterator to caller

Есть ли лучший способ достижения этой цели?

Заранее спасибо!

Ответы [ 2 ]

0 голосов
/ 30 мая 2018

Это несколько неуловимо - это зависит от недокументированного свойства функции tee вместе с намеренно неопределенным свойством сборщика мусора .Пример кода Python будет хранить все элементы с момента создания итераторов до тех пор, пока они не будут использованы каждым итератором, но можно легко представить, что итераторы будут иметь эффект очистки, который отбросит их требование на данные в очереди.Но даже в этом случае del удаляет ваше имя;это не гарантирует разрушение объекта.Такая очистка, таким образом, будет работать, но не обязательно в то время, когда вы этого ожидаете.Чтобы узнать, произойдет ли это, потребуется прочитать исходный код tee.Он имеет поддержку слабая ссылка для отдельных итераторов, что позволяет предположить, что такая оптимизация могла бы быть выполнена.

Код CPython для tee_next достаточно прост;он содержит ссылку на teedataobject, который представляет собой пакет из 57 элементов, также образуя односвязный список.Таким образом, нормальная семантика подсчета ссылок применяется на этом уровне пакета.Таким образом, в основном для CPython до 56 элементов хранятся в памяти даже после , они были использованы всеми итераторами, но не более того, поскольку обработка счетчика ссылок выполняется немедленно,Пока существуют итераторы tee, любое количество элементов между ними может удерживаться, но они не читают вперед от исходного итератора;по крайней мере, один итератор должен получить элементы через teedataobject_getitem.

Итак, основной вердикт таков: да, del будет работать в CPython, но использование tee означает, что вы временно храните пакеты из 57 элементов, а не 1. Повторение этого метода может привести к произвольному числутакие окна - за исключением tee итераторов, которые можно копировать, и будут делиться своим базовым списком.

Это интерпретация одной версии (4243df51fe43) CPython.Реализации будут отличаться, например, PyPy, IronPython, Jython или другими версиями CPython.

Например, Тройник PyPy (версия cadf868) использует аналогичный связанный список с одним элементом на ссылку, поэтому не выполняет пакетную обработку, как в этой версии CPython.

Существует один заметный ярлык, который предотвращает рост этой стоимости: обе tee реализации, которые я исследовал, создают копируемые итераторы, а также копируемые копируемые итераторы.Поэтому повторное применение tee не создает новых слоев итераторов, что является потенциальной проблемой с подходом chain.

0 голосов
/ 30 мая 2018

Я имею в виду, в вашем конкретном случае, что не так с этим

from itertools import chain
def get_iterator(i):
    try:
        first = next(i):
    except StopIteration:
       # iterator is empty - raises some error
       pass
    return chain([first], i)

Он делает то же самое, но не сохраняет ничего, кроме первого значения.

...