Класс с iter, но не рядом - PullRequest
1 голос
/ 04 марта 2020

Для протокола итератора вы создаете метод __iter__ и __next__. Однако, как насчет следующего случая:

class Item:
    def __init__(self):
        self.name = 'James'
    def __iter__(self):
        return self

Теперь я могу сделать:

>>> i=Item()
>>> iter(i)
<__main__.Item instance at 0x10bfe6e18>

Но не:

>>> next(i)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: instance has no next() method

Насколько я знаю определение итератора / итерируемого:

  • Итерируемый имеет метод __iter__
  • Итератор имеет метод __next__

Будет ли это тогда означает, что мой элемент выше является итерируемым, но не итератором? Или это не так, потому что выполнение следующего не будет работать:

>>> for item in i:
...     print (item)
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: instance has no next() method

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

class Item:
    def __init__(self):
        self.name = 'James'
        self.i = 0
    def __iter__(self):
        return self
    def __next__(self):
        if self.i >= len(self.name): raise StopIteration
        value = self.name[self.i]
        self.i += 1
        return value

Ответы [ 3 ]

3 голосов
/ 04 марта 2020

Вы в основном правы в своих определениях, но не совсем.

  • Объект итерируемый , если у него есть метод __iter__, который возвращает итератор.
  • Объект является итератором , если у него есть метод __next__ для получения следующего значения во время итерации. Но итераторы в Python также должны быть итеративными. Все они должны иметь метод __iter__, который возвращает self.

В вашем первом примере есть метод __iter__, но потому что он возвращает self, а объект не является итератором (поскольку у него нет __next__ метода), он также не является действительным итерируемым.

Чтобы сделать итератор не итеративным, вам нужно вернуть некоторый другой объект , который является действительным итератором. Один хитрый способ сделать это - сделать __iter__ методом генератора (используя yield в его реализации). Но если у вас есть последовательность возвращаемых значений, вы также можете просто вернуть итератор для этой последовательности.

Класс в вашем последнем кодовом блоке действительно является итератором. Но если вы хотите сделать его итеративным, не являющимся его собственным итератором (возможно, потому что вы хотите иметь возможность повторять его несколько раз), вы, вероятно, захотите что-то более похожее на это:

class Item:
    def __init__(self):
        self.name = 'James'
    def __iter__(self):
        return iter(self.name)
2 голосов
/ 04 марта 2020

__iter__ и __next__, как итерируемый и итератор, это разные вещи. И хотя возможно иметь оба метода в одном классе, при __iter__, возвращающем self, это будет работать только для проверки концепций, а не для производственного кода.

Итерация будет иметь __iter__ метод, который возвращает объект с __next__. Если оба экземпляра совпадают с

, то это просто демонстрация с ошибочным кодом


class Item:
   def __init__(self, data):
       self.data = data
   def __iter__(self):
       self.counter = 0
       return self
   def __next__(self):
       self.counter += 1
       if self.counter > len(self.data):
            raise StopIteration()
       return self.data[self.counter - 1]

Это будет работать - но если вы попытаетесь создать два независимых Итераторы одного и того же экземпляра Item не будут работать должным образом - поскольку оба будут использовать один и тот же счетчик - атрибут counter в экземпляре.

Редко, однако, нужно реализовать __next__: если __iter__ записано как функция генератора, имеющая yield вместо возврата self, это будет просто работать. Python вызовет __next__ на генераторе, созданном автоматически при каждом вызове __iter__:

, это работает


class Item:
   def __init__(self, data):
       self.data = data
   def __iter__(self):
       for item in self.data:
            yield item

Как видите, правильный путь является «бесполезным» и намного более простым, и в этом случае также может быть реализован путем возврата независимого итератора для данных. (Реализация yield необходима, если для получения каждого элемента требуются пользовательские вычисления)

это также работает


class Item:
   def __init__(self, data):
       self.data = data
   def __iter__(self):
       return iter(self.data)

(в этом случае Python вызовет __next__ на итераторе, созданном для self.data)

Если вы действительно хотите реализовать __next__, объект с этим методом должен отслеживать любые счетчики или указатели, необходимые для получения следующих элементов, и это должно быть независимо от экземпляра хоста. Самый простой способ сделать это - иметь второй класс, связанный с вашим первым, и вместо этого __iter__ вернуть его экземпляр:

рабочий "полный" пример


class Item:
   def __init__(self, data):
       self.data = data

   def __iter__(self):
       return ItemIterator(self)

class ItemIterator:
   def __init__(self, item):
      self.item = item
      self.counter = 0

   def __next__(self):
       self.counter += 1
       if self.counter > len(self.item.data):
            raise StopIteration()
       return self.item.data[self.counter - 1]

2 голосов
/ 04 марта 2020

Значит ли это, что мой элемент выше является итеративным, но не итератором?

Нет; чтобы быть итеративным, метод __iter__ должен возвращать итератор. Ваш нет.

Согласно глоссарию в официальных Python документах:

Итерируемый

Объект, способный возвращать своих членов по одному. Примеры итераций включают в себя все типы последовательностей (например, list, str и tuple) и некоторые непоследовательные типы, такие как dict, файловые объекты и объекты любых классов, которые вы определяете с помощью метода __iter__() или с помощью метода __getitem__(), который реализует семантику последовательности.

Итерации могут использоваться в for l oop и во многих других местах, где требуется последовательность (zip(), map(), …).

Поскольку ваш элемент не "способен возвращать своих членов по одному" и не может использоваться в for l oop [или] в других местах, где требуется последовательность ", она не повторяется.

Обратите также внимание, что метод __next__ недостаточен для того, чтобы быть итератором; итератор также должен иметь метод __iter__ , который возвращает себя :

Объекты итератора также должны реализовывать этот метод; они должны вернуться сами.

...