You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
14 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"cells": [
{
"cell_type": "markdown",
"id": "f5f73fc7",
"metadata": {},
"source": [
"# Итераторы и генераторы, в чём разница? #"
]
},
{
"cell_type": "markdown",
"id": "829f614a",
"metadata": {},
"source": [
"На этой лекции мы рассмотрим то, как устроены итераторы и генераторы. А самое главное, мы выясним их сходства и различия. Нам потребуются знания о том, как устроены генераторы для того, чтобы в дальнейшем понимать, как работают сопрограммы и как устроен фрэймворк `asyncio`.\n",
"\n",
"Итак, давайте рассмотрим пример с итератором."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "1c2c0ee2",
"metadata": {},
"outputs": [],
"source": [
"# Итераторы\n",
"class MyRangeIterator:\n",
" def __init__(self, top):\n",
" self.top = top\n",
" self.current = 0\n",
" \n",
" def __iter__(self):\n",
" return self\n",
" \n",
" def __next__(self):\n",
" if self.current >= self.top:\n",
" raise StopIteration\n",
" \n",
" current = self.current\n",
" self.current += 1\n",
" \n",
" return current"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "419086db",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<__main__.MyRangeIterator at 0x7fa903e11a90>"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"counter = MyRangeIterator(3)\n",
"counter"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "9659b408",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"2\n"
]
}
],
"source": [
"for it in counter:\n",
" print(it)"
]
},
{
"cell_type": "markdown",
"id": "15295206",
"metadata": {},
"source": [
"Итераторы могут применяться для генерации каких-либо последовательностей. Давайте рассмотрим пример, который приведён на слайде. Здесь мы генерируем последовательность 0, 1, 2. Для того, чтобы решить такую задачу при помощи итераторов, нам необходимо создать класс, в данном случае `MyRangeIterator`, а также переопределить у него `__init__` и методы `__iter__` и `__next__`.\n",
"\n",
"Итак, если мы создадим объект итератора и передадим его в цикл `for`, то в цикле `for` будет последовательно, то вызовется метод `__iter__`, а затем последовательно будет вызываться метод `__next__`.\n",
"\n",
"Давайте запустим консоль, отладчик и посмотрим, как этот код будет выполняться. Так, нам потребуется наш пример. Это наш итератор. Давайте запустим отладчик. Используем команду python3, указываем флаг `m pdb` и запускаем наш код."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "3fc740e1",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"> /home/mikhaylovaf/projects/python/5. Многопоточное и асинхронное программирование/3. Асинхронное программирование/ex2.py(2)<module>()\n",
"-> class MyRangeIterator:\n",
"(Pdb) \n",
"--KeyboardInterrupt--\n",
"(Pdb) "
]
}
],
"source": [
"! python -m pdb ex2.py"
]
},
{
"cell_type": "markdown",
"id": "cccf97a8",
"metadata": {},
"source": [
"Итак, объявили наш класс. Далее создаем объект класса `MyRangeIterator`, передаем туда значение \"3\" для генерации последовательности и видим, что вызвался наш метод `__init__`. Мы сохранили значение \"3\" в неком внутреннем свойстве класса `top` и запомнили `current`. Давайте вспомним эти строчки. Мы вернулись из нашего метода `__init__` и находимся в начале цикла. Можем посмотреть, что `counter` действительно является объектом `MyRangeIterator`. Итак, инициируем цикл. Цикл вызывает метод `__iter__`, в методе `__iter__` мы должны вернуть себя же. То есть, мы возвращаем `self`, затем в цикле последовательно, как я уже говорил, будет вызываться метод `__next__`. Проверяем условия. Условия не срабатывают. Хочу обратить внимание, что здесь мы создали некую локальную переменную `current` и ее потом будем возвращать. А также, мы сохранили некое значение во внутреннем свойстве нашего объекта. Это очень важно. Мы к этому еще вернемся. Так, сохранили значение. Вернули. Напечатали текущее значение нашей переменной. Получили ноль. То же самое будет с другими итерациями цикла, пока мы не дойдем до завершения работы нашего итератора. Итак, выполнилось условие для выхода. Сгенерировалось исключение `StopIteration`. И наш итератор прекратил работу.\n",
"\n",
"Итак, запомним ключевые моменты работы итератора. В итераторе вызывается метод `__iter__` один раз, и на каждой итерации вызывается метод `__next__`. Для завершения мы используем генерацию исключения `StopIteration`, и самое главное на каждой итерации цикла, мы сохраняем некоторое значение, которое нам потребуется для генерации следующей последовательности в объекте итераторе."
]
},
{
"cell_type": "markdown",
"id": "d0c328f2",
"metadata": {},
"source": [
"Давайте рассмотрим, как можно решить такую же задачу при помощи генераторов. Как вы уже знаете, для того, чтобы объявить генератор, необходимо написать обычную функцию, назовем ее `my_range_generator`. Точно так же, как мы назвали класс."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "21720586",
"metadata": {},
"outputs": [],
"source": [
"# Генераторы\n",
"def my_range_generator(top):\n",
" current = 0\n",
" while current < top:\n",
" yield current\n",
" current += 1"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "5d8d7600",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<generator object my_range_generator at 0x7fa903f298e0>"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"counter = my_range_generator(3)\n",
"counter"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "5075abde",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"2\n"
]
}
],
"source": [
"for it in counter:\n",
" print(it)"
]
},
{
"cell_type": "markdown",
"id": "1269323e",
"metadata": {},
"source": [
"И для того, чтобы эта функция стала генератором, необходимо в ней объявить ключевое слово `yield`. Всё, после того, как в функции есть `yield`, она становится генератором. Если мы вызовем эту функцию, то на самом деле вызова не произойдет, а создастся объект. И для того, чтобы вызвать код этой функции необходимо по этому объекту проитерироваться. Давайте точно так же запустим отладчик и посмотрим как этот код будет выполняться в отладчике."
]
},
{
"cell_type": "markdown",
"id": "186b48e8",
"metadata": {},
"source": [
"Завершим предыдущий пример. И запустим пример с генератором. Точно также используем флаг `-m pdb`. Наш пример."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "de9cb81d",
"metadata": {},
"outputs": [],
"source": [
"! python -m pdb ex3.py"
]
},
{
"cell_type": "markdown",
"id": "9da18c98",
"metadata": {},
"source": [
"Итак мы объявили функцию. Обратите внимание, что сейчас выполнилась строчка и не произошло вызова функции. Так как функция является генератором, то вызов такой создаёт просто объект типа генератор.\n",
"\n",
"Если мы его попробуем напечатать, то действительно, это объект - генератор. После этого наш цикл `for` будет вызывать метод `next` для нашего генератора, и он начнёт свое исполнение. Давайте продолжим выполнение нашего генератора. Как мы видим, выполнилась функция, мы в нее зашли, зашли в наш цикл и дошли до первой инструкции `yield`. После того, как мы дошли до инструкции `yield`, наша функция вернула значение в наш цикл. И теперь уже мы в цикле сможем напечатать то значение, которое вернул нам наш генератор.\n",
"\n",
"Давайте немного остановимся на том, как произошел возврат из нашей функции. На самом деле, генератор как бы заморозил свою функцию и сохранил значения своего фрейма стека в объекте `counter`, или в объекте-генераторе. После того, как наша функция продолжит свое выполнение, стек восстановится, восстановится значение всех локальных переменных. В данном случае у нас есть переменные `current`, у нас есть локальные переменные `top`, значения их восстановятся, и цикл продолжится с того места, где у нас был вызов `yield`. Давайте проверим это.\n",
"\n",
"Итак мы напечатали значение ноль. Перешли снова в нашу функцию, восстановился стек, и функция продолжила выполнение с того же места, где она была как бы заморожена. Дальше происходит всё то же самое. До тех пор, пока наша функция не завершится. Давайте посмотрим. Все, наша функция завершилась. Итак, как мы видим, ключевое отличие здесь по сравнению с итераторами то, что нам не надо объявлять никакой класс. Мы объявили буквально функцию, также второе отличие - нам не нужно сохранять никаких значений, или глобальных состояний в объектах типа генератор, или типа итератор. Мы используем обычные локальные переменные, и это очень удобно.\n",
"\n",
"Подводя итоги, можно сказать, что в генераторы заложены очень большие возможности для написания `concurrency` кода. И в следующих лекциях мы рассмотрим, как работают корутины и разберемся в деталях их работы."
]
}
],
"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
}