Как создать итерационную функцию (или итератор) в Python?
Итераторские объекты в 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__
)Примеры:
Чтобы увидеть все четыре метода в действии:
Что приводит к:
Примечание :
Два типа генератора (
uc_gen
иuc_genexp
) не могут бытьreversed()
; простой итератор (uc_iter
) должен был бы использовать__reversed__
магический метод (который, согласно документации , должен возвращать новый итератор, но возвращающийself
работу (по крайней мере, в CPython)); и getitem iteratable (uc_getitem
) должен иметь__len__
магический метод:Чтобы ответить на вторичный вопрос полковника Паника о бесконечно лениво вычисляемом итераторе, вот те примеры, использующие каждый из четырех методов выше:
Что приводит к (по крайней мере для моего образца):
Как выбрать, какой использовать? Это в основном дело вкуса. Чаще всего я вижу два метода: генераторы и протокол итератора, а также гибрид (
__iter__
возвращающий генератор).Выражения генератора полезны для замены списочных представлений (они ленивы и поэтому могут экономить ресурсы).
Если требуется совместимость с более ранними версиями Python 2.x, используйте
__getitem__
.источник
uc_iter
должен истечь, когда это будет сделано (в противном случае он будет бесконечным); если вы хотите сделать это снова, вы должны получить новый итератор, позвонивuc_iter()
снова.self.index = 0
в__iter__
так что вы можете повторять много раз. В противном случае вы не можете.Прежде всего, модуль itertools невероятно полезен для всех случаев, когда итератор был бы полезен, но вот все, что вам нужно для создания итератора в python:
Разве это не круто? Выход можно использовать для замены нормального возврата в функции. Он возвращает объект точно так же, но вместо того, чтобы разрушать состояние и выходить из него, он сохраняет состояние, когда вы хотите выполнить следующую итерацию. Вот пример этого в действии, извлеченный непосредственно из списка функций itertools :
Как указано в описании функций (это функция count () из модуля itertools ...), он создает итератор, который возвращает последовательные целые числа, начиная с n.
Выражения генератора - это еще одна банка червей (удивительные черви!). Они могут быть использованы вместо списка Понимания , чтобы сохранить память (списочные создать список в памяти, уничтожаются после использования , если не назначена переменный, но выражения генератора могут создать генератор объект ... который является причудливым способом говорю итератор). Вот пример определения выражения генератора:
Это очень похоже на наше определение итератора, приведенное выше, за исключением того, что задан полный диапазон от 0 до 10.
Я только что нашел xrange () (удивлен, что раньше его не видел ...) и добавил в приведенный выше пример. xrange () - это итеративная версия range (), которая имеет преимущество в том, что не создает список заранее. Было бы очень полезно, если бы у вас было огромное количество данных для перебора, и у вас было бы столько памяти для этого.
источник
Я вижу , что некоторые из вас делают
return self
в__iter__
. Я просто хотел отметить, что__iter__
сам по себе может быть генератором (таким образом, устраняя необходимость__next__
и повышаяStopIteration
исключения)Конечно, здесь можно также сделать генератор напрямую, но для более сложных классов это может быть полезно.
источник
return self
в__iter__
. Когда я собирался попробовать использоватьyield
его, я обнаружил, что ваш код делает именно то, что я хочу попробовать.next()
?return iter(self).next()
?self.current
или любого другого счетчика. Это должен быть самый популярный ответ!iter
экземпляры класса, но сами они не являются экземплярами класса.Этот вопрос касается итеративных объектов, а не итераторов. В Python последовательности тоже итерируемы, поэтому один из способов создать итерируемый класс - заставить его вести себя как последовательность, то есть дать его
__getitem__
и__len__
методы. Я проверил это на Python 2 и 3.источник
__len__()
метод.__getitem__
одного с ожидаемым поведением достаточно.Все ответы на этой странице действительно хороши для сложного объекта. Но для тех , которые содержат встроенный Iterable типов как атрибуты, как
str
,list
,set
илиdict
, или любая реализацияcollections.Iterable
, вы можете пропустить некоторые вещи в своем классе.Может использоваться как:
источник
return iter(self.string)
.Это итеративная функция без
yield
. Он используетiter
функцию и замыкание, которое сохраняет свое состояние в mutable (list
) во вложенной области видимости для python 2.Для Python 3 состояние закрытия сохраняется в неизменяемой области видимости и
nonlocal
используется в локальной области для обновления переменной состояния.Тестовое задание;
источник
iter
, но просто для ясности: это более сложно и менее эффективно, чем просто использованиеyield
основанной функции генератора; У Python есть масса поддержки интерпретатора дляyield
основанных функций генератора, которые вы не можете здесь использовать, что делает этот код значительно медленнее. Тем не менее, за него проголосовали.Если вы ищете что-то короткое и простое, возможно, вам этого будет достаточно:
пример использования:
источник
Вдохновленный ответом Мэтта Грегори, здесь есть более сложный итератор, который будет возвращать a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz
источник