Вопрос:

Создайте базовый итератор Python

python object iterator

326215 просмотра

9 ответа

12906 Репутация автора

Как создать итерационную функцию (или итератор) в Python?

Автор: akdom Источник Размещён: 21.08.2008 12:36

Ответы (9)


100 плюса

12906 Репутация автора

Прежде всего, модуль 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 (), которая имеет преимущество в том, что не создает список заранее. Было бы очень полезно, если бы у вас был огромный массив данных для перебора, и у вас было только столько памяти, чтобы сделать это.

Автор: akdom Размещён: 21.08.2008 12:36

602 плюса

86591 Репутация автора

Решение

Объекты итераторов в 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 2: 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

Вывод на печать будет таким же. Под капотом объект генератора поддерживает протокол итератора и делает что-то примерно похожее на класс Counter.

Статья Дэвида Мерца « Итераторы и простые генераторы» - довольно хорошее введение.

Автор: ars Размещён: 23.08.2008 04:57

380 плюса

38054 Репутация автора

Существует четыре способа создания итеративной функции:

Примеры:

# 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)

Чтобы ответить на дополнительный вопрос полковника Паника о бесконечно лениво вычисляемом итераторе, вот те примеры, использующие каждый из четырех методов, описанных выше:

# 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
Автор: Ethan Furman Размещён: 24.09.2011 10:13

98 плюса

2265 Репутация автора

Я вижу , что некоторые из вас делают 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

Конечно, здесь можно также сделать генератор напрямую, но для более сложных классов это может быть полезно.

Автор: Manux Размещён: 27.07.2012 03:05

3 плюса

5243 Репутация автора

Это итеративная функция без 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
Автор: Nizam Mohamed Размещён: 03.03.2016 05:55

11 плюса

181 Репутация автора

Этот вопрос касается итеративных объектов, а не итераторов. В 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)
Автор: aq2 Размещён: 21.03.2016 05:39

2 плюса

1815 Репутация автора

Если вы ищете что-то короткое и простое, возможно, вам этого будет достаточно:

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]
Автор: Daniil Mashkin Размещён: 26.04.2018 08:38

0 плюса

111 Репутация автора

Вдохновленный ответом Мэтта Грегори, здесь есть более сложный итератор, который будет возвращать 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)
Автор: Ace.Di Размещён: 13.07.2018 05:34

3 плюса

790 Репутация автора

Все ответы на этой странице действительно хороши для сложного объекта. Но для тех , которые содержат встроенные 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 string)

Может использоваться как:

for x in Test("abcde"):
    print(x)

# prints
# a
# b
# c
# d
# e
Автор: John Strood Размещён: 14.08.2018 08:25
32x32