Почему yield возвращает итератор? - PullRequest
0 голосов
/ 24 июля 2011

Я пытаюсь понять, как работает yield, и после прочтения этого текста я поверил, что это вполне понятно.

Однако я все еще не понимаю, какова связь между yield и __iter__, потому что я только что узнал, что этот код работает:

class Vect():
    def __init__(self, *args):
        self.__a = list(args)
    def print_(self):
        print self.__a
    def __iter__(self):
        yield self.__a

asd = Vect(1,2,3,4,5)
for foo in asd:
    print foo

Я подумал, что когда у меня есть генератор (функция, которая возвращает один аргумент за раз, но возвращает столько аргументов, сколько может, пока не достигнет конца) yield работает так: «Хорошо, давайте вернем этот аргумент, но, возможно, мы еще можем вернуть другое ". Однако в моем примере у меня нет генератора, yield «возвращает» список и каким-то образом получает доступ к итератору списка. Я понятия не имею, что происходит.

Ответы [ 3 ]

9 голосов
/ 24 июля 2011

yield возвращает любой объект, переданный ему, даже если этот объект является последовательностью, генератором или другим итератором.Таким образом:

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

__iter__ вызывается для объекта, когда требуется итератор для содержимого объекта (как в случае, когда он является частью конструкции for x in obj).Вы можете использовать yield для создания генератора (поскольку генераторы являются итераторами), но в данном примере вам это не нужно.Следующее также будет работать:

def __iter__(self):
   return iter(self.__a)

Если вы хотите использовать yield, и вы хотите, чтобы итератор для Vect объектов перемещался по содержимому вектора, вы должны выдайте каждое значение отдельно :

def __iter__(self):
    for i in self.__a:
        yield i

yield означает, что __iter__ вернет генератор, а вызов next() для объекта-генератора возобновит функцию с той точки, где он последнийостановился, поскольку он перебирает __a.

=======

В ответ на дополнительный вопрос о том, как Python отслеживает, где в исполнении генератора он находится,Я полагаю, что он использует f_lasti (== "последняя инструкция") атрибута gi_frame генератора (генераторы, в отличие от обычных функций, несут с собой кадр выполнения).Вот небольшой инструментарий, показывающий, как меняются значения:

>>> import dis
>>> def g():
...     yield 1
...     for i in range(10):
...             yield i*2
... 
>>> gen = g() 
>>> dis.dis(gen.gi_code)
  2           0 LOAD_CONST               1 (1)
              3 YIELD_VALUE         
              4 POP_TOP             

  3           5 SETUP_LOOP              29 (to 37)
              8 LOAD_GLOBAL              0 (range)
              11 LOAD_CONST               2 (10)
              14 CALL_FUNCTION            1
              17 GET_ITER            
         >>   18 FOR_ITER                15 (to 36)
              21 STORE_FAST               0 (i)

   4          24 LOAD_FAST                0 (i)
              27 LOAD_CONST               3 (2)
              30 BINARY_MULTIPLY     
              31 YIELD_VALUE         
              32 POP_TOP             
              33 JUMP_ABSOLUTE           18
         >>   36 POP_BLOCK           
         >>   37 LOAD_CONST               0 (None)
              40 RETURN_VALUE        
>>> gen.gi_frame.f_lasti ## -1 because we haven't started yet
-1 
>>> gen.next()  
1
>>> gen.gi_frame.f_lasti
3
>>> gen.gi_frame.f_locals
{}
>>> gen.next() 
0
>>> gen.gi_frame.f_lasti , gen.gi_frame.f_locals
(31, {'i': 0})
>>> gen.next()
2
>>> gen.gi_frame.f_lasti , gen.gi_frame.f_locals
(31, {'i': 1})
>>> 

Обратите внимание, как значение f_lasti соответствует пронумерованной строке в разобранном коде, на котором был последний результат: он перезапускается с этой точкигенератор снова включен.

1 голос
/ 24 июля 2011

если вы проверите свой отпечаток, вы увидите, что iter возвращает список целиком, а когда вы печатаете foo, он печатает только один раз

проверьте это:

for index, foo in enumerate(asd):
    print index, foo
1 голос
/ 24 июля 2011

yield работает точно так, как вы его поняли - он возвращает один объект, который в вашем случае является списком.

Возможно, это прояснит ситуацию - измените код на:

i = 1
for foo in asd:
    print i
    i += 1
    print foo

Как вы можете видеть, есть только одна итерация - итератор содержит один элемент - список [1,2,3,4,5].

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...