Почему переопределение __iter__ вызывает ошибку рекурсии?

#python #inheritance #overriding #iterable

#python #наследование #переопределение #повторяемый

Вопрос:

Я хочу переопределить __iter__ метод ExternalIter , который определен во внешней библиотеке, поэтому я не могу изменить исходный код ExternalIter класса. Я реализовал два разных способа переопределения в MyIter2 и MyIter3 .

 class ExternalIter:
    
    def __init__(self):
        self.x = iter([
            (1, 2),
            (3, 4),
            (5, 6),
            (7, 8),
            (9, 10)
        ])
        
    def __iter__(self):
        return self
        
    def __next__(self):
        return next(self.x)
    
class MyIter2(ExternalIter):
    
    def __iter__(self):
        while True:
            try:
                i, j = super().__next__()
                yield i
                yield j
            except StopIteration:
                break
    
class MyIter3(ExternalIter):
    
    def __iter__(self):
        for i, j in super().__iter__():
            yield i
            yield j
  

Первоначальное поведение ExternalIter заключается в следующем:

 for i in ExternalIter():
    print(i)
# (1, 2)
# (3, 4)
# (5, 6)
# (7, 8)
# (9, 10)
  

Я хочу переопределить поведение __iter__ , как в следующем примере:

 for i in MyIter2():
    print(i)
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 10
  

MyIter2 работает как исключенный. Однако альтернативный способ переопределения, как я сделал в MyIter3 throws RecursionError .

 for i in MyIter3():
    print(i)
---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
<ipython-input-8-4e1114201cac> in <module>
----> 1 for i in MyIter3():
      2     print(i)

<ipython-input-5-82da3324a8a3> in __iter__(self)
     30 
     31     def __iter__(self):
---> 32         for i, j in super().__iter__():
     33             yield i
     34             yield j

... last 1 frames repeated, from the frame below ...

<ipython-input-5-82da3324a8a3> in __iter__(self)
     30 
     31     def __iter__(self):
---> 32         for i, j in super().__iter__():
     33             yield i
     34             yield j

RecursionError: maximum recursion depth exceeded while calling a Python object
  

Можете ли вы объяснить, почему вторая реализация переопределения выдает ошибку, в то время как первый способ работает? Как я могу переопределить __iter__ использование super().__iter__() без получения RecursionError ?

Можете ли вы объяснить, как __iter__ работает в этом контексте наследования, чтобы понять, почему super().__iter__() ссылается на self , а не на родительский?

Комментарии:

1. Я действительно пытался ответить на ваш вопрос, но это трудно объяснить. Я рекомендую вам помещать операторы print в начале ваших функций, например, print("ExternalIter.__iter__") , print("MyIter3.__iter__") чтобы увидеть, что происходит. И чтобы решить вашу проблему, просто верните self.x in ExternalIter.__iter__ вместо just self , и все готово. Хороший вопрос, жаль, что у меня недостаточно английского, чтобы дать ответ: D

2. @Asocia У меня нет доступа к self.x . В реальном случае я могу выполнить итерацию только родительского элемента. Родительский элемент выполняет итерацию значений в результате сложных внутренних вычислений.

3. Обратите внимание, что ExternalIter вероятно, это не то, что вы думаете. Каждый итератор просто извлекает следующий элемент из одного общего итератора по списку кортежей.

Ответ №1:

super().__iter__() возвращает, self прежде чем for цикл выполнит свой собственный неявный вызов iter(self) , который запускает бесконечную рекурсию.

Из-за того, как работает for цикл, я не видел никакого способа использовать его здесь. Вместо этого вы можете использовать явный while цикл, чтобы избежать вызовов __iter__ .

 def __iter__(self):

    while True:
        try:
            i, j = next(self)
        except StopIteration:
            break
        yield i
        yield j
  

Это не обрабатывается self как итерируемое, только как итератор.

Комментарии:

1. Я вижу причину. Но можете ли вы предоставить более подробный ответ? Есть ли способ исправить вторую переопределяющую проблему?