2015-05-12 5 views
16

При обертывании (внутреннего) итератора часто необходимо перенаправить метод __iter__ в базовый итерируемый. Рассмотрим следующий пример:"yield from iterable" vs "return iter (iterable)"

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() - это образец, который я использовал до сих пор.

ответ

11

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

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

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

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

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

Вот тест сравнения разборку двух подходов, а также показывает исключение tracebacks: 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 
+0

Предполагая, что цель состоит в том, чтобы просто передать/разоблачить основной Iterable, т.е. без изменения/инъекции других предметов, я не думаю, что я буду придерживаться 'ИТЭР()', потому что: 1) если I не добавляйте никакого дополнительного поведения, мне не нужно, чтобы он появлялся в трассировке исключения (я думаю?). 2), чтобы избежать использования глобального 'iter()' я мог бы вызвать 'return internal_iterable .__ iter __()'. Что касается третьего поднятого вами вопроса, мне придется подумать о том, что это значит. – PeterE