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

116

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

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

__iter__(self)

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

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

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

for k, v in mydict.items()

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

небесный орел
источник

Ответы:

120

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

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

def __iter__(self):
   yield 5
   yield from some_list

До 3.3 yield fromне существовало, поэтому вам нужно будет сделать:

def __iter__(self):
   yield 5
   for x in some_list:
      yield x
микероби
источник
Нужно ли поднимать StopIteration? Как это определить, когда нужно остановиться?
Джонатан
1
@JonathanLeaders Когда все элементы some_listпереданы.
laike9m
Фактически - с этим вариантом использования - вам нужно поднять StopIteration только в том случае, если вы хотите прекратить выдачу значений до того, как some_list будет исчерпано.
Тим
21
StopIterationавтоматически вызывается Python при возврате функции генератора, либо при явном вызове, returnлибо при достижении конца функции (которая, как и все функции, имеет неявное значение return Noneв конце). Явное повышение StopIterationне требуется, и в Python 3.5 фактически не будет работать (см. PEP 479 ): так же, как генераторы превращаются returnв StopIteration, они превращаются в явный raise StopIterationв RuntimeError.
Артур Такка,
28

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

Если контейнер является собственным итератором, вы можете наследовать от 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 : Спасибо Гленну Мейнарду за то, что обратил мое внимание на необходимость прояснить разницу между итераторами, с одной стороны, и контейнерами, которые являются итераторами, а не итераторами, с другой.

Мухаммад Алькарури
источник
13
Не путайте итерируемые объекты и итераторы - вы не хотите наследовать от Iterator итерируемый объект, который сам не является итератором (например, контейнер).
Гленн Мейнард,
@Glenn: Вы правы, что типичный контейнер не является итератором. Я просто следил за вопросом, в котором упоминаются типы итераторов. Я думаю, что правильнее унаследовать от более подходящего варианта, как я сказал в конце ответа. Я поясню этот момент в ответе.
Мухаммад Алькарури,
13

обычно __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

mouad
источник
7
Это то, что вы делаете для классов итераторов, но вопрос касается объектов-контейнеров.
Гленн Мейнард,
11

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

>>> 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
Squirrelsama
источник
2
Вместо проверки hasattrиспользуйтеtry/except AttributeError
IceArdor
10

«Итерируемый интерфейс» в Python состоит из двух методов __next__()и __iter__(). __next__Функция является наиболее важной, так как она определяет поведение итератора - то есть, функция определяет , какое значение должно быть возвращено в следующем. __iter__()Метод используется для сброса начальной точки итерации. Часто вы обнаружите, что __iter__()может просто возвращать self, когда __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
FredAKA
источник
7

Чтобы ответить на вопрос о сопоставлениях : предоставленный вами __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
Хуан А. Наварро
источник
4

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

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)
jfritz42
источник
2

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

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

# 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
WeizhongTu
источник