Как сделать пользовательский объект итерабельным?

У меня есть list объектов пользовательского класса (пример ниже).

Использование: list(itertools.chain.from_iterable(myBigList)) Я хотел "объединить" все подстраницы stations в один большой список. Поэтому я подумал, что мне нужно сделать свой пользовательский класс итерабельным.

Вот пример моего пользовательского класса.

class direction(object) :
    def __init__(self, id) :
        self.id = id              
        self.__stations = list()

    def __iter__(self):
        self.__i = 0                #  iterable current item 
        return iter(self.__stations)

    def __next__(self):
        if self.__i<len(self.__stations)-1:
            self.__i += 1         
            return self.__stations[self.__i]
        else:
            raise StopIteration

Я реализовал __iter__ и __next__, но он не работает. Их даже не называют.

Любая идея, что я мог сделать неправильно?

Примечание. Использование Python 3.3

Ответ 1

__iter__ - это то, что вызывается при попытке выполнить итерацию над экземпляром класса:

>>> class Foo(object):
...     def __iter__(self):
...         return (x for x in range(4))
... 
>>> list(Foo())
[0, 1, 2, 3]

__next__ - это то, что вызывается на объекте, который возвращается из __iter__ (на python2.x, it next, not __next__). Я обычно называю их обоими так, чтобы код работал с...):

class Bar(object):
   def __init__(self):
       self.idx = 0
       self.data = range(4)
   def __iter__(self):
       return self
   def __next__(self):
       self.idx += 1
       try:
           return self.data[self.idx-1]
       except IndexError:
           self.idx = 0
           raise StopIteration  # Done iterating.
   next = __next__  # python2.x compatibility.

Ответ 2

просто реализовать __iter__ должно быть достаточно.

class direction(object) :
    def __init__(self, id) :
        self.id = id              
        self.__stations = list()

    def __iter__(self):
        #return iter(self.__stations[1:]) #uncomment this if you wanted to skip the first element.
        return iter(self.__stations)


a = direction(1)
a._direction__stations= range(5)

b = direction(1)
b._direction__stations = range(10)

import itertools
print list(itertools.chain.from_iterable([a,b]))
print list(itertools.chain.from_iterable([range(5),range(10)]))

вывод:

[0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Смотрите здесь, почему он _direction__stations

Любой идентификатор формы __spam (по крайней мере, два ведущих символа подчеркивания, не более одного нижнего подчеркивания) заменяется на текст classname_spam, где classname - это текущее имя класса с лидирующим подчеркиванием (s).

Ответ 3

Вы также можете подклассифицировать list:

class Direction(list):
    def __init__(self, seq=[], id_=None):
        list.__init__(self,seq)
        self.id = id_ if id_ else id(self)

    def __iter__(self):
        it=list.__iter__(self) 
        next(it)                       # skip the first...
        return it  

d=Direction(range(10))
print(d)       # all the data, no iteration
# [0, 1, 2, 3, 4]

print (', '.join(str(e) for e in d))     # 'for e in d' is an iterator
# 1, 2, 3, 4

т.е. пропускает первый.

Работает и с вложенными списками:

>>> d1=Direction([range(5), range(10,15), range(20,25)])
>>> d1
[range(0, 5), range(10, 15), range(20, 25)]
print(list(itertools.chain.from_iterable(d1)))
[10, 11, 12, 13, 14, 20, 21, 22, 23, 24]