Как реализовать __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