Ответы:
Итераторские объекты в python соответствуют протоколу итератора, что в основном означает, что они предоставляют два метода: __iter__()
и __next__()
.
__iter__
Возвращает объект итератора и неявно вызывается в начале петли.
__next__()
Метод возвращает следующее значение и неявно вызывается при каждом приращении цикла. Этот метод вызывает исключение StopIteration, когда больше нет возвращаемого значения, которое неявно захватывается циклами для остановки итерации.
Вот простой пример счетчика:
class Counter:
def __init__(self, low, high):
self.current = low - 1
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 2: def next(self)
self.current += 1
if self.current < self.high:
return self.current
raise StopIteration
for c in Counter(3, 9):
print(c)
Это напечатает:
3
4
5
6
7
8
Это проще написать с помощью генератора, как описано в предыдущем ответе:
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
Вывод на печать будет таким же. Под капотом объект генератора поддерживает протокол итератора и делает что-то примерно похожее на класс Counter.
Статья Дэвида Мерца « Итераторы и простые генераторы» - довольно хорошее введение.
__next__
. counter
является итератором, но это не последовательность. Он не хранит свои значения. Например, не следует использовать счетчик в цикле for с двойным вложением.
__iter__
(в дополнение к в __init__
). В противном случае объект может быть повторен только один раз. Например, если вы говорите ctr = Counters(3, 8)
, то вы не можете использовать for c in ctr
более одного раза.
Counter
является итератором, и итераторы должны повторяться только один раз. При сбросе self.current
в __iter__
, то вложенный цикл над Counter
будет полностью разрушен, и все виды предполагаемого поведения итераторов (что вызов iter
на них идемпотентно) нарушается. Если вы хотите иметь возможность повторять итерацию ctr
более одного раза, она должна быть повторяемой без итератора, при которой каждый раз __iter__
вызывается новый итератор . Попытка смешать и сопоставить (итератор, который неявно сбрасывается при __iter__
вызове) нарушает протоколы.
Counter
бы итератор был не итератором, вы удалили бы определение целиком __next__
/ next
и, возможно, переопределили __iter__
бы функцию-генератор той же формы, что и генератор, описанный в конце этого ответа (за исключением случаев, когда вместо границ) исходя из аргументов __iter__
, они были бы аргументы , чтобы __init__
сохранить на self
и доступны из self
в __iter__
).
Существует четыре способа создания итеративной функции:
__iter__
и__next__
(или next
в Python 2.x))__getitem__
)Примеры:
# generator
def uc_gen(text):
for char in text.upper():
yield char
# generator expression
def uc_genexp(text):
return (char for char in text.upper())
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text.upper()
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text.upper()
def __getitem__(self, index):
return self.text[index]
Чтобы увидеть все четыре метода в действии:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print(ch, end=' ')
print()
Что приводит к:
A B C D E
A B C D E
A B C D E
A B C D E
Примечание :
Два типа генератора ( uc_gen
и uc_genexp
) не могут быть reversed()
; простой итератор ( uc_iter
) должен был бы использовать __reversed__
магический метод (который, согласно документации , должен возвращать новый итератор, но возвращающий self
работу (по крайней мере, в CPython)); и getitem iteratable ( uc_getitem
) должен иметь __len__
магический метод:
# for uc_iter we add __reversed__ and update __next__
def __reversed__(self):
self.index = -1
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += -1 if self.index < 0 else +1
return result
# for uc_getitem
def __len__(self)
return len(self.text)
Чтобы ответить на вторичный вопрос полковника Паника о бесконечно лениво вычисляемом итераторе, вот те примеры, использующие каждый из четырех методов выше:
# generator
def even_gen():
result = 0
while True:
yield result
result += 2
# generator expression
def even_genexp():
return (num for num in even_gen()) # or even_iter or even_getitem
# not much value under these circumstances
# iterator protocol
class even_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
# getitem method
class even_getitem():
def __getitem__(self, index):
return index * 2
import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
limit = random.randint(15, 30)
count = 0
for even in iterator():
print even,
count += 1
if count >= limit:
break
print
Что приводит к (по крайней мере для моего образца):
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
Как выбрать, какой использовать? Это в основном дело вкуса. Чаще всего я вижу два метода: генераторы и протокол итератора, а также гибрид ( __iter__
возвращающий генератор).
Выражения генератора полезны для замены списочных представлений (они ленивы и поэтому могут экономить ресурсы).
Если требуется совместимость с более ранними версиями Python 2.x, используйте __getitem__
.
uc_iter
должен истечь, когда это будет сделано (в противном случае он будет бесконечным); если вы хотите сделать это снова, вы должны получить новый итератор, позвонив uc_iter()
снова.
self.index = 0
в __iter__
так что вы можете повторять много раз. В противном случае вы не можете.
Прежде всего, модуль itertools невероятно полезен для всех случаев, когда итератор был бы полезен, но вот все, что вам нужно для создания итератора в python:
Уступать
Разве это не круто? Выход можно использовать для замены нормального возврата в функции. Он возвращает объект точно так же, но вместо того, чтобы разрушать состояние и выходить из него, он сохраняет состояние, когда вы хотите выполнить следующую итерацию. Вот пример этого в действии, извлеченный непосредственно из списка функций itertools :
def count(n=0):
while True:
yield n
n += 1
Как указано в описании функций (это функция count () из модуля itertools ...), он создает итератор, который возвращает последовательные целые числа, начиная с n.
Выражения генератора - это еще одна банка червей (удивительные черви!). Они могут быть использованы вместо списка Понимания , чтобы сохранить память (списочные создать список в памяти, уничтожаются после использования , если не назначена переменный, но выражения генератора могут создать генератор объект ... который является причудливым способом говорю итератор). Вот пример определения выражения генератора:
gen = (n for n in xrange(0,11))
Это очень похоже на наше определение итератора, приведенное выше, за исключением того, что задан полный диапазон от 0 до 10.
Я только что нашел xrange () (удивлен, что раньше его не видел ...) и добавил в приведенный выше пример. xrange () - это итеративная версия range (), которая имеет преимущество в том, что не создает список заранее. Было бы очень полезно, если бы у вас было огромное количество данных для перебора, и у вас было бы столько памяти для этого.
Я вижу , что некоторые из вас делают return self
в __iter__
. Я просто хотел отметить, что __iter__
сам по себе может быть генератором (таким образом, устраняя необходимость __next__
и повышая StopIteration
исключения)
class range:
def __init__(self,a,b):
self.a = a
self.b = b
def __iter__(self):
i = self.a
while i < self.b:
yield i
i+=1
Конечно, здесь можно также сделать генератор напрямую, но для более сложных классов это может быть полезно.
return self
в __iter__
. Когда я собирался попробовать использовать yield
его, я обнаружил, что ваш код делает именно то, что я хочу попробовать.
next()
? return iter(self).next()
?
self.current
или любого другого счетчика. Это должен быть самый популярный ответ!
iter
экземпляры класса, но сами они не являются экземплярами класса.
Этот вопрос касается итеративных объектов, а не итераторов. В Python последовательности тоже итерируемы, поэтому один из способов создать итерируемый класс - заставить его вести себя как последовательность, то есть дать его __getitem__
и __len__
методы. Я проверил это на Python 2 и 3.
class CustomRange:
def __init__(self, low, high):
self.low = low
self.high = high
def __getitem__(self, item):
if item >= len(self):
raise IndexError("CustomRange index out of range")
return self.low + item
def __len__(self):
return self.high - self.low
cr = CustomRange(0, 10)
for i in cr:
print(i)
__len__()
метод. __getitem__
одного с ожидаемым поведением достаточно.
Все ответы на этой странице действительно хороши для сложного объекта. Но для тех , которые содержат встроенный Iterable типов как атрибуты, как str
, list
, set
илиdict
, или любая реализация collections.Iterable
, вы можете пропустить некоторые вещи в своем классе.
class Test(object):
def __init__(self, string):
self.string = string
def __iter__(self):
# since your string is already iterable
return (ch for ch in self.string)
# or simply
return self.string.__iter__()
# also
return iter(self.string)
Может использоваться как:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
return iter(self.string)
.
Это итеративная функция без yield
. Он использует iter
функцию и замыкание, которое сохраняет свое состояние в mutable ( list
) во вложенной области видимости для python 2.
def count(low, high):
counter = [0]
def tmp():
val = low + counter[0]
if val < high:
counter[0] += 1
return val
return None
return iter(tmp, None)
Для Python 3 состояние закрытия сохраняется в неизменяемой области видимости и nonlocal
используется в локальной области для обновления переменной состояния.
def count(low, high):
counter = 0
def tmp():
nonlocal counter
val = low + counter
if val < high:
counter += 1
return val
return None
return iter(tmp, None)
Тестовое задание;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
iter
, но просто для ясности: это более сложно и менее эффективно, чем просто использование yield
основанной функции генератора; У Python есть масса поддержки интерпретатора для yield
основанных функций генератора, которые вы не можете здесь использовать, что делает этот код значительно медленнее. Тем не менее, за него проголосовали.
Если вы ищете что-то короткое и простое, возможно, вам этого будет достаточно:
class A(object):
def __init__(self, l):
self.data = l
def __iter__(self):
return iter(self.data)
пример использования:
In [3]: a = A([2,3,4])
In [4]: [i for i in a]
Out[4]: [2, 3, 4]
Вдохновленный ответом Мэтта Грегори, здесь есть более сложный итератор, который будет возвращать a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz
class AlphaCounter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
alpha = ' abcdefghijklmnopqrstuvwxyz'
n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
if n_current > n_high:
raise StopIteration
else:
increment = True
ret = ''
for x in self.current[::-1]:
if 'z' == x:
if increment:
ret += 'a'
else:
ret += 'z'
else:
if increment:
ret += alpha[alpha.find(x)+1]
increment = False
else:
ret += x
if increment:
ret += 'a'
tmp = self.current
self.current = ret[::-1]
return tmp
for c in AlphaCounter('a', 'zzz'):
print(c)