{ "cells": [ { "cell_type": "markdown", "id": "992d2d16", "metadata": {}, "source": [ "# Итераторы #" ] }, { "cell_type": "markdown", "id": "bc05e33b", "metadata": {}, "source": [ "В этой лекции мы с вами поговорим про итераторы. На самом деле, с итераторами вы уже работали раньше, когда, например, использовали функцию `range` и цикл `for`. Цикл `for` позволяет вам бежать по какому-то итератору и, например, выводить все числа, как в случае с функцией `range`." ] }, { "cell_type": "code", "execution_count": 1, "id": "21e1622c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "0\n", "1\n", "0\n" ] } ], "source": [ "for number in range(5):\n", " print(number & 1)" ] }, { "cell_type": "markdown", "id": "8fafa013", "metadata": {}, "source": [ "Также простейшим итератором является строка или коллекция." ] }, { "cell_type": "code", "execution_count": 2, "id": "5d32d58a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "112\n", "121\n", "116\n", "104\n", "111\n", "110\n" ] } ], "source": [ "for letter in 'python':\n", " print(ord(letter))" ] }, { "cell_type": "markdown", "id": "89519d9e", "metadata": {}, "source": [ "Итератор — это какой-то объект, по которому вы можете бежать, то есть итерироваться. Можно создать свой простейший итератор при помощи встроенной функции `iter` и, например, передать ей список." ] }, { "cell_type": "code", "execution_count": 4, "id": "d397be0d", "metadata": {}, "outputs": [], "source": [ "iterator = iter([1, 2, 3])" ] }, { "cell_type": "code", "execution_count": 5, "id": "67f95b4d", "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] } ], "source": [ "print(next(iterator))" ] }, { "cell_type": "code", "execution_count": 6, "id": "c59e91cc", "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "print(next(iterator))" ] }, { "cell_type": "code", "execution_count": 7, "id": "e86be523", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3\n" ] } ], "source": [ "print(next(iterator))" ] }, { "cell_type": "code", "execution_count": 8, "id": "f16a382b", "metadata": {}, "outputs": [ { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0miterator\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mStopIteration\u001b[0m: " ] } ], "source": [ "print(next(iterator))" ] }, { "cell_type": "markdown", "id": "44a59bd2", "metadata": {}, "source": [ "Внутри протокол итерации работает очень просто. У нас каждый раз, когда мы хотим получить следующий элемент, вызывается функция `next`, и она возвращает следующий элемент. В данном случае это 1, 2 и 3. Когда у нас элементы исчерпаны, то есть итератор закончился, у нас выбрасывается исключение `StopIteration`, которое говорит о том, что, например, нужно выйти из цикла `for`." ] }, { "cell_type": "markdown", "id": "c76cb6b5", "metadata": {}, "source": [ "В Python'е вы, конечно, можете реализовать свой собственный итератор, написав класс с соответствующими магическими методами. Эти магические методы — это методы `__iter__` и `__next__`. Метод `__iter__` должен возвращать итератор в себя, а метод `__next__` определяет то, какие элементы возвращаются из итератора при, собственно, итерации.\n", "\n", "Давайте напишем свой класс `SquareIterator`, который будет каким-то аналогом функции `range`, только будет возвращать квадраты чисел. То есть `SquareIterator` принимает границы, внутри которых мы будем итерироваться, и возвращает квадраты чисел внутри этих лимитов." ] }, { "cell_type": "code", "execution_count": 9, "id": "8edb11e1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "4\n", "9\n" ] } ], "source": [ "class SquareIterator:\n", " def __init__(self, start, end):\n", " self.current = start\n", " self.end = end\n", " \n", " def __iter__(self):\n", " return self\n", " \n", " def __next__(self):\n", " if self.current >= self.end:\n", " raise StopIteration\n", " \n", " result = self.current ** 2\n", " self.current += 1\n", " return result\n", "\n", "for num in SquareIterator(1, 4):\n", " print(num)" ] }, { "cell_type": "markdown", "id": "3b25cc95", "metadata": {}, "source": [ "В функции `__init__` мы сохраняем наши пределы, и в функции `__next__` мы будем и в функции говорить о том, что происходит при вызове следующего элемента Если у нас элементы исчерпаны, то есть у нас `current` превысил `end`, мы выбрасываем исключение `StopIteration`, которое говорит протоколу итерации о том, что итерация должна закончиться. Ну а в любом другом случае мы просто возводим число в квадрат и инкрементируем счётчик." ] }, { "cell_type": "markdown", "id": "7a915cf7", "metadata": {}, "source": [ "Таким образом мы можем использовать наш класс для того, чтобы итерироваться по нему и использовать знакомый вам цикл `for` и выводить числа, например, квадраты от 1 до 4, не включая 4." ] }, { "cell_type": "markdown", "id": "0f31137a", "metadata": {}, "source": [ "Python позволяет вам создавать собственные итераторы, и иногда это бывает полезно, когда вам нужно поддержать протокол итерации в каком-то своём классе. Что интересно, можно также определить свой собственный итератор, не определяя `__iter__` и `__next__`. Это можно сделать, написав у класса метод `__getitem__`, который определяет работу класса при обращении к его объектам с помощью квадратных скобочек, то есть как к контейнеру. Мы можем создать свой собственный контейнер `IndexIterable`, который определит `__getitem__`, и вы уже в этом случае можете по нему итерироваться." ] }, { "cell_type": "code", "execution_count": 11, "id": "9e0b2779", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "s\n", "t\n", "r\n" ] } ], "source": [ "class IndexIterable:\n", " def __init__(self, obj):\n", " self.obj = obj\n", " \n", " def __getitem__(self, index):\n", " return self.obj[index]\n", "\n", "for letter in IndexIterable(\"str\"):\n", " print(letter)" ] }, { "cell_type": "markdown", "id": "a7128fd1", "metadata": {}, "source": [ "Это делается довольно редко. Чаще всего для того чтобы определить свой итератор, используются именно методы `__iter__` и `__next__`. На этом всё." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.8" } }, "nbformat": 4, "nbformat_minor": 5 }