Почему итератор не выдает элементы как я хочу?

429 просмотра
0
0 Комментариев

Код

class CitiesManager(object):
    """Класс, содержащий функциональность работы с городами."""
 
    def __init__(self, cities):
        self.cities = cities
 
    def __getitem__(self, item: int):
        """Получить элемент по индексу.
 
        Args:
            item (int): индекс элемента для получения в операциях среза.
        """
        return self.cities[item]
 
    def __len__(self):
        return len(self.cities)
 
    def __iter__(self):
        """Возвратить себя как объект итератора."""
        return self
 
    def __next__(self):
        """Получить следующий элемент """
        for item in self.cities:
            yield item
 
cities = ['Петрозаводск', 'Хельсинки', 'Санкт-Петербург']
north_cities = CitiesManager(cities)
 
 
for city in north_cities:
    print(city)

выводит подобное:

<generator object __next__ at 0x7f13de432d58>
<generator object __next__ at 0x7f13de432c50>
<generator object __next__ at 0x7f13de432d58>
<generator object __next__ at 0x7f13de432c50>
<generator object __next__ at 0x7f13de432d58>

Почему?
Я хочу, что бы он выводил строки «Петрозаводск», «Хельсинки», «Санкт-Петербург»


Добавить комментарий

3 Answers

Python Опубликовано 14.12.2018
0

Потому что метод __next__ должен возвращать текущее значение итерации, а вы возвращаете новый генератор.

Есть два способа решить эту проблему. Во-первых, возвращать в качестве итератора не себя, а генератор:

def __iter__(self):
    for item in self.cities:
        yield item

Во-вторых, контролировать своё состояние, как положено итератору:

class CitiesManager(object):
    ...
 
    def __iter__(self):
        """Возвратить себя как объект итератора."""
        self._item_index = -1
        return self
 
    def __next__(self):
        """Получить следующий элемент """
        self._item_index += 1
        if len(self.cities) <= self._item_index:
            raise StopIteration
        return self.cities[self._item_index]

UPDATE: Как верно заметил @AndrioSkur, ещё более правильный вариант — это возвращать из метода __iter__ новый итератор при каждом вызове.

class CitiesManagerIter(object):
    def __init__(self, cmanager):
        self.cmanager = cmanager
        self.__current_index = -1
 
    def __iter__(self):
        return self
 
    def __next__(self):
        """Получить следующий элемент """
        self.__current_index += 1
        if len(self.cmanager.cities) <= self.__current_index:
            raise StopIteration
        return self.cmanager.cities[self.__current_index]
 
 
class CitiesManager(object):
    ...
 
    def __iter__(self):
        """Возвратить объект итератора."""
        return CitiesManagerIter(self)

Добавить комментарий
0

Во первых, __next__(self) обычная функция, это значит, она хочет return, нет yield.

Во вторых, итератор должен во вашем случае окончиться, значит, метод __next__(self) должен в какое-то время возбудить исключение StopIteration.

И так, ваш код после мелких переделок в 2 местах (см. 2 комментарии в нем) может быть например таким :

class CitiesManager(object):
    """Класс, содержащий функциональность работы с городами."""
 
    def __init__(self, cities):
        self.cities = cities
        self.i      = -1                        // Добавлено
 
    def __getitem__(self, item: int):
        """Получить элемент по индексу.
 
        Args:
            item (int): индекс элемента для получения в операциях среза.
        """
        return self.cities[item]
 
    def __len__(self):
        return len(self.cities)
 
    def __iter__(self):
        """Возвратить себя как объект итератора."""
        return self
 
    def __next__(self):                         // Изменено содержание метода
        """Получить следующий элемент """
        self.i += 1
        if self.i < len(self.cities):
            return self.cities[self.i]
        else:
            self.i = -1
            raise StopIteration
 
cities = ['Петрозаводск', 'Хельсинки', 'Санкт-Петербург']
north_cities = CitiesManager(cities)
 
 
for city in north_cities:
    print(city)

