"выход из итерируемого" vs "return iter (iterable)"

При обертывании (внутреннего) итератора часто необходимо перенаправить метод __iter__ в исходный итеративный. Рассмотрим следующий пример:

class FancyNewClass(collections.Iterable):
    def __init__(self):
        self._internal_iterable = [1,2,3,4,5]

    # ...

    # variant A
    def __iter__(self):
        return iter(self._internal_iterable)

    # variant B
    def __iter__(self):
        yield from self._internal_iterable

Есть ли существенная разница между вариантами A и B? Вариант A возвращает объект итератора, который был запрошен через iter() из внутреннего итерабельного. Вариант B возвращает объект-генератор, который возвращает значения из внутреннего итерабельного. По какой-то причине это выгодно? В collections.abc используется версия yield from. Вариант return iter() - это шаблон, который я использовал до сих пор.

Ответы

Ответ 1

Единственное существенное различие заключается в том, что происходит, когда исключение возникает из итерируемого. Используя return iter(), ваш FancyNewClass не появится в трассировке исключения, тогда как с yield from он будет. Как правило, хорошо иметь как можно больше информации о трассировке, хотя могут быть ситуации, когда вы хотите скрыть свою обертку.

Другие отличия:

  • return iter должно загружать имя iter из globals - это потенциально медленное (хотя вряд ли может существенно повлиять на производительность) и может быть запущено (хотя любой, кто перезаписывает такие глобальные значения, заслуживает того, что они получают).

  • С помощью yield from вы можете вставлять другие выражения yield до и после (хотя вы могли бы одинаково использовать itertools.chain).

  • Как показано, форма yield from отбрасывает любое возвращаемое значение генератора (т.е. raise StopException(value). Вы можете исправить это, написав вместо этого return (yield from iterator).

Здесь приведено сравнение сравнения разборки двух подходов, а также отображение исключений: http://ideone.com/1YVcSe

Использование return iter():

  3           0 LOAD_GLOBAL              0 (iter)
              3 LOAD_FAST                0 (it)
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 RETURN_VALUE
Traceback (most recent call last):
  File "./prog.py", line 12, in test
  File "./prog.py", line 10, in i
RuntimeError

Использование return (yield from):

  5           0 LOAD_FAST                0 (it)
              3 GET_ITER
              4 LOAD_CONST               0 (None)
              7 YIELD_FROM
              8 RETURN_VALUE
Traceback (most recent call last):
  File "./prog.py", line 12, in test
  File "./prog.py", line 5, in bar
  File "./prog.py", line 10, in i
RuntimeError