Итерируем все и вся

    image
    Насколько я успел понять по собственному опыту, при переходе на Python с другого языка программирования порой сложно привыкнуть к его специфическому подходу к циклам. Например, взять тот же самый for, который работает совершенно по-другому, нежели в других языках. Возьму на себя смелость рассказать о том, что мне самому поначалу было сложно осознать, а тем более использовать в своем коде — итераторы. Вещь на самом деле очень полезная, надо только уметь правильно ей пользоваться! ;)

    АПД: Только сейчас заметил, что тема функционального программирования сегодня популярна как никогда :) Спасибо товарищу uj2 за раскрытие такой интересной темы, поддерживаю!

    Итераторы — это специальные объекты, представляющие последовательный доступ к данным из контейнера. При этом немаловажную роль играет то, что память фактически не тратится, так как промежуточные данные выдаются по мере необходимости при запросе, поэтому фактически в памяти останутся только исходные данные и конечный результат, да и их можно читать и записывать, используя файл на диске.

    Итератор удобно использовать вместе с последовательностью. Любой объект, поддерживающий интерфейс итератора, имеет метод next(), позволяющий переходить на следующую ступень вычисления. Чтобы получить итератор по объекту (например, по списку), к нему нужно применить функцию iter(). Кстати, уже упомянутый цикл for тоже задействует итератор, только без дополнительных танцев — все делается автоматически. Для обработки сложных наборов данных можно подключить стандартный модуль itertools.

    Создадим простейший итератор вручную:
    testIt = iter([1, 2, 3, 4, 5])
    print [x for x in testIt]

    Конечно, это простейшее использование цикла for, только записанное немного по-другому.
    «И что же тут нового?» — спросите вы. Да, в принципе, ничего, просто немного залезли глубже в структуру обработки последовательностей. Но теперь, как вам такое: функция iter() может принимать не только структуру данных, которая так запросто представляет свой итератор, но и два совсем других аргумента: функцию без аргументов и стоповое значение, на котором итерация остановится. Пример:
    def getSimple(state=[]):
      if len(state) < 4:
        state.append(" ")
        return " "

    testIt2 = iter(getSimple, None)
    print [x for x in testIt2]

    Пример основан на том, что в Python при отсутствии явного возвращения значения из функции возвращается значение None.

    Теперь рассмотрим несколько функций, работа которых основана на итераторах:

    enumerate()
    Предназначена для нумерации элементов структуры данных (в том числе и другого итератора). Возвращает список кортежей, в каждом из которых первый элемент — номер (начиная с нуля), а второй — элемент исходного итератора. Пример:
    >>> print [x for x in enumerate("abcd")]
    [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]

    sorted()
    Итератор, выполняющий сортировку:
    >>> sorted('avdsdf')
    ['a', 'd', 'd', 'f', 's', 'v']

    itertools.chain()
    Итератор, позволяющий объединить два разных итератора в один. Пример:
    from itertools import chain
    it1 = iter([1,2,3])
    it2 = iter([8,9,0])
    for i in chain(it1, it2):
      print i,

    даст в результате «1 2 3 8 9 0».

    itertools.count()
    Итератор, выдающий бесконечные числа, начиная с заданного:
    for i in itertools.count(1):
      print i,
      if i > 100:
        break

    Результат:
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
    27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
    50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
    73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
    96 97 98 99 100 101

    itertools.cycle()
    Бесконечно повторяет заданную последовательность:
    tango = [1, 2, 3]
    for i in itertools.cycle(tango):
      print i,

    Результат:
    1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1..

    Так же в этом модуле есть аналоги map(), starmap(), filter() и zip(), называются они, естественно, itertools.imap(), itertools.starmap(), itertools.ifilter() и itertools.izip(). Их преимущество в том, что, в отличие от своих «обычных» аналогов тратят гораздо меньше памяти. Существует также пара фильтров: itertools.takewhile() и itertools.dropwhile():
    for i in takewhile(lambda x: x > 0, [1, -2, 3, -3]):
      print i,
    for i in dropwhile(lambda x: x > 0, [1, -2, 3, -3]):
      print i,

    Результат:
    1
    -2 3 -3

    Дело в том, что takewhile() возвращает значения, пока условие истинно, а остальные значения даже не берет из итератора (именно не берет, а не высасывает все до конца!). И, наоборот, dropwhile() ничего не выдает, пока выполняется условие, зато потом выдает все без остатка.

    Ну и, напоследок, давайте все-таки напишем свой собственный итератор (чуть было не дописал известную фразу Бендера)!

    class Fibonacci:
    """Итератор последовательности Фибоначчи до N"""
      def __init__(self, N):
        self.n, self.a, self.b, self.max = 0, 0, 1, N
      def __iter__(self):
        # сами себе итератор: в классе есть метод next()
         return self
      def next(self):
         if self.n < self.max:
           a, self.n, self.a, self.b = self.a, self.n+1, self.b, self.a+self.b
           return a
         else:
           raise StopIteration
    # Использование:
    for i in Fibonacci(100):
      print i,

    Вот, собственно и вся основная теория. Если хотите узнать побольше тонкостей — читайте специальную литературу. Если есть какие-то замечания — буду рад выслушать :)

    В статье использованы материалы лекции Романа Арвиевича Сузи «Элементы функционального программирования».
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 25
    • –4
      >Чтобы получить итератоп по объекту (например, по списку),
      интератор?
      • +2
        Итератор…
        • +2
          Спасибо, исправил. Как не вылавливай ошибки, пара всегда ускользает… Горькая правда для программиста :)
        • 0
          > Чтобы получить итератор по объекту (например, по списку), к нему нужно применить функцию iter()

          Можно ещё упомянуть, как получить итераторы для dict: itervalues(), iterkeys(), iteritems().
          • 0
            > Итераторы — это специальные объекты, представляющие последовательный доступ к данным из контейнера. При этом немаловажную роль играет то, что память фактически не тратится

            Не очень понимаю, как в таких условиях реализован sorted().
            • 0
              Ступил. Жаль комментарии удалять нельзя.
            • 0
              А я и на Delphi придумал простой интерфейс IIterator с двумя методами Rewind и Next. И кучу реализаций этого самого итератора. Конечно, Delphi не Python и на нём всё не так красиво, но и на «рабочей лошадке» Delphi хочу чуть больше хорошего.

              А началось всё с того, что в одной из программ линейный список пришлось переиначить в древовидный.
              • +1
                можно короче написать итератор:
                def Fibonacci1(n):
                    a, b = 0, 1
                    while b < n:
                        yield b
                        a, b = b, a+b
                
                if __name__ == '__main__':
                    for i in Fibonacci1(100): print i,
                

                • НЛО прилетело и опубликовало эту надпись здесь
                  • +1
                    Генераторы используются как раз для создания итераторов.
                  • 0
                    Это уже генератор
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • +1
                      Вообще в питоне это генератором зовётся. А если понимать что это «лениво»-вычисленный контейнер то и итератор подходит ;)
                      • 0
                        Генераторы — это следующий шаг. Вообще основная фишка итераторов по сравнению с обычными структурами именно в их ленивости и именно вследствии этого экономности используемых ресурсов, хотя в заметке об этом почему-то не говорится.
                        • НЛО прилетело и опубликовало эту надпись здесь
                      • 0
                        А yeld?
                        • 0
                          Хм. Собственно говоря и в обычной сишке итераторы — вполне привычный механизм. STL весь на них построен, все те же листы, мапы и векторы с их сортировками и перестановками оперируют именно итераторами. И все те же foreach и конструирование итераторов на лету — тоже обычное дело.
                          В этом ключе не очень понял чем же подход Питона к ним является таким особенным?
                          • +2
                            Наверное, yield'ом, о котором не упомянули в статье. Кстати, не в «сишке», а в С++.
                            • 0
                              Хотя бы тем, что в механизм итераторов built-in в Python. В С++ для итераторов нужны доплнительные либы. Если в проекте не используется STL (довольно распространенное явление), прийдется изобретать свои велосипеды (контейнеры и итераторы).
                            • 0
                              Итератор удобно использовать вместе с последовательностью. Любой объект, поддерживающий интерфейс итератора, имеет метод next(), позволяющий переходить на следующую ступень вычисления. Чтобы получить итератор по объекту (например, по списку), к нему нужно применить функцию iter().

                              Масло маслянное. Для меня не совсем очевидны те редкие случаи, когда вам может понадобится применять iter() к уже готовому списку, хотя вы только этим в своей статье и занимаетесь. Вы бы лучше про внутреннее устройсто итератора рассказали, помимо функции next(), ну и про генераторы тоже полезно было бы.
                              • 0
                                Прочитал. Ещё раз прочитал. Так и не понял зачем нужны и в чём отличие итераторов. И зачем они нужны.
                                Если статья для тех кто зхнает язык, то она тупо не нужна.
                                А если для тех кто не знаком с этим языком и автор хотел показать как всё круто в питоне, то тема не раскрыта.

                                Например
                                «testIt = iter([1, 2, 3, 4, 5])
                                print [x for x in testIt]

                                Конечно, это простейшее использование цикла for, только записанное немного по-другому.
                                «И что же тут нового?» — спросите вы»
                                «Для меня тут всё новое» — отвечу я.

                                PS Мне интересен язык. Но, повторюсь, тема не раскрыта…
                                • 0
                                  итераторы — самое главное это не «собрать данные, пробежаться по данным», а «взять следующее пока не кончились». Другими словами:

                                  for i in xrange(1000000000):
                                  print i
                                  break

                                  не создаст в памяти огромный список.

                                  В общем-то основной смысл итераторов раскрывается в генераторах +). Например, собираете вы веб-темплейт. Предположим данные для этого собирает некая функция-генератор:

                                  def prepare_data():
                                  yield 'asd', 123
                                  yield 'dsa', 123
                                  if not something_happens:
                                  yield 'zzz', 123

                                  Собственно суть в том что логику с something_happens можно перенести в саму функцию генератора, а не во внешнюю часть. И тем самым не делать того, чего не нужно в данном конкретном случае. К слову, это все становится заметным вовсе не на пресловутых примерах с числами и малюсенькими строками. А в больших приложениях, особенно после анализа разношерстных django и тп.
                                  • 0
                                    а вот первый пример автора с «простейшим циклом for» я и сам не понял.

                                    testit = [1,2,3,4,5]
                                    print [x for x in testit]

                                    и без итератора — аналогичный результат. А так и вовсе на лету генератор делается:
                                    testit = [1,2,3,4,5]
                                    generator = (x for x in testit)
                                    print list(generator)

                                    Зачем там вызывать iter() — неясно +).
                                • 0
                                  reversed забыли =)

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.