Как реализовать __iter__(self) для объекта контейнера (Python)

Я написал собственный контейнерный объект.

Согласно этой странице, мне нужно реализовать этот метод на моем объекте:

__iter__(self)

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

Может кто-нибудь опубликовать фрагмент (или ссылку на ресурс), который показывает, как это сделать?

Контейнер, который я пишу, является картой (т.е. хранит значения по уникальным ключам). Диктовки можно повторять так:

for k, v in mydict.items()

В этом случае мне нужно иметь возможность вернуть два элемента (кортеж?) В итератор. До сих пор не ясно, как реализовать такой итератор (несмотря на несколько любезно предоставленных ответов). Может ли кто-нибудь пролить свет на то, как реализовать итератор для контейнерного объекта типа карты? (т.е. пользовательский класс, который действует как диктат)?

9 ответов

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

Следующее создаст итератор, который возвращает пять, а затем каждый элемент в some_list.

def __iter__(self):
   yield 5
   yield from some_list

Pre-3,3, yield from не существует, поэтому вам придется сделать:

def __iter__(self):
   yield 5
   for x in some_list:
      yield x

Другим вариантом является наследование от соответствующего абстрактного базового класса от модуля "коллекций", как описано здесь.

Если контейнер является его собственным итератором, вы можете наследовать от collections.Iterator, Вам нужно только реализовать next метод тогда.

Примером является:

>>> from collections import Iterator
>>> class MyContainer(Iterator):
...     def __init__(self, *data):
...         self.data = list(data)
...     def next(self):
...         if not self.data:
...             raise StopIteration
...         return self.data.pop()
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
4.0
3
two
1

Пока вы смотрите на collections модуль, рассмотрите наследование от Sequence, Mapping или другой абстрактный базовый класс, если это более уместно. Вот пример для Sequence подкласс:

>>> from collections import Sequence
>>> class MyContainer(Sequence):
...     def __init__(self, *data):
...         self.data = list(data)
...     def __getitem__(self, index):
...         return self.data[index]
...     def __len__(self):
...         return len(self.data)
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
1
two
3
4.0

NB: Спасибо Гленну Мейнарду за то, что он обратил мое внимание на необходимость прояснить разницу между итераторами, с одной стороны, и контейнерами, которые являются итеративными, а не итераторами, с другой.

"Итерируемый интерфейс" в Python состоит из двух методов __next__() а также __iter__(), __next__ функция является наиболее важной, так как она определяет поведение итератора, то есть функция определяет, какое значение должно быть возвращено следующим. __iter__() Метод используется для сброса начальной точки итерации. Часто вы найдете это __iter__() может просто вернуть себя, когда __init__() используется для установки начальной точки

См. Следующий код для определения обратного класса, который реализует "итеративный интерфейс" и определяет итератор для любого экземпляра из любого класса последовательности. __next__() Метод начинается в конце последовательности и возвращает значения в обратном порядке последовательности. Обратите внимание, что экземпляры из класса, реализующего "интерфейс последовательности", должны определять __len__() и __getitem__() метод.

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, seq):
        self.data = seq
        self.index = len(seq)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

>>> rev = Reverse('spam')
>>> next(rev)   # note no need to call iter()
'm'
>>> nums = Reverse(range(1,10))
>>> next(nums)
9

Обычно __iter__() просто верните self, если вы уже определили метод next() (объект генератора):

Вот фиктивный пример генератора:

class Test(object):

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

    def next(self):
        if not self.data:
           raise StopIteration
        return self.data.pop()

    def __iter__(self):
        return self

но __iter__() также можно использовать так: http://mail.python.org/pipermail/tutor/2006-January/044455.html

Если ваш объект содержит набор данных, с которыми вы хотите связать его объект, вы можете обмануть и сделать это:

>>> class foo:
    def __init__(self, *params):
           self.data = params
    def __iter__(self):
        if hasattr(self.data[0], "__iter__"):
            return self.data[0].__iter__()
        return self.data.__iter__()
>>> d=foo(6,7,3,8, "ads", 6)
>>> for i in d:
    print i
6
7
3
8
ads
6

Чтобы ответить на вопрос о отображениях: ваш предоставлен __iter__ следует перебрать ключи сопоставления. Ниже приведен простой пример, который создает сопоставление x -> x * x и работает над Python3, расширяющим отображение ABC.

import collections.abc

class MyMap(collections.abc.Mapping):
    def __init__(self, n):
        self.n = n

    def __getitem__(self, key): # given a key, return it's value
        if 0 <= key < self.n:
            return key * key
        else:
            raise KeyError('Invalid key')

    def __iter__(self): # iterate over all keys
        for x in range(self.n):
            yield x

    def __len__(self):
        return self.n

m = MyMap(5)
for k, v in m.items():
    print(k, '->', v)
# 0 -> 0
# 1 -> 1
# 2 -> 4
# 3 -> 9
# 4 -> 16

Если вы не хотите наследовать от dict как и предполагали другие, вот прямой ответ на вопрос о том, как реализовать __iter__ для грубого примера пользовательского диктата:

class Attribute:
    def __init__(self, key, value):
        self.key = key
        self.value = value

class Node(collections.Mapping):
    def __init__(self):
        self.type  = ""
        self.attrs = [] # List of Attributes

    def __iter__(self):
        for attr in self.attrs:
            yield attr.key

Это использует генератор, который хорошо описан здесь.

Так как мы наследуем от Mappingнужно также реализовать __getitem__ а также __len__:

    def __getitem__(self, key):
        for attr in self.attrs:
            if key == attr.key:
                return attr.value
        raise KeyError

    def __len__(self):
        return len(self.attrs)

Один из вариантов, который может работать в некоторых случаях, - это сделать ваш пользовательский класс наследуемым от dict, Это кажется логичным выбором, если он действует как диктат; может быть, это должен быть диктат. Таким образом, вы получаете итеративную итерацию бесплатно.

class MyDict(dict):
    def __init__(self, custom_attribute):
        self.bar = custom_attribute

mydict = MyDict('Some name')
mydict['a'] = 1
mydict['b'] = 2

print mydict.bar
for k, v in mydict.items():
    print k, '=>', v

Выход:

Some name
a => 1
b => 2

Пример наследования от dict, измените его iter, например, пропустить клавишу 2 когда в течение цикла

# method 1
class Dict(dict):
    def __iter__(self):
        keys = self.keys()
        for i in keys:
            if i == 2:
                continue
            yield i

# method 2
class Dict(dict):
    def __iter__(self):
        for i in super(Dict, self).__iter__():
            if i == 2:
                continue
            yield i
Другие вопросы по тегам