Як реалізувати __iter __ (self) для контейнерного об'єкта (Python)


115

Я написав спеціальний об'єкт контейнера.

Відповідно до цієї сторінки , мені потрібно реалізувати цей метод на своєму об'єкті:

__iter__(self)

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

Чи може хтось розмістити фрагмент (або посилання на ресурс), який показує, як це зробити?

Я пишу контейнер - це карта (тобто зберігає значення за унікальними ключами). дикти можна повторити так:

for k, v in mydict.items()

У цьому випадку мені потрібно мати можливість повернути два елементи (кортеж?) В ітераторі. Досі не зрозуміло, як реалізувати такий ітератор (незважаючи на кілька відповідей, які були ласкаво надані). Може хтось, будь ласка, прожене трохи більше про те, як реалізувати ітератор для об’єкта, що нагадує контейнер? (тобто спеціальний клас, який діє як диктант)?

Відповіді:


120

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

Далі буде створено ітератор, який дає п'ять, а потім кожен елемент у списку 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 буде вичерпано.
Tim Peoples

21
StopIterationавтоматично піднімається Python, коли функція генератора повертається, або ви явно викликаєте, returnабо доходячи до кінця функції (яка, як і всі функції, має неявне значення return Noneв кінці). Явне підвищення StopIterationне є необхідним і , як в Python 3.5 буде на самому ділі не працює (див PEP 479 ): так само , як генератори перетворити returnв StopIteration, вони перетворюються явно raise StopIterationв RuntimeError.
Артур Такка

28

Іншим варіантом є успадкування відповідного абстрактного базового класу з модуля `collection, як тут задокументовано .

У випадку, якщо контейнер є власним ітератором, ви можете його успадкувати 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__()просто повертаю себе, якщо ви вже визначили наступний () метод (об’єкт генератора):

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

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


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

2
Замість перевірки hasattrвикористовуйтеtry/except AttributeError
IceArdor

9

"Ітерабельний інтерфейс" в python складається з двох методів __next__()і __iter__(). __next__Функція є найбільш важливою, так як вона визначає поведінку ітератора - тобто, функція визначає , яке значення має бути повернуто в наступному. __iter__()Метод використовується для скидання початкової точки ітерації. Часто ви виявите, що __iter__()може просто повернути себе, коли __init__()використовується для встановлення початкової точки.

Див. Наступний код для визначення класу Reverse, який реалізує "ітерабельний інтерфейс" та визначає ітератор для будь-якого примірника з будь-якого класу послідовностей. __next__()Спосіб починається в кінці значень послідовності і повертає в зворотному порядку послідовності. Зауважте, що екземпляри класу, що реалізує "інтерфейс послідовностей", повинні визначати a __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

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__для грубого прикладу нестандартний дикт:

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)

2

Один з варіантів, який може працювати в деяких випадках, - це зробити свій власний клас у спадок від 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

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.