Итератор как логические операторы?

я наткнулся на этот код:

def myzip(*args):
    iters = map(iter, args)
    while iters:
        res = [next(i) for i in iters]
        yield tuple(res)

Я не уверен в:

  • почему понимание списка не нужно ловить StopIteration
  • как while iters работа, как я пробовал:

    x=[1,2]
    x=iter(x)
    if x: 
        print("Still True")
    next(x)
    next(x)
    if x: 
        print("Still True")
    

и он все еще печатает "Still True" в обоих случаях.

автор кода также сказал, потому что map возвращает "одноразовую итерацию"в 3.X и "как только мы запустим понимание списка внутри цикла однажды, iters будет исчерпан, но все еще верно (и res будет []) навсегда". Он предложил использовать list(map(iters, args) вместо этого, если мы используем 3.Х.

Я не уверен, как это изменение на самом деле помогает ему работать, как я думал, что даже если итераторы на StopIteration точка все еще True (на основе того, что я пробовал ранее).

Edit:

автор привел это в качестве примера

>>> list(myzip('abc', 'lmnop'))
[('a', 'l'), ('b', 'm'), ('c', 'n')]

2 ответов


есть несколько аспектов этого вопроса.

python-2.x

на map возвращает list и while iters просто убедитесь, что код не входит в цикл, если не было *args передается в функцию. Это потому, что пустой список является False и не-пустой список является True.

в случае, если нет *args он не будет входить в цикл и неявно returns, который затем поднимает StopIteration.

в случае, если есть хотя бы один аргумент while iters эквивалентно while True и он опирается на один из итераторов, чтобы поднять StopIteration после того, как исчерпаны. Эта StopIteration не должна быть поймана (по крайней мере, до Python 3.7), потому что вы хотите myzip чтобы остановить, если один iterable исчерпан.

python-3.x

В Python 3 map возвращает map экземпляр, который всегда будет считать True так while цикл эквивалентен while True.

однако в python-3 есть одна проблема.x: после итерации над map экземпляр, как только он будет исчерпан. На первой итерации (из while loop), который работает, как ожидалось, но на следующей итерации map будет пустым, и он просто создаст пустой список:

>>> it = map(iter, ([1,2,3], [3,4,5]))
>>> [next(sub) for sub in it]
[1, 3]
>>> [next(sub) for sub in it]
[]

нет ничего, что могло бы поднять StopIteration больше, поэтому он будет идти в бесконечном цикле и возвращать пустой tuple s навсегда. Это " s также причина, по которой вы не хотите вводить while цикл если iters-список пуст!

это может быть исправлено (как указано) с помощью:

iters = list(map(iter, args))

общее наблюдение

просто заметка о том, как это имело бы больше смысла:

def myzip(*args):
    if not args:
        return
    iters = [iter(arg) for arg in args]  # or list(map(iter, args))
    while True:
        res = [next(i) for i in iters]
        yield tuple(res)

если вы хотите, чтобы код был совместим с python-3.7 (спасибо @Kevin за указание на это), вам явно нужно поймать StopIterations. Для получения дополнительной информации см. PEP-479:

def myzip(*args):
    if not args:
        return
    iters = [iter(arg) for arg in args]  # or list(map(iter, args))
    while True:
        try:
            res = [next(i) for i in iters]
        except StopIteration:  
            # the StopIteration raised by next has to be catched in python-3.7+
            return
        yield tuple(res)

последний код также работает на Python-2.7 и Python 3.х StopIterations в python 3.7+


почему понимание списка не нужно ловить StopIteration

весь точка этого цикла для next() поднять StopIteration и список понимания, чтобы не поймать его. Это как этот генератор выходы; the while цикл по существу бесконечен, условие оно испытывает стабилизировано.

таким образом генератор перестает аккуратно, когда один из входов был измученный:

>>> g = myzip([1], [2, 3])
>>> next(g)
(1, 2)
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in myzip
StopIteration

на StopIteration исключение было вызвано next() вызов первого итератора в iters.

в сущности, все, что while iters: не возвращать False если ты назвал myzip() без входов вообще:

>>> list(myzip())
[]

в противном случае он может также использовать while True: для всех разница.

теперь, в Python 3,map(iter, args) возвращает итератор, а не список. Этот объект может быть повторен только один раз, но это не считается пустым и всегда будет иметь логическое значение правда. Это означает, что второй пробег через while iters: цикл iters - это верно, но [next(i) for i in iters] выполняется 0 раз, next() никогда не вызывается и не требуется StopIteration для выхода из генератора никогда не поднимается. Вот почему автор предложил list(map(iter, args)) как обойти, чтобы захватить map значения итератора в список.