Добавить комментарий
0

Чтобы итерация заработала, достаточно убрать несовместимые или сломанные __iter__, __next__ методы из кода в вопросе.

Наличие __getitem__ и __len__ методов указывает, что вы хотите создать последовательность (Sequence), а не итератор (__next__ метод возвращает (return) следующее значение (если есть) или выбрасывает StopIteration исключение (если нет), __iter__ возвращает self для итераторов).

Одну и ту же последовательность вы можете обходить многократно. Вы можете получать индивидуальные элементы по индексу из последовательности:

>>> R = range(3)
>>> print(*R)
0 1 2
>>> print(*R)
0 1 2
>>> R[0]

Что значит * (звёздочка) и ** двойная звёздочка в Питоне?

Итератор можно обойти ровно один раз. Когда элементы закончатся и итератор выбросил StopIteration, он будет продолжать выбрасывать StopIteration (сигнализируя конец итерации):

>>> iterator = iter(range(3))
>>> print(*iterator)
0 1 2
>>> print(*iterator)
 
>>> iterator[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'range_iterator' object is not subscriptable
'range_iterator' object is not subscriptable

Второй print печатает только новую строчку так как первый print все элементы забрал из итератора. Итератор в отличии от последовательностей не поддерживает индексацию.

1) Я хочу использовать данный класс как контейнер множества городов и добавить к этому классу другой функционал, например фильтрацию по населению и т.д.

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

from collections import namedtuple
 
City = namedtuple('City', 'name latitude')
cities = [
    City('Петрозаводск', 61.77),
    City('Хельсинки', 60.17),
    City('Санкт-Петербург', 59.95)
]

легко найти самый северный город в этом списке городов:

northernmost_city = max(cities, key=lambda city: city.latitude)

Или если вы хотите напечатать имена городов в алфавитном порядке, также новый класс не нужно создавать:

for city in sorted(cities):
    print(city.name)

Вывод:

Петрозаводск
Санкт-Петербург
Хельсинки

2) я хочу хорошо понять как работают итераторы

Если вы хотите создать итератор, то уберите из кода в вопросе __getitem__, __len__ методы, а в методе __next__ возвращайте следующее значение. К примеру, вот бесконечный итератор:

class Ones:
    def __iter__(self):
        return self
    def __next__(self):
        return 1

Пример:

>>> from itertools import islice
>>> print(*islice(Ones(), 10))
1 1 1 1 1 1 1 1 1 1

Если вы хотите, чтобы закончился итератор, то следует выбросить StopIteration (и продолжать выбрасывать при последующих обращениях):

class EmptyIterator:
    def __iter__(self):
        return self
    def __next__(self):
        raise StopIteration

Этот класс определяет пустой итератор (нет элементов, сразу StopIteration выбрасывается):

>>> print(*EmptyIterator())
 
>>>

Комбинируя первые два примера итераторов, можно определить аналог итератора для списков в чистом Питоне:

class ListIterator:
    def __init__(self, lst):
        self.lst = lst
        self.i = 0
    def __iter__(self):
        return self
    def __next__(self):
        if self.i < len(self.lst):
            item = lst[self.i]
        else:
            raise StopIteration
        self.i += 1
        return item

Пример:

>>> lst = [1, 2, 3]
>>> it = ListIterator(lst)
>>> print(*it)
1 2 3
>>> print(*it)
 
>>> print(*lst)
1 2 3

Видно, что после первого обхода, элементы в итераторе закончились (второй print только новую строку печатает) и это никак не влияет на исходный список. И наоборот, изменение списка во время обхода итератора может оказать влияние на то что возвращается: В чем разница между двумя циклами for: при удалении элементов во время обхода списка.

Посмотрите ещё примеры и ссылки в ответе (особенно iter()): Различия между циклами for и while.

Добавить комментарий
Напишите свой ответ на данный вопрос.
Scroll Up