Как создать итеративную функцию (или объект-итератор) в python?Построить базовый итератор Python
ответ
Объекты Iterator в python соответствуют протоколу итератора, что в основном означает, что они обеспечивают два метода: __iter__()
и next()
. __iter__
возвращает объект итератора и неявно вызывается в начале циклов. Метод next()
возвращает следующее значение и неявно вызывается при каждом приращении цикла. next()
вызывает исключение StopIteration, когда больше нет значения для возврата, которое неявно захватывается с помощью циклов, чтобы остановить итерацию.
Вот простой пример счетчика:
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def next(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
for c in Counter(3, 8):
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, 8):
print c
Печатный выход будет таким же. Под капотом объект-генератор поддерживает протокол итератора и делает что-то примерно похожее на счетчик классов.
Статья Дэвида Мерца, Iterators and Simple Generators, является довольно хорошим представлением.
Обратите внимание, что функция `next()` не дает значения `yield`, она` возвращает `их. – 2012-10-17 07:03:44
Прежде всего itertools module невероятно полезно для всех видов случаев, в которых итератор был бы полезен, но здесь все, что нужно для создания итератора в Python:
выход
Разве это не круто? Выход можно использовать для замены нормального возврата в функции. Он возвращает объект одинаково, но вместо уничтожения состояния и выхода он сохраняет состояние, когда вы хотите выполнить следующую итерацию. Вот пример его в действие берутся непосредственно из itertools function list:
def count(n=0):
while True:
yield n
n += 1
Как указано в описании функций (это граф() функции из модуля itertools ...), она производит итератор, возвращает последовательные целые числа, начиная с n.
Generator expressions это целая другая червь червей (удивительные черви!). Они могут использоваться вместо List Comprehension для сохранения памяти (для понимания списка создается список в памяти, который уничтожается после использования, если не назначен переменной, но выражения генератора могут создавать объект генератора ... что является причудливым способом сказать Итератор). Ниже приведен пример определения экспрессии генератора:
gen = (n for n in xrange(0,11))
Это очень похоже нашему определению выше, за исключением итератора полного диапазона предопределено быть между 0 и 10.
Я только что нашел xrange() (это я не видел раньше ...) и добавил его к приведенному выше примеру. xrange() - это итерируемая версия range(), которая имеет то преимущество, что не является предварительным составлением списка. Было бы очень полезно, если бы у вас был гигантский массив данных для перебора и у него было столько памяти, чтобы сделать это.
Есть четыре способа построить итерационную функцию:
- создать генератор (использует yield keyword)
- использовать выражение генератора (genexp)
- создать итератор (определяет
__iter__
and__next__
(илиnext
в Python 2.x)) - создать функцию, которую Python может выполнять самостоятельно (defines
__getitem__
)
Примеры:
# generator
def uc_gen(text):
for char in text:
yield char.upper()
# generator expression
def uc_genexp(text):
return (char.upper() for char in text)
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index].upper()
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text
def __getitem__(self, index):
result = self.text[index].upper()
return result
Чтобы увидеть все четыре метода в действии:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print ch,
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__
(который должен возвращать новый итератор, который идет назад); и GetItem iteratable (uc_getitem
) должен иметь метод __len__
магии:
# for uc_iter
def __reversed__(self):
return reversed(self.text)
# for uc_getitem
def __len__(self)
return len(self.text)
Для того, чтобы ответить на вторичный вопрос полковника Panic в о бесконечном лениво оцениваемом итераторе, вот эти примеры, используя каждый из четырех методов выше:
# 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
Я вижу, что некоторые из вас делают 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
Конечно здесь можно было бы так же непосредственно сделать генератор, но для более сложных классов это может быть полезным.
Это итеративная функция без yield
. Это делает использование iter
функции и закрытия, которая держит его состояние в изменяемый (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
Этот вопрос касается истребимых объектов, а не об итераторах. В 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)
Здесь два вопроса, оба важных. Как сделать класс итерабельным (т. Е. С чем вы можете перебирать)? И как создать функцию, которая возвращает последовательность с ленивой оценкой? – 2015-07-12 13:26:31
Хорошее упражнение, я думаю, это написать класс, представляющий четные числа (бесконечную последовательность). – 2015-07-12 13:30:02
@ColonelPanic: Хорошо, добавили пример бесконечного числа в [мой ответ] (http://stackoverflow.com/a/7542261/208880). – 2016-02-04 17:25:03