Compare commits

...

14 Commits
main ... edits

Author SHA1 Message Date
Александр Пильщиков c5bec63bad mac 2 years ago
Alexandr Pilshchikov 82a1cce831 исправить опечатку (не уверен) 2 years ago
Alexandr Pilshchikov 2e43ac90ad исправить опечатку (пропущен предлог) 2 years ago
Alexandr Pilshchikov dbc40571a3 исправить опечатку 2 years ago
Alexandr Pilshchikov 91ca48062f исправить опечатку 2 years ago
Alexandr Pilshchikov 7031c135fe исправить опечатки 2 years ago
Alexandr Pilshchikov 54a67660c8 исправить опечатку (import два раза в комаде) 2 years ago
Alexandr Pilshchikov 6c438c65b7 исправить опечатку 2 years ago
Alexandr Pilshchikov 4af38b0789 исправить опечатку (поменял слова местами) 2 years ago
Alexandr Pilshchikov 46b1b6bb8d исправить опечатку 2 years ago
Alexandr Pilshchikov b810c44a97 поправить ссылки в Readme (у меня не работали, так и не понял почему, заново прописал пути 2 years ago
Alexandr Pilshchikov 38363295a4 добавить в конце каждого урока переход к следующему в 3. Базовые типы и конструкции 2 years ago
Alexandr Pilshchikov 03e55b07d9 добавить ссылку на следующий урок 2 years ago
Alexandr Pilshchikov cb84369324 изменить названия переменных (во всех примерах используются x и y, а в описании a и b 2 years ago

@ -514,7 +514,9 @@
"- REPL — Википедия (https://ru.wikipedia.org/wiki/REPL)\n", "- REPL — Википедия (https://ru.wikipedia.org/wiki/REPL)\n",
"- Как запустить скрипт на Python (https://mkdev.me/posts/kak-zapustit-skript-na-python)\n", "- Как запустить скрипт на Python (https://mkdev.me/posts/kak-zapustit-skript-na-python)\n",
"- Комментирование Python кода (https://dev-gang.ru/article/kommentirovanie-python-koda-auf6lgv2vv/)\n", "- Комментирование Python кода (https://dev-gang.ru/article/kommentirovanie-python-koda-auf6lgv2vv/)\n",
"- Правильный выбор имен переменных в Python (http://pythonlearn.ru/python-dlya-nachinayushhix/imena-dlya-peremennyx-v-python/)" "- Правильный выбор имен переменных в Python (http://pythonlearn.ru/python-dlya-nachinayushhix/imena-dlya-peremennyx-v-python/)\n",
"\n",
"[Далее...](Полезные%20ссылки.ipynb)"
] ]
} }
], ],

@ -126,7 +126,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Мы определяем переменную со значением `google.com` строковый, и дальше у нас в данном случае срабатывает блок `elif`, который выполняется тогда, когда подстрока `google` была найдена в нашей строке.\n", "Мы определяем переменную со значением `google.com` как строковый, и дальше у нас в данном случае срабатывает блок `elif`, который выполняется тогда, когда подстрока `google` была найдена в нашей строке.\n",
"\n", "\n",
"Также во многих языках программирования вы могли встречать тернарный оператор. В Python'е есть его аналог, и он записывается вот таким вот образом, который вы видите на примере." "Также во многих языках программирования вы могли встречать тернарный оператор. В Python'е есть его аналог, и он записывается вот таким вот образом, который вы видите на примере."
] ]
@ -623,7 +623,9 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Теперь, когда мы рассмотрели все основные конструкции управления потоком, мы можем попробовать применить их на практике и написать небольшую программу, которая будет содержать их в себе. Давайте это сделаем." "Теперь, когда мы рассмотрели все основные конструкции управления потоком, мы можем попробовать применить их на практике и написать небольшую программу, которая будет содержать их в себе. Давайте это сделаем.\n",
"\n",
"[Далее...](Пример%20на%20управление%20потоком.ipynb)"
] ]
} }
], ],
@ -643,7 +645,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.9.12" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -505,7 +505,9 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"На этой лекции мы поговорили про логический тип в Python, тип `bool`, рассмотрели логические операторы, а также посмотрели на составные логические выражения." "На этой лекции мы поговорили про логический тип в Python, тип `bool`, рассмотрели логические операторы, а также посмотрели на составные логические выражения.\n",
"\n",
"[Далее...](Строки%20и%20байтовые%20строки.ipynb)"
] ]
} }
], ],

@ -0,0 +1,535 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Базовые типы: логический тип #"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"На этой лекции мы поговорим о ещё одном базовом типе, который есть в Python, а именно о логическом типе, он же тип `bool`.\n",
"\n",
"В Python'е тип `bool` представлен двумя значениями, как и в других языках — это `True` и это `False`."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"True"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"False"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Что важно отметить, что в Python тип `bool` является подтипом целого числа, и, по большому счету `True` — это `True` соответствует единице, а `False` соответствует нулю. Если мы присвоим переменной `result` `True`, то, посмотрев на тип переменной, мы увидим, что это как раз тип `bool`."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'bool'>\n"
]
}
],
"source": [
"result = True\n",
"print(type(result))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Когда нужны логические типы? По большому счету они нужны тогда, когда нужно проверить на истинность какое-то выражение — является выражение истинным либо является выражение ложью.\n",
"\n",
"Например, в Python'е мы можем проверить, что значения двух объектов равны. Это делается с помощью оператора «равно». Это два знака равно, идущие друг за другом."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"13 == 13"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Также есть оператор «не равно», который позволяет сделать противоположное действие, то есть сравнить то, что значения не равны. Обратите внимание, что результатом выполнения обоих выражений являются логические типы — либо `True`, либо `False`."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"1 != 2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Операторы сравнения ##\n",
" - `>` больше;\n",
" - `>=` больше или равно;\n",
" - `<` меньше;\n",
" - `<=` меньше или равно."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"False\n",
"True\n",
"True\n",
"False\n"
]
}
],
"source": [
"print(3 > 3)\n",
"print(3 >= 3)\n",
"print(6 < 7)\n",
"print(6 <= 5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"И также интересной особенностью является наличие в Python'е множественного сравнения."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"x = 2\n",
"print(1 < x < 3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Мы объявляем переменную `x`, которая равна целому числу 2. Дальше записываем множественное сравнение, которое в итоге выдает результат `True`, потому что `x > 1`, и в то же время `x < 3`. Это очень удобно бывает на практике."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Конвертация типов ##\n",
"\n",
"Если мы попробуем преобразовать целое число к типу `bool`, то есть к логическому типу, то если число не равняется нулю, то получим в результате `True`, если 0, то `False`. То же самое верно для вещественных чисел."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bool(12)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bool(0)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bool(-4)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bool(.5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Поговорим о логических выражениях.\n",
"\n",
"Логические выражения — это выражения, которые содержат в себе один или больше логических операторов. Какие мы знаем логические операторы? Это может быть логическое «и», логическое «или» или, например, логическое отрицание.\n",
"\n",
"В Python'е представлены все эти операторы и, например, логическое «и» записывается с помощью слова `and`.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"False\n"
]
}
],
"source": [
"x, y = True, False\n",
"print(x and y)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Также есть логическое «или» — это оператор `or`. И есть логическое отрицание — это оператор `not`."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"x, y = True, False\n",
"print(x or y)"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"y = False\n",
"print(not y)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Мы можем составлять сложные логические выражения, в которые будут входить несколько логических операторов. При этом в этом логическом выражении у операторов есть свой приоритет, порядок исполнения, как и в математических операциях. Стоит отметить, что этот порядок мы также можем задавать, используя круглые скобки, как и в случае с математическим выражением."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"x, y, z = True, False, True\n",
"result = x and y or z\n",
"print(result)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Следующая особенность Python — это то, что логические выражения ленивые. Давайте подробно разберем это на примере. У нас есть переменная `x`, которая равняется 12 и переменная `y`, которая равняется `False`. И есть логическое выражение `x or y`. В результате работы этого выражения мы видим, что на экране не `True`, как мы могли бы ожидать, а число 12."
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"12\n"
]
}
],
"source": [
"x = 12\n",
"y = False\n",
"\n",
"print(x or y)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Что здесь происходит? Python начинает интерпретировать логическое выражение, видит, что `x` является истинным, и понимает, что ему не нужно выполнять оставшуюся часть логического выражения. Поэтому он y уже не будет проверять, `x` — это истина, оператор `or` стоит, значит нас устраивает то, что мы можем остановиться в этот момент и результатом выполнения выражения будет как раз значение `x`. На другом примере у нас есть переменная `x`, которая равна 12, и переменная `z`, которая равна строке `boom`. В результате работы логического выражения `x and z` мы получаем как результат работы логического выражения строку `boom`."
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"boom\n"
]
}
],
"source": [
"x = 12\n",
"z = \"boom\"\n",
"\n",
"print(x and z)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Происходит все то же самое. Python выполняет логическое выражение, до тех пор, пока оно имеет смысл, и результатом является последнее значение.\n",
"\n",
"Посмотрим небольшой пример — задачу, определить високосный год или нет. Существует такое правило, которое позволяет понять, является ли год високосным или нет. Год является високосным, если он кратен 4, но при этом не кратен 100, либо кратен 400. Звучит достаточно запутанно, однако решение всего в три строчки и как раз с помощью логических выражений. Мы объявляем переменную, которая содержит в себе год и далее составляем сложное логическое выражение, включающее в себя несколько логических операторов. При этом задаем порядок выполнения операторов скобками, обратите внимание. И что стоит отметить — то, что это то, о чем я говорил. Если год не кратен четырем, то есть первое выражение не является истинным, то логическое выражение не будет выполняться дальше, Python в этот момент остановится. Если же оно истинное, то выполнение логического выражения продолжится. Ну и мы получаем ответ. В данном случае 2017 год не является високосным."
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"False\n"
]
}
],
"source": [
"year = 2017\n",
"\n",
"is_leap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)\n",
"\n",
"print(is_leap)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Что стоит отметить — это то, что эту задачку мы могли бы решить ещё короче, всего в две строчки, используя модуль стандартной библиотеки `calendar`. Как принято говорить, Python — это «батарейки включены», то есть это язык, который предоставляет очень много возможностей в своей стандартной библиотеке. Так что библиотеку языка ставят в пример многим другим языкам программирования. Соответственно, есть модуль «календарь», который мы можем импортировать, и внутри модуля календарь есть функция `isleap`, которая делает как раз то, что нам нужно. Мы, например, можем убедиться, что 1980-й год был високосным. Про импорты мы ещё с вами будем говорить в дальнейшем."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"from calendar import isleap\n",
"\n",
"print(isleap(1980))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"На этой лекции мы поговорили про логический тип в Python, тип `bool`, рассмотрели логические операторы, а также посмотрели на составные логические выражения.\n",
"\n",
"[Далее...](Строки%20и%20байтовые%20строки.ipynb)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

@ -150,7 +150,9 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Что еще можно сделать с `None`? `None` будет часто использоваться как значение именованного аргумента в функции, для того чтобы подчеркнуть, что аргумент функции является опциональным. Также значение `None` присваивается при инициализации классом всевозможным атрибутам, которые в дальнейшем могут быть перезаписаны каким-то полезным значением. Также `None` по умолчанию возвращается из функции, если вы самостоятельно явным образом ничего из функции не вернули с помощью конструкции `return`. Все эти слова про функции и про классы могут звучать для вас по-новому, но мы про все это расскажем в следующих лекциях нашего курса. Ну а пока вы познакомились с объектом `None`, который теперь вас не испугает, когда вы его увидите." "Что еще можно сделать с `None`? `None` будет часто использоваться как значение именованного аргумента в функции, для того чтобы подчеркнуть, что аргумент функции является опциональным. Также значение `None` присваивается при инициализации классом всевозможным атрибутам, которые в дальнейшем могут быть перезаписаны каким-то полезным значением. Также `None` по умолчанию возвращается из функции, если вы самостоятельно явным образом ничего из функции не вернули с помощью конструкции `return`. Все эти слова про функции и про классы могут звучать для вас по-новому, но мы про все это расскажем в следующих лекциях нашего курса. Ну а пока вы познакомились с объектом `None`, который теперь вас не испугает, когда вы его увидите.\n",
"\n",
"[Далее...](Конструкции%20управления%20потоком.ipynb)"
] ]
} }
], ],

@ -343,7 +343,9 @@
"source": [ "source": [
"Чтобы запустить код, мы нажимаем правой кнопкой на белой области редактора и выбираем пункт меню `Run`. `PyCharm` запускает встроенный терминал, и, в принципе, мы можем пользоваться нашей программой как раз из этого терминала. Однако здесь написано, как `PyCharm` запускает программу, которую мы только что написали. Давайте запустим её самостоятельно из терминала. Введите число. Давайте попробуем для начала ввести что-то неправильное. Введите правильное число. А теперь попробуем отгадать число.\n", "Чтобы запустить код, мы нажимаем правой кнопкой на белой области редактора и выбираем пункт меню `Run`. `PyCharm` запускает встроенный терминал, и, в принципе, мы можем пользоваться нашей программой как раз из этого терминала. Однако здесь написано, как `PyCharm` запускает программу, которую мы только что написали. Давайте запустим её самостоятельно из терминала. Введите число. Давайте попробуем для начала ввести что-то неправильное. Введите правильное число. А теперь попробуем отгадать число.\n",
"\n", "\n",
"Мы написали программу, игру, как пример на конструкции управления потоком." "Мы написали программу, игру, как пример на конструкции управления потоком.\n",
"\n",
"[Далее...](Тест%20на%20типы%20и%20конструкции.ipynb)"
] ]
} }
], ],
@ -363,7 +365,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.9.12" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -1073,7 +1073,9 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"На этой лекции мы научились работать со строками, посмотрели на основные операции, которые есть у нас для работы со строками, а также посмотрели на байтовые строки, на тип bytes. Вам на практике часто придется с ними сталкиваться." "На этой лекции мы научились работать со строками, посмотрели на основные операции, которые есть у нас для работы со строками, а также посмотрели на байтовые строки, на тип bytes. Вам на практике часто придется с ними сталкиваться.\n",
"\n",
"[Далее...](Объект%20None.ipynb)"
] ]
} }
], ],

@ -240,7 +240,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@ -254,7 +254,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.8" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -737,7 +737,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Что еще можно сделать было бы? Мы могли бы записать вместо `x, y = 0, 0`, `x = y = 0`, тем самым мы бы связали переменную `x` и переменную `y` с объектом, с зачисленным объектом 0. Однако нужно быть внимательным. первый объект это изменяемый объект, а второй - это неизменяемые, изменяемые объекты это объекты, которые после создания могут менять свое значение, а неизменяемые, соответственно, это объекты, которые после создания свое значение не меняют. Примитивные основные типы, которые мы разбираем на этой неделе, по большому счету, не изменяемы, поэтому для нашей задачи запись `x = y = 0` была бы оправдана. В данном случае, если бы мы поменяли только `х`, например, добавили бы к `х` впоследствии единичку, у нас `х` стал бы равен 1, а `у` остался бы нулем." "Что еще можно сделать было бы? Мы могли бы записать вместо `a, b = 0, 0`, `a = b = 0`, тем самым мы бы связали переменную `a` и переменную `b` с объектом, с зачисленным объектом 0. Однако нужно быть внимательным. первый объект это изменяемый объект, а второй - это неизменяемые, изменяемые объекты это объекты, которые после создания могут менять свое значение, а неизменяемые, соответственно, это объекты, которые после создания свое значение не меняют. Примитивные основные типы, которые мы разбираем на этой неделе, по большому счету, не изменяемы, поэтому для нашей задачи запись `a = b = 0` была бы оправдана. В данном случае, если бы мы поменяли только `a`, например, добавили бы к `a` впоследствии единичку, у нас `a` стал бы равен 1, а `b` остался бы нулем."
] ]
}, },
{ {
@ -764,7 +764,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Но если бы мы написали `х = у = пустой список`, а пустой список - это объект-контейнер, который мы будем разбирать на дальнейших неделях, он позволяет хранить последовательность объектов в себе, а этот объект уже изменяемый, поэтому, когда мы присваиваем `х = у = пустой список`, а затем в список `х` добавляем два элемента, то, если мы выведем на экран `х` и `у`, то мы увидим, что изменились значения обеих переменных. С этим нужно быть внимательным, это нюанс, который необходимо понимать и необходимо понимать, что в Python-е есть неизменяемые объекты и изменяемые объекты." "Но если бы мы написали `a = b = пустой список`, а пустой список - это объект-контейнер, который мы будем разбирать на дальнейших неделях, он позволяет хранить последовательность объектов в себе, а этот объект уже изменяемый, поэтому, когда мы присваиваем `a = b = пустой список`, а затем в список `a` добавляем два элемента, то, если мы выведем на экран `a` и `b`, то мы увидим, что изменились значения обеих переменных. С этим нужно быть внимательным, это нюанс, который необходимо понимать и необходимо понимать, что в Python-е есть неизменяемые объекты и изменяемые объекты."
] ]
}, },
{ {
@ -793,7 +793,9 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"На этой лекции мы с вами поговорили об основных численных типах, которые есть в языке, рассмотрели математические операции с ними, узнали о конвертации типов, а также затронули тему изменяемых и не изменяемых объектов в Python. В дальнейших лекциях, мы продолжим знакомиться с основными типами в языке." "На этой лекции мы с вами поговорили об основных численных типах, которые есть в языке, рассмотрели математические операции с ними, узнали о конвертации типов, а также затронули тему изменяемых и не изменяемых объектов в Python. В дальнейших лекциях, мы продолжим знакомиться с основными типами в языке.\n",
"\n",
"[Далее...](Логический%20тип.ipynb)"
] ]
} }
], ],

@ -0,0 +1,219 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "08735fdb",
"metadata": {},
"source": [
"# Байткод #"
]
},
{
"cell_type": "markdown",
"id": "764f3e24",
"metadata": {},
"source": [
"А сейчас давайте поговорим про байт-код.\n",
"\n",
"Помните, когда мы рассматривали организацию кода на Python, мы импортировали функции из модулей и пакетов, и Python автоматически создавал файлики с расширением.pyc. Я сказал, что это файлики, в которых содержится байт-код, то есть скомпилированное представление вашего кода в форме, удобной Python для интерпретирования.\n",
"\n",
"Давайте сейчас подробнее посмотрим, что это значит. Для этого мы создадим модуль, назовем его `mymodule`.\n",
"\n",
"Мы с вами это уже умеем делать, и внутри определим функцию `multiply`, точно такую же, которую мы с вами уже писали. Она принимает два аргумента и возвращает их произведение.\n",
"\n",
"```python\n",
"def multiply(a, b):\n",
" return a * b\n",
"```\n",
"\n",
"Функция определена, теперь мы можем ее импортировать. Давайте воспользуемся конструкцией `from`. `from mymodule import multiply`. `multiply` — это функция."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "03f059a0",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"importing mypackage\n"
]
}
],
"source": [
"from mymodule import multiply"
]
},
{
"cell_type": "markdown",
"id": "0c3e8648",
"metadata": {},
"source": [
"Как я уже сказал, практически всё в Python — это объект, не исключение и функция. Если мы посмотрим с помощью функции `dir` методы, которые содержит функция, мы увидим, что их много, в том числе у функции есть атрибут `__code__` с двумя подчеркиваниями, это `code object`."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "1374e3bc",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']\n"
]
}
],
"source": [
"print(dir(multiply))"
]
},
{
"cell_type": "markdown",
"id": "43989f9d",
"metadata": {},
"source": [
"По сути, это объект, который оборачивает тот код, который вы пишете. У этого объекта есть метод `co_code`, который содержит байт-код. Это байт-код, который Python сгенерировал для этой функции."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "a3373ab6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"b'|\\x00|\\x01\\x14\\x00S\\x00'\n"
]
}
],
"source": [
"print(multiply.__code__.co_code)"
]
},
{
"cell_type": "markdown",
"id": "eb706b6d",
"metadata": {},
"source": [
"Что это значит? Это значит, что наша функция была преобразована в байт-код, который представляет собой набор инструкций. В данном случае это байтовая строка, которая содержит инструкцию одну за другой, которые подаются на вход интерпретатору Python.\n",
"\n",
"Интерпретатор Python, по большому счету, это большая конструкция `switch` кода на C, которая, в зависимости от входящей инструкции байт-кода, предпринимает то или иное действие."
]
},
{
"cell_type": "markdown",
"id": "33872151",
"metadata": {},
"source": [
"Как мы видим, байт-код нечеловекочитаемый Мы можем привести его в человекочитаемый вид, используя модуль `dis` стандартной библиотеки Python.\n",
"\n",
"Давайте это сделаем. Заимпортируем модуль `dis` и с помощью метода `dis` модуля `dis` дисассемблируем нашу функцию в человекочитаемый вид. Что мы видим?"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e2ccab2f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 0 LOAD_FAST 0 (0)\n",
" 2 LOAD_FAST 1 (1)\n",
" 4 BINARY_MULTIPLY\n",
" 6 RETURN_VALUE\n"
]
}
],
"source": [
"from dis import dis\n",
"\n",
"dis(multiply.__code__.co_code)"
]
},
{
"cell_type": "markdown",
"id": "42c24b47",
"metadata": {},
"source": [
"Мы видим, что наша функция на самом деле преобразовывается в байт-код, который состоит из четырех инструкций. Каждая из этих инструкций делает какое-то действие. Давайте посмотрим на каждую инструкцию подробно.\n",
"\n",
"Первая — это `LOAD_FAST`, это инструкция, которая загружает содержимое переменной `a` в память и помещает его на стек. Стек — это структура данных, последним пришёл - первым вышел (LIFO), которая определена на уровне C и используется Python для того, чтобы манипулировать ходом выполнения программы.\n",
"\n",
"Как только переменная `a` помещена на стек, мы переходим ко второй инструкции, которая также `LOAD_FAST`, она делает то же самое со значением переменной `b`, помещает значение переменной `b` на стек. Далее.\n",
"\n",
"Следующая инструкция — это `BINARY_MULTIPLY`. `BINARY_MULTIPLY` говорит интерпретатору Python, что нужно взять два самых верхних значения на стеке и произвести их умножение. Заметьте, что мы могли передать в функцию значение любого типа. Могли передать целые числа, могли вещественные. Могли бы передать даже строки, например. Но в результате преобразования нашей функции в байт-код байт-код получился тем же самым. Типизация начинает по большому счету работать тогда, когда Python выполняет ту или иную инструкцию.\n",
"\n",
"В данном случае, когда выполняется инструкция `BINARY_MULTIPLY`, Python посмотрит на тип объектов, и, например, если это два числа, произведет умножение двух чисел. Результат умножения он поместит на вершину стека.\n",
"\n",
"И последняя инструкция — это `RETURN_VALUE`. `RETURN_VALUE` берет вершину стека, это результат умножения наших значений `a` и `b`, и возвращает его. По большому счету, это то, как работает Python, как устроен байт-код, и любой код на Python, который вы будете писать, будет превращаться, компилироваться в байт-код. Этот байт-код будет выглядеть посложнее, чем мы с вами видели, потому что функции, которые вы будете писать, могут быть сложнее, содержать всевозможные циклы, условия и так далее. И будут использоваться соответствующие инструкции.\n",
"\n",
"Все эти инструкции задокументированы, их можно посмотреть как в исходном коде Python, так и в документации. Ну, и можно посмотреть на них сейчас, есть модуль в стандартной библиотеке `opcode`, который мы можем заимпортировать и напечатать, например, на экран `opmap`."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "b4d07e58",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'POP_TOP': 1, 'ROT_TWO': 2, 'ROT_THREE': 3, 'DUP_TOP': 4, 'DUP_TOP_TWO': 5, 'ROT_FOUR': 6, 'NOP': 9, 'UNARY_POSITIVE': 10, 'UNARY_NEGATIVE': 11, 'UNARY_NOT': 12, 'UNARY_INVERT': 15, 'BINARY_MATRIX_MULTIPLY': 16, 'INPLACE_MATRIX_MULTIPLY': 17, 'BINARY_POWER': 19, 'BINARY_MULTIPLY': 20, 'BINARY_MODULO': 22, 'BINARY_ADD': 23, 'BINARY_SUBTRACT': 24, 'BINARY_SUBSCR': 25, 'BINARY_FLOOR_DIVIDE': 26, 'BINARY_TRUE_DIVIDE': 27, 'INPLACE_FLOOR_DIVIDE': 28, 'INPLACE_TRUE_DIVIDE': 29, 'GET_AITER': 50, 'GET_ANEXT': 51, 'BEFORE_ASYNC_WITH': 52, 'BEGIN_FINALLY': 53, 'END_ASYNC_FOR': 54, 'INPLACE_ADD': 55, 'INPLACE_SUBTRACT': 56, 'INPLACE_MULTIPLY': 57, 'INPLACE_MODULO': 59, 'STORE_SUBSCR': 60, 'DELETE_SUBSCR': 61, 'BINARY_LSHIFT': 62, 'BINARY_RSHIFT': 63, 'BINARY_AND': 64, 'BINARY_XOR': 65, 'BINARY_OR': 66, 'INPLACE_POWER': 67, 'GET_ITER': 68, 'GET_YIELD_FROM_ITER': 69, 'PRINT_EXPR': 70, 'LOAD_BUILD_CLASS': 71, 'YIELD_FROM': 72, 'GET_AWAITABLE': 73, 'INPLACE_LSHIFT': 75, 'INPLACE_RSHIFT': 76, 'INPLACE_AND': 77, 'INPLACE_XOR': 78, 'INPLACE_OR': 79, 'WITH_CLEANUP_START': 81, 'WITH_CLEANUP_FINISH': 82, 'RETURN_VALUE': 83, 'IMPORT_STAR': 84, 'SETUP_ANNOTATIONS': 85, 'YIELD_VALUE': 86, 'POP_BLOCK': 87, 'END_FINALLY': 88, 'POP_EXCEPT': 89, 'STORE_NAME': 90, 'DELETE_NAME': 91, 'UNPACK_SEQUENCE': 92, 'FOR_ITER': 93, 'UNPACK_EX': 94, 'STORE_ATTR': 95, 'DELETE_ATTR': 96, 'STORE_GLOBAL': 97, 'DELETE_GLOBAL': 98, 'LOAD_CONST': 100, 'LOAD_NAME': 101, 'BUILD_TUPLE': 102, 'BUILD_LIST': 103, 'BUILD_SET': 104, 'BUILD_MAP': 105, 'LOAD_ATTR': 106, 'COMPARE_OP': 107, 'IMPORT_NAME': 108, 'IMPORT_FROM': 109, 'JUMP_FORWARD': 110, 'JUMP_IF_FALSE_OR_POP': 111, 'JUMP_IF_TRUE_OR_POP': 112, 'JUMP_ABSOLUTE': 113, 'POP_JUMP_IF_FALSE': 114, 'POP_JUMP_IF_TRUE': 115, 'LOAD_GLOBAL': 116, 'SETUP_FINALLY': 122, 'LOAD_FAST': 124, 'STORE_FAST': 125, 'DELETE_FAST': 126, 'RAISE_VARARGS': 130, 'CALL_FUNCTION': 131, 'MAKE_FUNCTION': 132, 'BUILD_SLICE': 133, 'LOAD_CLOSURE': 135, 'LOAD_DEREF': 136, 'STORE_DEREF': 137, 'DELETE_DEREF': 138, 'CALL_FUNCTION_KW': 141, 'CALL_FUNCTION_EX': 142, 'SETUP_WITH': 143, 'LIST_APPEND': 145, 'SET_ADD': 146, 'MAP_ADD': 147, 'LOAD_CLASSDEREF': 148, 'EXTENDED_ARG': 144, 'BUILD_LIST_UNPACK': 149, 'BUILD_MAP_UNPACK': 150, 'BUILD_MAP_UNPACK_WITH_CALL': 151, 'BUILD_TUPLE_UNPACK': 152, 'BUILD_SET_UNPACK': 153, 'SETUP_ASYNC_WITH': 154, 'FORMAT_VALUE': 155, 'BUILD_CONST_KEY_MAP': 156, 'BUILD_STRING': 157, 'BUILD_TUPLE_UNPACK_WITH_CALL': 158, 'LOAD_METHOD': 160, 'CALL_METHOD': 161, 'CALL_FINALLY': 162, 'POP_FINALLY': 163}\n"
]
}
],
"source": [
"from opcode import opmap\n",
"\n",
"print(opmap)"
]
},
{
"cell_type": "markdown",
"id": "a86677b4",
"metadata": {},
"source": [
"Мы видим, что на самом деле инструкций существует очень много. И, так или иначе, они в определенный момент будут задействованы. \n",
"\n",
"Что хотелось бы отметить? Знание о том, что ваш код компилируется в байт-код, может оказаться в определенный момент очень полезным. И, как минимум, вы не будете бояться того, что Python автоматически создает скомпилированные байт-код файлики в ваших директориях.\n",
"\n",
"Мы посмотрели на то, как Python преобразовывает код в байт-код, и теперь вы знаете, как это устроено."
]
}
],
"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.8.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

@ -51,7 +51,7 @@
"id": "5c66f0b0", "id": "5c66f0b0",
"metadata": {}, "metadata": {},
"source": [ "source": [
"`pip install request` ставит request в нашу систему, мы сейчас видим что библиотека `requests` уже установлена в глобальный директорию Python, это не очень удобно, мы не хотим трогать эту установленную версию библиотеки, поэтому следующим шагом нам нужно будет создать виртуальное окружение." "`pip install request` ставит request в нашу систему, мы сейчас видим что библиотека `requests` уже установлена в глобальную директорию Python, это не очень удобно, мы не хотим трогать эту установленную версию библиотеки, поэтому следующим шагом нам нужно будет создать виртуальное окружение."
] ]
}, },
{ {
@ -312,7 +312,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@ -326,7 +326,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.8" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -729,7 +729,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Это все модули стандартной библиотеки в Python, коих просто тонны. Там есть модули на все случаи жизни. И с какой-то частью из этих модулей мы вас познакомим в рамках нашего курса в дальнейшем. Однако может так получиться, что даже этой богатой функциональности, которая есть в стандартной библиотеке Python, вам может не хватить — у вас какая-то специфичная задача. Не отчаивайтесь, Python — это язык с огромным сообществом, и существует масса библиотек, написанных этим сообществом на все случаи жизни, которые вы можете установить в свою систему. Эти библиотеки находятся на ресурсе `pypi.org`, и вы в любой момент можете зайти на этот ресурс, посмотреть, какая библиотека вам нужна, и установить ее в вашу систему. А установка пакета стороннего в систему производиться с помощью утилиты `pip`, и мы с вами на следующей лекции посмотрим, как стороннюю библиотеку в вашу систему можно поставить.\n", "Это все модули стандартной библиотеки в Python, коих просто тонны. Там есть модули на все случаи жизни. И с какой-то частью из этих модулей мы вас познакомим в рамках нашего курса в дальнейшем. Однако может так получиться, что даже этой богатой функциональности, которая есть в стандартной библиотеке Python, вам может не хватить — у вас какая-то специфичная задача. Не отчаивайтесь, Python — это язык с огромным сообществом, и существует масса библиотек, написанных этим сообществом на все случаи жизни, которые вы можете установить в свою систему. Эти библиотеки находятся на ресурсе `pypi.org`, и вы в любой момент можете зайти на этот ресурс, посмотреть, какая библиотека вам нужна, и установить ее в вашу систему. А установка стороннего пакета в систему производиться с помощью утилиты `pip`, и мы с вами на следующей лекции посмотрим, как стороннюю библиотеку в вашу систему можно поставить.\n",
"\n", "\n",
"На этой лекции мы познакомились с организацией кода на Python, посмотрели, что такое модули и что такое пакеты, и как их организовывать, как импортировать код из модулей и пакетов. Также с некоторыми особенностями, которые с модулями и пакетами связаны. Нас ждет в дальнейшем еще немало работы с модулями стандартной библиотеки. Теперь вы умеете ими пользоваться." "На этой лекции мы познакомились с организацией кода на Python, посмотрели, что такое модули и что такое пакеты, и как их организовывать, как импортировать код из модулей и пакетов. Также с некоторыми особенностями, которые с модулями и пакетами связаны. Нас ждет в дальнейшем еще немало работы с модулями стандартной библиотеки. Теперь вы умеете ими пользоваться."
] ]
@ -737,7 +737,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@ -751,7 +751,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.8" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -45,7 +45,7 @@
"\n", "\n",
"Обратите внимание, что точка - это замена команды `source`, которую я показывал ранее. Виртуальное окружение создано, и мы можем установить пакет. Мы установим пакет `requests`. Это библиотека, как я уже говорил, для работы с `http` запросами, причем очень удобная библиотека, которой пользуются практически все питонисты для работы с `http`.\n", "Обратите внимание, что точка - это замена команды `source`, которую я показывал ранее. Виртуальное окружение создано, и мы можем установить пакет. Мы установим пакет `requests`. Это библиотека, как я уже говорил, для работы с `http` запросами, причем очень удобная библиотека, которой пользуются практически все питонисты для работы с `http`.\n",
"\n", "\n",
"Все успешно установлено. Давайте начнем программировать. Назовем наш файлик `location.py`. Первое, что мы сделаем, это сделаем `import import requests`, который мы будем использовать.\n", "Все успешно установлено. Давайте начнем программировать. Назовем наш файлик `location.py`. Первое, что мы сделаем, это сделаем `import requests`, который мы будем использовать.\n",
"\n", "\n",
"Далее мы хотим чтобы наша программка работала только тогда, когда мы ее запускаем интерпретатором Python, то есть нам нужно написать конструкцию `if __name__ == \"__main__\":`. Внутри этой конструкции мы напечатаем на экран результат выполнения нашей функции, которая будет называться `get_location_info`.\n", "Далее мы хотим чтобы наша программка работала только тогда, когда мы ее запускаем интерпретатором Python, то есть нам нужно написать конструкцию `if __name__ == \"__main__\":`. Внутри этой конструкции мы напечатаем на экран результат выполнения нашей функции, которая будет называться `get_location_info`.\n",
"\n", "\n",
@ -69,7 +69,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@ -83,7 +83,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.8" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -84,8 +84,8 @@
"### Базовые типы и конструкции ###\n", "### Базовые типы и конструкции ###\n",
"\n", "\n",
"- [Базовые типы: численные типы](3.%20Базовые%20типы%20и%20конструкции/Численные%20типы.ipynb)\n", "- [Базовые типы: численные типы](3.%20Базовые%20типы%20и%20конструкции/Численные%20типы.ipynb)\n",
"- [Базовые типы: логический тип](3.%20Базовые%20типы%20и%20конструкции/Логический%20тип.ipynb)\n", "- [Базовые типы: логический тип](3.%20Базовые%20типы%20и%20конструкции/Логический%20тип.ipynb)\n",
"- [Базовые типы: строки и байтовые строки](3.%20Базовые%20типы%20и%20конструкции/Строки%20и%20байтовые%20строки.ipynb)\n", "- [Базовые типы: строки и байтовые строки](3.%20Базовые%20типы%20и%20конструкции/Строки%20и%20байтовые%20строки.ipynb)\n",
"- [Базовые типы: объект None](3.%20Базовые%20типы%20и%20конструкции/Объект%20None.ipynb)\n", "- [Базовые типы: объект None](3.%20Базовые%20типы%20и%20конструкции/Объект%20None.ipynb)\n",
"- [Конструкции управления потоком](3.%20Базовые%20типы%20и%20конструкции/Конструкции%20управления%20потоком.ipynb)\n", "- [Конструкции управления потоком](3.%20Базовые%20типы%20и%20конструкции/Конструкции%20управления%20потоком.ipynb)\n",
"- [Пример на управление потоком](3.%20Базовые%20типы%20и%20конструкции/Пример%20на%20управление%20потоком.ipynb)\n", "- [Пример на управление потоком](3.%20Базовые%20типы%20и%20конструкции/Пример%20на%20управление%20потоком.ipynb)\n",

@ -15,7 +15,7 @@
"source": [ "source": [
"Итак, мы с вами разобрали множества, давайте попробуем решить задачу на их применение.\n", "Итак, мы с вами разобрали множества, давайте попробуем решить задачу на их применение.\n",
"\n", "\n",
"В качестве примера попробуем выяснить, через сколько итераций функция `random.randint` выдаст повтор. Как вы уже знаете, `randint` возвращает какое-то значение случайное в интервале ему переданном. Давайте импортируем наш модуль `random`, который мы будем использовать, и определим наш `random_set`, в который мы будем складывать наши уникальные числа." "В качестве примера попробуем выяснить, через сколько итераций функция `random.randint` выдаст повтор. Как вы уже знаете, `randint` возвращает какое-то значение случайное в переданном ему интервале. Давайте импортируем наш модуль `random`, который мы будем использовать, и определим наш `random_set`, в который мы будем складывать наши уникальные числа."
] ]
}, },
{ {
@ -91,7 +91,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@ -105,7 +105,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.8" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -685,7 +685,7 @@
"metadata": {}, "metadata": {},
"source": [ "source": [
"Еще один полезный метод, который работает со списками и строками, это метод строки `join`.\n", "Еще один полезный метод, который работает со списками и строками, это метод строки `join`.\n",
"Он позволяет взять список и форматировать строку с помощью разделителя какого-то." "Он позволяет взять список и форматировать строку с помощью какого-то разделителя."
] ]
}, },
{ {
@ -923,7 +923,7 @@
"id": "8e935365", "id": "8e935365",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Например, мы можем создать кортеж `immutables`, куда положим неизменяемые типы наши." "Например, мы можем создать кортеж `immutables`, куда положим наши неизменяемые типы."
] ]
}, },
{ {
@ -1076,7 +1076,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@ -1090,7 +1090,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.8" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -36,7 +36,7 @@
"source": [ "source": [
"Просто функция, принимающая одну функцию, возвращающая другую функцию. Как вы уже знаете, в Python'е функция - это объект первого класса, поэтому их можно возвращать, принимать и производить с ними разные операции.\n", "Просто функция, принимающая одну функцию, возвращающая другую функцию. Как вы уже знаете, в Python'е функция - это объект первого класса, поэтому их можно возвращать, принимать и производить с ними разные операции.\n",
"\n", "\n",
"Итак, простейший декоратор просто возвращает функцию и возвращает её же, то есть такой `identity` декоратор, который ничего не делает. Однако, часто бывает полезно с функцией что-то делать, и мы с вами это сейчас как раз обсудим." "Итак, простейший декоратор просто принимает функцию и возвращает её же, то есть такой `identity` декоратор, который ничего не делает. Однако, часто бывает полезно с функцией что-то делать, и мы с вами это сейчас как раз обсудим."
] ]
}, },
{ {
@ -765,7 +765,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@ -779,7 +779,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.8" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -0,0 +1,378 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "be7bc6cb",
"metadata": {},
"source": [
"# Файлы #"
]
},
{
"cell_type": "markdown",
"id": "cc8b2eae",
"metadata": {},
"source": [
"На этой лекции мы поговорим с вами о файлах и том, как с ними работать.\n",
"\n",
"С файлами, на самом деле, в Python'е никаких проблем не должно возникнуть.\n",
"Все довольно просто. Чтобы открыть файлы мы используем встроенный метод `open`, встроенную функцию `open`, и передаем ей `filename`."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "5974cb39",
"metadata": {},
"outputs": [],
"source": [
"f = open(\"filename\")"
]
},
{
"cell_type": "markdown",
"id": "aee8d1e2",
"metadata": {},
"source": [
"У нас возвращается файловый объект, с которым мы потом можем работать, для того чтобы записывать данные или читать данные из файлов. \n",
"\n",
"Можно их открывать на запись, на чтение, на чтение и запись, и на дозапись. Делается это с помощью модов, которым мы тоже придаем функцию `open`. Например, `a` — это дозапись, `w` — это, очевидно, запись, `r` — это прочтение, `r+` — это запись и чтение одновременно. Точно так же мы можем открывать файл в бинарном виде, то есть работать с бинарными данными, и происходит все аналогично обычному типу. Мы просто добавляем в мод букву `b`."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "3584b6c2",
"metadata": {},
"outputs": [],
"source": [
"text_modes = [\"r\", \"w\", \"a\", \"r+\"]\n",
"binary_modes = [\"br\", \"bw\", \"ba\", \"br+\"]"
]
},
{
"cell_type": "markdown",
"id": "83c0ce60",
"metadata": {},
"source": [
"Чтобы открыть файл, например, на запись, мы передаем мод `w` в функцию `open`. Чтобы записать данный файл, мы берем наш файловый объект и пользуемся методом файла `write`, передавая туда строку. В данном случае мы передаем строку, и наш метод `write` возвращает количество символов, которые мы записали. Если это будет байтовая информация, то, собственно, это будет не количество символов, а количество байт. Чтобы закрыть файл, нам нужно вызвать метод `close`, и файл нужно обязательно закрывать."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "2b25c2b5",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"47"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f = open(\"filename\", \"w\")\n",
"\n",
"f.write(\"The world is changed.\\nI taste it in the water.\\n\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "ebc3244a",
"metadata": {},
"outputs": [],
"source": [
"f.close()"
]
},
{
"cell_type": "markdown",
"id": "16383eec",
"metadata": {},
"source": [
"Если вы откроете файл и не закроете файл, может произойти что-нибудь страшное и ужасное. На самом деле нет, но принято файлы закрывать, и мы поговорим с вами о том, как с этим работать. Итак, чтобы открыть файл на чтение и запись, нам нужно использовать `r+`, и мы можем читать данные из файла с помощью метода `read`. `Read` по умолчанию читает столько, сколько сможет. Если файл не поместится в памяти, то это ваши проблемы. Вы также можете указать в методе `read` конкретное количество информации, которое вы хотите прочитать, передав `size`, но по умолчанию читается весь файл и выводится, собственно."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "9d740b8c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'The world is changed.\\nI taste it in the water.\\n'"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f = open(\"filename\", \"r+\")\n",
"f.read()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "aee2da15",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"49"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f.tell()"
]
},
{
"cell_type": "markdown",
"id": "1d4c7a2b",
"metadata": {},
"source": [
"Здесь мы выводим все строчки, которые туда записали, и видим, что на самом деле информация в файл попала. Очень важный момент. Когда мы прочитали весь файл, у нас указатель того, где мы сейчас находимся в файле — в самом конце. В данном случае это 47 символов. Если мы попробуем прочитать еще раз, то мы ничего не найдем, потому что мы весь файл уже прочитали и у нас пойнтер в самом конце."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "cf719f78",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"''"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f.read()"
]
},
{
"cell_type": "markdown",
"id": "9b0798a6",
"metadata": {},
"source": [
"Для того чтобы прочитать его, например, заново, нам нужно использовать метод `seek` и перенести указатель на начало файла. Как вы видите, он равен нулю, и теперь мы можем действительно читать данные заново и, например, опять же закрыть файл, потому что файлы всегда нужно закрывать."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "57b6f5bb",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f.seek(0)\n",
"f.tell()"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "4ce93652",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The world is changed.\n",
"I taste it in the water.\n",
"\n"
]
}
],
"source": [
"print(f.read())\n",
"f.close()"
]
},
{
"cell_type": "markdown",
"id": "8109309b",
"metadata": {},
"source": [
"Собственно, кроме обычного метода `read`, есть также полезные методы для работы со строками, которые позволяют вам читать строки, одну, например, или несколько.\n",
"\n",
"Чтобы прочитать конкретно одну строку, можно использовать метод `readline` у того же файлового объекта, и метод вернет строку, разбив по символу переноса строки. Как видите, у нас вернулась только первая строка без второй."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "1a2fd52d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The world is changed.\n",
"\n"
]
}
],
"source": [
"f = open(\"filename\", \"r+\")\n",
"print(f.readline())\n",
"f.close()"
]
},
{
"cell_type": "markdown",
"id": "16da382f",
"metadata": {},
"source": [
"Если вам интересно прочитать сразу все строки, разбить их по символу переноса строки и записать все это, например, в список, можно использовать метод `readlines`. Он сделает именно это — вернет список строк, разбитых по символу перевода строки."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "1d65e32e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['The world is changed.\\n', 'I taste it in the water.\\n']"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f = open(\"filename\", \"r+\")\n",
"f.readlines()"
]
},
{
"cell_type": "markdown",
"id": "9fb151d1",
"metadata": {},
"source": [
"Если мы закроем файл и попробуем его прочитать, очевидно, у нас ничего не получится, потому что закрытый файл прочитать нельзя. Будьте внимательны."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "13c0a8d3",
"metadata": {},
"outputs": [
{
"ename": "ValueError",
"evalue": "I/O operation on closed file.",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-14-bf012af3c351>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclose\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[1;32m----> 2\u001b[1;33m \u001b[0mf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mread\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;31mValueError\u001b[0m: I/O operation on closed file."
]
}
],
"source": [
"f.close()\n",
"f.read()"
]
},
{
"cell_type": "markdown",
"id": "6ac70071",
"metadata": {},
"source": [
"На самом деле, я рекомендую вам работать с файлами немного другим образом. Существует специальная вещь, называемая контекстный менеджер, который вам позволяет не заботиться о закрытии файлов. Вы можете открыть файл с помощью оператора `with`, и записать в файловый объект переменную `f` и потом работать с ним внутри этого контекстного блока и не закрывать его, потому что контекстный менеджер заботится об этом за вас."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "ed64d12d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The world is changed.\n",
"I taste it in the water.\n",
"\n"
]
}
],
"source": [
"with open(\"filename\") as f:\n",
" print(f.read())"
]
},
{
"cell_type": "markdown",
"id": "7318dd16",
"metadata": {},
"source": [
"Про контекстный менеджер мы поговорим позднее, а пока просто можете использовать эту конструкцию и не заботиться о том, чтобы закрывать файл.\n",
"\n",
"Итак, с файлами в Python работать довольно просто. У нас есть файловые объекты, мы можем в них писать, из них читать. Не забывайте закрывать файлы или используйте контекстный менеджер."
]
}
],
"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.8.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

@ -201,7 +201,7 @@
"id": "a080fbf3", "id": "a080fbf3",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Таким образом, если мы определим функцию `extender`, которая принимает какой-то `source_list`, то есть исходный `list`, и новый `list` и пытается расширить `source_list` с помощью `extend_list`'а, то есть просто добавить в конец все его элементы, мы вот определяем `values` и пытаемся расширить `values` с помощью `[4, 5, 6]`, то окажется, что `values` у нас изменится." "Таким образом, если мы определим функцию `extender`, которая принимает какой-то `source_list`, то есть исходный `list`, и новый `list` и пытается расширить `source_list` с помощью `extend_list`'а, то есть просто добавить в конец все его элементы, мы определяем `values` и пытаемся расширить `values` с помощью `[4, 5, 6]`, то окажется, что `values` у нас изменится."
] ]
}, },
{ {
@ -719,7 +719,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@ -733,7 +733,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.8" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -165,7 +165,7 @@
"id": "18d1ca79", "id": "18d1ca79",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Гораздо более интересный момент - это если мы наш `get_multiplier` что-нибудь передадим. Давайте попробуем определить функцию `inner`, которая будет принимать один аргумент и умножать она его будет всегда на то самое число, которое мы передали в `get_multiplier`." "Гораздо более интересный момент - это если мы в наш `get_multiplier` что-нибудь передадим. Давайте попробуем определить функцию `inner`, которая будет принимать один аргумент и умножать она его будет всегда на то самое число, которое мы передали в `get_multiplier`."
] ]
}, },
{ {
@ -887,7 +887,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@ -901,7 +901,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.8" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -102,7 +102,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
@ -116,7 +116,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.8" "version": "3.10.12"
} }
}, },
"nbformat": 4, "nbformat": 4,

@ -0,0 +1,540 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4b201c2f",
"metadata": {},
"source": [
"# Генерация исключений #"
]
},
{
"cell_type": "markdown",
"id": "249ae766",
"metadata": {},
"source": [
"На предыдущей лекции мы начали с вами свое знакомство с исключениями, и в этой лекции мы продолжим разбирать их работу и обсудим более подробно, как обрабатывать исключения, как получать доступ к объекту исключения, а также обсудим отдельные исключения вида `AssertionError` и вопросы производительности исключений."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "5677dc5b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2 No such file or directory\n"
]
}
],
"source": [
"try:\n",
" with open(\"/file/not/found\") as f:\n",
" content = f.read()\n",
" \n",
"except OSError as err:\n",
" print(err.errno, err.strerror)"
]
},
{
"cell_type": "markdown",
"id": "bba1230f",
"metadata": {},
"source": [
"Для того чтобы получить доступ к объекту исключений, нам необходимо воспользоваться конструкцией `except as err`. В данном примере, если будет сгенерировано исключение `OSError`, то сам объект исключений будет связан с переменной `err` и эта переменная `err` будет доступна в блоке `except`."
]
},
{
"cell_type": "markdown",
"id": "56767c12",
"metadata": {},
"source": [
"У каждого объекта типа исключений есть свои свойства, например, `errno` и `srterror` — это строковое описание ошибки и код ошибки. При помощи этих атрибутов можно получать доступ и обрабатывать исключения нужным вам образом. Очень часто используется атрибут `args` для доступа к атрибутам исключения."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "0c4d7d62",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"файл не существует /file/not/found\n"
]
}
],
"source": [
"import os.path\n",
"\n",
"filename = \"/file/not/found\"\n",
"\n",
"try:\n",
" if not os.path.exists(filename):\n",
" raise ValueError(\"файл не существует\", filename)\n",
" \n",
"except ValueError as err:\n",
" message, filename = err.args[0], err.args[1]\n",
" print(message, filename)"
]
},
{
"cell_type": "markdown",
"id": "e3e20202",
"metadata": {},
"source": [
"Предположим, мы проверили, что файл не существует, и сгенерировали исключение `ValueError`, передали туда строку и имя файла. Так вот, если мы в блоке `except` обратимся к объекту исключения, то у него будут доступен атрибут `args` — это список из наших параметров и можем делать с ними все, что захотим.\n",
"\n",
"Иногда нам может потребоваться стек вызовов при генерации исключения. Стек вызовов можно получить при помощи модуля `traceback` и вызвать метод `print_exc`. Давайте попробуем выполнить этот пример в консоли и посмотрим, что выведется на экран."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "21569499",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"None\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"Traceback (most recent call last):\n",
" File \"<ipython-input-4-adc0be654a54>\", line 4, in <module>\n",
" with open(\"/file/not/found\") as f:\n",
"FileNotFoundError: [Errno 2] No such file or directory: '/file/not/found'\n"
]
}
],
"source": [
"import traceback\n",
"\n",
"try:\n",
" with open(\"/file/not/found\") as f:\n",
" content = f.read()\n",
"\n",
"except OSError as err:\n",
" trace = traceback.print_exc()\n",
" print(trace)"
]
},
{
"cell_type": "markdown",
"id": "06d47261",
"metadata": {},
"source": [
"Итак, у нас получилось распечатать стек вызовов. Иногда стек вызовов может вам помочь при разбирательстве причин, при которых возникло исключение. \n",
"\n",
"Для того чтобы сгенерировать, собственно, исключение, вам необходима инструкция `raise`. Для генерации исключения мы должны написать `raise` и указать класс исключения. Также можно указывать не только класс, но и объект исключения, и указывать ему дополнительные какие-то свойства. Как я уже говорил, к этим свойствам потом можно будет обратится через объект исключения при помощи атрибута `args`."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "b578c4de",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"введите число: w\n",
"некорректное значение!\n"
]
}
],
"source": [
"try:\n",
" raw = input(\"введите число: \")\n",
" if not raw.isdigit():\n",
" raise ValueError\n",
" \n",
"except ValueError:\n",
" print(\"некорректное значение!\")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "28e93b13",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"введите число: w\n",
"некорректное значение! ('плохое число', 'w')\n"
]
}
],
"source": [
"try:\n",
" raw = input(\"введите число: \")\n",
" if not raw.isdigit():\n",
" raise ValueError(\"плохое число\", raw)\n",
" \n",
"except ValueError as err:\n",
" print(\"некорректное значение!\", err)"
]
},
{
"cell_type": "markdown",
"id": "531ad8cc",
"metadata": {},
"source": [
"В данном случае мы проверяем, что пользователь ввел не число и если так действительно произошло, то генерируем исключение и затем в блоке `except` обрабатываем его.\n",
"\n",
"Иногда может случиться так, что вы пишете какую-то функцию и отлавливаете исключения, но вы не знаете, что делать дальше с этим исключением. Например, вы можете просто вывести какую-то информацию на экран и делегировать обработку этого исключения другим функциям — тем, который вызвали вашу. Для этого нужно использовать инструкцию `raise` без параметров."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "0348d576",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"введите число: e\n",
"некорректное значение! ('плохое число', 'e')\n"
]
},
{
"ename": "ValueError",
"evalue": "('плохое число', 'e')",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-7-03e61a5d927e>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0mraw\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0minput\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"введите число: \"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0misdigit\u001b[0m\u001b[1;33m(\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[1;32m----> 4\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"плохое число\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mValueError\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mValueError\u001b[0m: ('плохое число', 'e')"
]
}
],
"source": [
"try:\n",
" raw = input(\"введите число: \")\n",
" \n",
" if not raw.isdigit():\n",
" raise ValueError(\"плохое число\", raw)\n",
"\n",
"except ValueError as err:\n",
" print(\"некорректное значение!\", err)\n",
" # делегирование обработки исключения\n",
" raise"
]
},
{
"cell_type": "markdown",
"id": "75611c95",
"metadata": {},
"source": [
"В данном случае все происходит тоже самое, как в предыдущем примере, генерируются исключения, на экран выводится надпись \"Некорректное значение\", и при помощи инструкции `raise` мы делегируем исключение выше. Если мы попробуем исполнить код, то интерпретатор просто покажет на стандартный вывод информации об этом исключении и прекратит работу нашей программы. Если таких мест, в которых исключения делегируются, очень много, то иногда не понятно по последнему исключению, где именно оно возникло и для этого может использоваться конструкция `raise from Exception`. Давайте посмотрим, как отработает наша программа."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "fea7bab6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"введите число: d\n",
"ошибка: плохое число d\n"
]
},
{
"ename": "TypeError",
"evalue": "ошибка",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-8-23aebd9f3114>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0misdigit\u001b[0m\u001b[1;33m(\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[1;32m----> 5\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"плохое число\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mraw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mValueError\u001b[0m: ('плохое число', 'd')",
"\nThe above exception was the direct cause of the following exception:\n",
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-8-23aebd9f3114>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mValueError\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"ошибка:\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m1\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[1;32m----> 9\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"ошибка\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mTypeError\u001b[0m: ошибка"
]
}
],
"source": [
"try:\n",
" raw = input(\"введите число: \")\n",
" \n",
" if not raw.isdigit():\n",
" raise ValueError(\"плохое число\", raw)\n",
" \n",
"except ValueError as err:\n",
" print(\"ошибка:\", err.args[0], err.args[1])\n",
" raise TypeError(\"ошибка\") from err"
]
},
{
"cell_type": "markdown",
"id": "00db7b28",
"metadata": {},
"source": [
"Если мы сгенерируем одно исключение, отловим его в блоке `except`, и затем сгенерируем второе исключение `TypeError`, но укажем ему конструкцию `from err`. У нас получилось вызвать исключение и давайте внимательно посмотрим на `stack trace`, который произошел. Мы видим, что произошло исключение `ValueError` — это изначально которое мы сгенерировали. И дальше мы видим его `stack trace`, этого исключения. Дальше мы видим, что после этого исключения произошло второе исключение — `TypeError` и его `stack trace`. Таким образом, если мы будем использовать конструкцию `raise from Exception`, мы сможем отслеживать всю цепочку исключений, которая была сгенерирована."
]
},
{
"cell_type": "markdown",
"id": "c41f831b",
"metadata": {},
"source": [
"Говоря об исключениях, нельзя не затронуть инструкцию `assert`. Давайте поговорим, зачем она нужна. По умолчанию, если выполнить инструкцию `assert` с логическим выражением `True`, ничего не произойдет, но если попробовать выполнить инструкцию `assert` с логическим выражением, которое равно `False`, или \"ложь\", то будет сгенерировано исключение `AssertionError`."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "64bc3761",
"metadata": {},
"outputs": [],
"source": [
"assert True"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "4c4c07fe",
"metadata": {
"scrolled": true
},
"outputs": [
{
"ename": "AssertionError",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAssertionError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-10-e99f91a18d62>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32massert\u001b[0m \u001b[1;36m1\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mAssertionError\u001b[0m: "
]
}
],
"source": [
"assert 1 == 0"
]
},
{
"cell_type": "markdown",
"id": "caeb02ab",
"metadata": {},
"source": [
"Также мы можем передать некую дополнительную строку, которая будет потом передана в сам объект `AssertionError`."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "381676b1",
"metadata": {},
"outputs": [
{
"ename": "AssertionError",
"evalue": "1 не равен 0",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAssertionError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-11-e2b654902c90>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32massert\u001b[0m \u001b[1;36m1\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"1 не равен 0\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mAssertionError\u001b[0m: 1 не равен 0"
]
}
],
"source": [
"assert 1 == 0, \"1 не равен 0\""
]
},
{
"cell_type": "markdown",
"id": "e799aaab",
"metadata": {},
"source": [
"Давайте разберем кейсы, в каких случаях может использоваться `AssertionError` и инструкция `assert`. \n",
"\n",
"Предположим, у нас есть функция, она называется `get_user_by_id`, она нам ищет некоего пользователя по численному идентификатору. Мы должны убедиться в том, что нам действительно передали число, что мы работаем с целым числом. Это можно сделать при помощи `assert` и функции `isinstance`, то есть мы проверяем, является ли входной параметр `id` целым числом. Если это не так, то будет сгенерирована `AssertionError`."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "33871e62",
"metadata": {},
"outputs": [
{
"ename": "AssertionError",
"evalue": "id должен быть целым числом",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAssertionError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-12-6bcefc7016e1>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m\"__main__\"\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[0mget_user_by_id\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"foo\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;32m<ipython-input-12-6bcefc7016e1>\u001b[0m in \u001b[0;36mget_user_by_id\u001b[1;34m(id)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_user_by_id\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mid\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[1;32m----> 2\u001b[1;33m \u001b[1;32massert\u001b[0m \u001b[0misinstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mid\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mint\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"id должен быть целым числом\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"выполняем поиск\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m\"__main__\"\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mAssertionError\u001b[0m: id должен быть целым числом"
]
}
],
"source": [
"def get_user_by_id(id):\n",
" assert isinstance(id, int), \"id должен быть целым числом\"\n",
" print(\"выполняем поиск\")\n",
"\n",
"if __name__ == \"__main__\":\n",
" get_user_by_id(\"foo\")"
]
},
{
"cell_type": "markdown",
"id": "5196f5e1",
"metadata": {},
"source": [
"Итак, у нас сгенерировалось исключение `AssertionError`. Дело в том, что такие исключения не нужно обрабатывать — эти исключения, они предназначены, скорее, для программистов. То есть при написании наших программ на этапе разработки мы должны видеть, что мы делаем что-то не так, то есть мы передали в функцию некорректное значение, и видим `AssertionError` — наша программа завершила свою работу. В таком случае мы должны как-то реагировать и изменять код нашей программы. Не нужно, например, обрабатывать пользовательский ввод и пытаться обработать исключение `AssetionError` блоком `try except`.\n",
"\n",
"Если у нас таких мест будет очень много, то это затронет и производительность нашей программы. Оказывается, можно отключить все инструкции `assert` при помощи флага `O`. Тогда `AssertionError`, инструкция не будет сгенерирована, потому что `assert` вызван не будет. Этим как раз отличаются эти исключения от обычных пользовательских исключений и исключений стандартной библиотеки. Давайте попробуем сделать это. Запустим с флагом `O` нашу программу."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "310942f0",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"выполняем поиск\n"
]
}
],
"source": [
"! python -O ex.py"
]
},
{
"cell_type": "markdown",
"id": "765e8f49",
"metadata": {},
"source": [
"Итак, мы видим, что наша функция отработала, исключение `AssertionError` не было сгенерировано.\n",
"\n",
"Давайте еще поговорим немного о производительности исключений. Несмотря на то, что механизм обработки исключений очень удобный и его очень хорошо использовать в своих программах, тем не менее этот механизм не бесплатен. \n",
"\n",
"Рассмотрим пример. У нас есть цикл, и в этом цикле мы обращаемся к несуществующему ключу словаря и каждый раз при этом обращении у нас генерируется исключение `KeyError`. Попробуем при помощи `timeit` замерить, сколько это займет — мы получим некую условную единицу, тысячу операций мы смогли выполнить."
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "6f59e6df",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"454 µs ± 9.97 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n"
]
}
],
"source": [
"%%timeit\n",
"my_dict = {\"foo\": 1}\n",
"\n",
"for _ in range(1000):\n",
" try:\n",
" my_dict[\"bar\"]\n",
" \n",
" except KeyError:\n",
" pass"
]
},
{
"cell_type": "markdown",
"id": "469eb46f",
"metadata": {},
"source": [
"Если мы попробуем реализовать ту же самую задачу, но без использования исключений, например, при помощи конструкции `in`, то есть мы проверяем, что у нас ключик `bar` находится в словаре, и только в этом случае тогда мы обращаемся по ключу к этому словарю. Давайте опять попробуем посмотреть на результаты работы `timeit` для этого примера — и мы видим, что результат отличается на порядок, а иногда он может отличаться и на несколько порядков."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "4c40a7cb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"67.3 µs ± 4.65 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n"
]
}
],
"source": [
"%%timeit\n",
"my_dict = {\"foo\": 1}\n",
"\n",
"for _ in range(1000):\n",
" if \"bar\" in my_dict:\n",
" _ = my_dict[\"bar\"]"
]
},
{
"cell_type": "markdown",
"id": "85daa3f9",
"metadata": {},
"source": [
"То есть хочу обратить ваше внимание на то, что исключения, особенно если они у вас генерируются в программе очень часто, возможно следует изменить код вашей программы и сделать так, чтобы она работала без исключений, потому, что это очень сильно влияет на производительность вашей итоговой Python программы.\n",
"\n",
"Итак, мы обсудили вопросы, которые касаются исключений, поговорили про доступ к объекту исключений, о том как генерируются исключения при помощи `raise`, также обсудили специальные исключения типа `AssertionError`, поговорили о вопросах производительности и нам осталось поговорить о работе с собственными исключениями. В целом работа с собственными исключениями никак не отличается от работы с исключениями стандартной библиотеки и мы попробуем в следующей лекции разобрать работу с собственными исключениями на конкретном примере работы с библиотекой `requests`."
]
}
],
"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.8.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

@ -0,0 +1,741 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "adeac7e8",
"metadata": {},
"source": [
"# Классы исключений и их обработка #"
]
},
{
"cell_type": "markdown",
"id": "e85bd50b",
"metadata": {},
"source": [
"В предыдущих лекциях мы несколько раз упоминали про исключения в Python. Сегодня мы как раз обсудим то, как устроены исключения в Python. Мы поговорим про генерацию исключений, что при этом происходит, обсудим типы исключений, а также рассмотрим, как обрабатывать исключения в Python.\n",
"\n",
"Для начала давайте попробуем вызвать исключения и посмотрим, что при этом произойдет. Для этого нам потребуется консоль. Давайте попробуем написать простенькую программу на Python. Пусть это будет файл `zero.py`. Попробуем вывести строчку `1/0`. Давайте запустим нашу программу."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "3a0fab6c",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Traceback (most recent call last):\n",
" File \"zero.py\", line 1, in <module>\n",
" 1/0\n",
"ZeroDivisionError: division by zero\n"
]
}
],
"source": [
"! python zero.py"
]
},
{
"cell_type": "markdown",
"id": "546ec448",
"metadata": {},
"source": [
"Итак, мы видим, что произошло. При делении на 0 возникло исключение, и при возникновении исключения на стандартный вывод печатается информация о его типе, дополнительная информация о исключении, а также стек вызовов.\n",
"\n",
"Давайте посмотрим, что у нас произошло. Мы видим, на экран напечаталось - тип исключения — `ZeroDivisionError`, дополнительная информация и сам стек. Стек у нас пока небольшой, и для того чтобы посмотреть на стек настоящей программы, давайте посмотрим дополнительный пример."
]
},
{
"cell_type": "markdown",
"id": "4e1a1ee8",
"metadata": {},
"source": [
"Пусть у нас есть программа, которая считает количество строк в исходном файле, который подали на вход. Выглядит она нам не очень интересно как, то есть есть некая функция `main`, в функции `main` вызывается другая функция. Эта функция вызывает функцию `wc` с переданным файлом и печатает на экран некую строчку с именем файла и количеством строк в нем. А функция `wc` открывает этот файл и итератором проходится по всем строкам в этом файле.\n",
"\n",
"```python\n",
"import sys\n",
"\n",
"def wc(filename):\n",
" count = 0\n",
" \n",
" with open(filename) as f:\n",
" for _ in f:\n",
" count += 1\n",
" \n",
" return count\n",
"\n",
"def process_file(filename):\n",
" count = wc(filename)\n",
" \n",
" print(f\"file: {filename} has {count} lines\")\n",
"\n",
"def _main():\n",
" process_file(sys.argv[1])\n",
" \n",
"if __name__ == \"__main__\":\n",
" _main()\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "8f7658a8",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Traceback (most recent call last):\n",
" File \"wc.py\", line 21, in <module>\n",
" _main()\n",
" File \"wc.py\", line 18, in _main\n",
" process_file(sys.argv[1])\n",
" File \"wc.py\", line 13, in process_file\n",
" count = wc(filename)\n",
" File \"wc.py\", line 6, in wc\n",
" with open(filename) as f:\n",
"FileNotFoundError: [Errno 2] No such file or directory: '/etc/test'\n"
]
}
],
"source": [
"! python wc.py /etc/test"
]
},
{
"cell_type": "markdown",
"id": "7e6043d2",
"metadata": {},
"source": [
"Давайте попробуем вызвать данную программу, передадим ей либо несуществующий файл, либо файл, недоступный для чтения, и посмотрим, что при этом произойдет. Итак, у нас есть наша программа. Давайте попробуем ей передать файл, которого нет. Это файл `/etc/test`. Итак, как видим, сгенерировалось исключение.\n",
"\n",
"При этом Python остановил свою работу, и на экране мы видим тип исключения — это `FileNotFoundError`, а также дополнительную информацию - код ошибки, текстовые сообщения, что файла `/etc/test` нет. Также теперь мы видим стек вызовов, теперь он у нас побольше. Давайте распечатаем текст самой программы и немножко разберемся, что же значит этот стек вызовов.\n",
"\n",
"Давайте посмотрим, что на строке 6 фукнции `wc` была вызвана функция `open`. Давайте еще раз сгенерируем наше исключение. Итак, мы сгенерировали исключение, и посмотрим на текст нашей программы. Итак, мы видим, что в строке 6 нашей программы была вызвана функция `open`. Именно это привело к генерации исключения. Давайте дальше пройдемся по стеку вызовов и раскрутим его наверх. Мы видим, что наша функция `wc` была вызвана на строке 13, вот видим ее. Это было сделано функцией `process_file`. Если мы будем раскручивать стек дальше, то мы сможем отследить всю последовательность вызовов функции, которая привела к исключению. Таким образом, это нам очень может сильно помочь, если мы будем разбираться с чужими исключениями, которые вдруг внезапно возникли в программе и не были обработаны программистом.\n",
"\n",
"Какие типы исключений бывают? В Python бывают по большому счету два типа исключений.\n",
"\n",
"Первый — это исключения из стандартной библиотеки в Python. Они генерируются, собственно, самой библиотекой. То есть когда мы вызываем функцию стандартной библиотеки, например, мы видели, как функция `open` сгенерировала исключение `PermissionError`.\n",
"\n",
"А также второй тип исключений — это пользовательские исключения. Они могут быть сгенерированы и обработаны самим программистом при написании программ на Python.\n",
"\n",
"Давайте посмотрим на иерархию исключений в стандартной библиотеке Python. Все исключения наследуются от базового класса `BaseException`. Существуют несколько системных исключений, например, `SystemExit` — это исключение генерируется, если мы вызвали функцию `OSExit`. `KeyboardInterrupt` — это исключение генерируется, если мы нажали сочетание клавиш `Ctrl + C` и так далее."
]
},
{
"cell_type": "markdown",
"id": "0ffb082b",
"metadata": {},
"source": [
"```\n",
"BaseException\n",
"+-- SystemExit\n",
"+-- KeyboardInterrupt\n",
"+-- GeneratorExit\n",
"+-- Exception\n",
" +-- StopIteration\n",
" +-- AssertionError\n",
" +-- AttributeError\n",
" +-- LookupError\n",
" +-- IndexError\n",
" +-- KeyError\n",
" +-- OSError\n",
" +-- SystemError\n",
" +-- TypeError\n",
" +-- ValueError\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "1dd418b8",
"metadata": {},
"source": [
"Все остальные исключения генерируется от базового класса `Exception`. Именно от этого класса нужно будет порождать и свои исключения, чем мы и займемся дальше.\n",
"\n",
"Давайте посмотрим и обсудим некоторые исключения из базы библиотеки, такие как, например, `AttributeError`, `IndexError`, `KeyError`, `TypeError`, и попробуем их вызвать. Для этого нам снова потребуется консоль. Давайте запустим интерпретатор. Объявим простенький класс. Пусть это будет класс, который ничего не делает."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "b09aeca0",
"metadata": {},
"outputs": [],
"source": [
"class MyClass:\n",
" pass"
]
},
{
"cell_type": "markdown",
"id": "9f2183bb",
"metadata": {},
"source": [
"Давайте создадим объект этого класса и попробуем обратиться к атрибуту `foo`."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "6ac92428",
"metadata": {},
"outputs": [
{
"ename": "AttributeError",
"evalue": "'MyClass' object has no attribute 'foo'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-11-0c1a83a28f93>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mobj\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mMyClass\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[1;32m----> 2\u001b[1;33m \u001b[0mobj\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfoo\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mAttributeError\u001b[0m: 'MyClass' object has no attribute 'foo'"
]
}
],
"source": [
"obj = MyClass()\n",
"obj.foo"
]
},
{
"cell_type": "markdown",
"id": "e2657a83",
"metadata": {},
"source": [
"Как мы видим, сгенерировалось исключение `AttributeError`.\n",
"\n",
"Давайте попробуем объявить словарик. Пусть в нем будет один элемент. "
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "e57ffe19",
"metadata": {},
"outputs": [],
"source": [
"d = {\"foo\": 1}"
]
},
{
"cell_type": "markdown",
"id": "7535f072",
"metadata": {},
"source": [
"Попробуем обратиться к несуществующему ключику."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "69060ba8",
"metadata": {},
"outputs": [
{
"ename": "KeyError",
"evalue": "'bar'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-13-586c1efcf0c1>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0md\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"bar\"\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mKeyError\u001b[0m: 'bar'"
]
}
],
"source": [
"d[\"bar\"]"
]
},
{
"cell_type": "markdown",
"id": "87cb9673",
"metadata": {},
"source": [
"Итак, у нас получилось сгенерировать исключение `KeyError`.\n",
"\n",
"Если бы мы объявили список из двух элементов и обратились, например, к 10-му элементу, у нас бы сгенерировался `IndexError`."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "e134d942",
"metadata": {},
"outputs": [
{
"ename": "IndexError",
"evalue": "list index out of range",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mIndexError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-14-8c556715db69>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0ml\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0ml\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mIndexError\u001b[0m: list index out of range"
]
}
],
"source": [
"l = [1, 2]\n",
"l[10]"
]
},
{
"cell_type": "markdown",
"id": "aeb59421",
"metadata": {},
"source": [
"Также если мы, например, попробовали преобразовать к целому числу строчку, мы бы получили `ValueError`."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "cd6a04fc",
"metadata": {},
"outputs": [
{
"ename": "ValueError",
"evalue": "invalid literal for int() with base 10: 'fdf'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-15-0921b230eda9>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"fdf\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mValueError\u001b[0m: invalid literal for int() with base 10: 'fdf'"
]
}
],
"source": [
"int(\"fdf\")"
]
},
{
"cell_type": "markdown",
"id": "51be3abc",
"metadata": {},
"source": [
"Или если бы попробовали сложить число и строку, получили бы `TypeError`."
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "f6ccfd3b",
"metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "unsupported operand type(s) for +: 'int' and 'str'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-16-b780703cc5f9>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;36m1\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;34m\"1\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'int' and 'str'"
]
}
],
"source": [
"1 + \"1\""
]
},
{
"cell_type": "markdown",
"id": "6ea521e0",
"metadata": {},
"source": [
"Все эти исключения — из стандартной библиотеки. Вам при работе очень часто придется сталкиваться с ними, и теперь вы знаете, как они выглядят, и при каких обстоятельствах они могут быть сгенерированы.\n",
"\n",
"Если исключение сгенерировано, то, как я уже говорил, Python-интерпретатор остановит свою работу и на экран будет выведен стек вызовов и информация о типе исключений.\n",
"\n",
"И нам это не всегда хочется, чтобы такое поведение было по умолчанию, и поэтому нужно как-то обработать сгенерированные исключения. Обработать его можно при помощи блока `try except`. То есть у нас есть код, который потенциально может генерировать исключения, мы этот код обрамляем в блок `try except`, и тем самым при генерации исключений управление будет передано в блок `except`."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "a88352fe",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Error\n"
]
}
],
"source": [
"try:\n",
" 1 / 0\n",
"\n",
"except:\n",
" print(\"Error\")"
]
},
{
"cell_type": "markdown",
"id": "ed95ab0c",
"metadata": {},
"source": [
"Таким образом можно отловить все исключения, которые генерируются в блоке `try except`.\n",
"\n",
"Если мы в блоке `except` укажем исключение, например в данном случае `Exception`, то мы будем отлавливать исключения всех типов, у которых класс `Exception` является родителем."
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "c48a05fb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Error\n"
]
}
],
"source": [
"try:\n",
" 1 / 0\n",
"\n",
"except Exception:\n",
" print(\"Error\")"
]
},
{
"cell_type": "markdown",
"id": "4427af89",
"metadata": {},
"source": [
"В целом неправильно ждать любые исключения, и это может привести к непредвиденным сюрпризам работы вашей программы.\n",
"\n",
"Давайте рассмотрим следующий пример. Мы в бесконечном цикле просим пользователя ввести число, преобразовываем его строку к числу целому."
]
},
{
"cell_type": "markdown",
"id": "b8d34e47",
"metadata": {},
"source": [
"```python\n",
"while True:\n",
" try:\n",
" raw = input(\"Input number: \")\n",
" number = int(raw)\n",
" break\n",
" \n",
" except:\n",
" print(\"not number\")\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "0f185a81",
"metadata": {},
"source": [
"В данном случае может возникнуть исключение `ValueError`. Однако, мы в своей программе отлавливаем все исключения. Давайте попробуем ее исполнить и посмотрим, как она работает. Так, нас просят ввести число. Давайте введем. Нас снова просят ввести число. Давайте, например, попросим наш интерпретатор завершить свою работу и нажмем `Ctrl + C`.\n",
"\n",
"Как мы видим, у нас это не получилось сделать, то есть наша программа стала вести себя непредвиденным образом. Она нас снова опять заставляет ввести число. Это как раз говорит о том, что нужно обрабатывать конкретные исключения, и в данном случае правильным вариантом данной программы была бы обработка исключений `ValueError`."
]
},
{
"cell_type": "markdown",
"id": "eef3c8bd",
"metadata": {},
"source": [
"```python\n",
"while True:\n",
" try:\n",
" raw = input(\"Input number: \")\n",
" number = int(raw)\n",
" break\n",
" \n",
" except ValueError:\n",
" print(\"not number\")\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "d7444dfb",
"metadata": {},
"source": [
"Если мы попробуем снова запустить данную программу и нажмем `Ctrl + C`. Давайте введем сначала неправильное число, нажмем `Ctrl + C`, то возникнет исключение и его никто не обработает, и наша программа завершит свою работу. Поэтому не следует обрабатывать все исключения и оставлять пустым блок `except`. Имейте это в виду.\n",
"\n",
"Также у блока `try except` может быть блок `else`. Блок `else` вызывается в том случае, если никакого исключения не произошло."
]
},
{
"cell_type": "markdown",
"id": "28cb2cbe",
"metadata": {},
"source": [
"```python\n",
"while True:\n",
" try:\n",
" raw = input(\"Input number: \")\n",
" number = int(raw)\n",
" break\n",
" \n",
" except ValueError:\n",
" print(\"not number\")\n",
" \n",
" else:\n",
" break\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "545ef10a",
"metadata": {},
"source": [
"Если нам нужно обработать несколько исключений, мы можем использовать несколько блоков `except` и указать разные классы для обработки исключения. Причем в каждом блоке `except` может быть свой собственный обработчик."
]
},
{
"cell_type": "markdown",
"id": "2a06fab7",
"metadata": {},
"source": [
"```python\n",
"while True:\n",
" try:\n",
" raw = input(\"Input number: \")\n",
" number = int(raw)\n",
" break\n",
" \n",
" except ValueError:\n",
" print(\"not number\")\n",
" \n",
" except KeyboardInterrupt:\n",
" print(\"exit\")\n",
" break\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "dd9d3b79",
"metadata": {},
"source": [
"Например, если мы в данном примере ввели некорректное число, то мы еще раз продолжим работу нашего цикла. В противном случае, если мы нажали `Ctrl + C`, то мы прекратим цикл. Именно таким образом можно обработать несколько исключений."
]
},
{
"cell_type": "markdown",
"id": "2db929d8",
"metadata": {},
"source": [
"Если обработчик исключений выглядит одинаково, то несколько исключений можно передать в виде списка в блок `except` и также обработать сгенерированные исключения."
]
},
{
"cell_type": "markdown",
"id": "8185d03b",
"metadata": {},
"source": [
"```python\n",
"while True:\n",
" try:\n",
" raw = input(\"Input number: \")\n",
" number = int(raw)\n",
" total_count /= number\n",
" break\n",
" \n",
" except (ValueError, ZeroDivisionError):\n",
" print(\"not number\")\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "076e56ef",
"metadata": {},
"source": [
"В данном случае мы ожидаем два исключения, например, что пользователь ввел некорректное число, либо если он ввел 0, то данная программа сгенерирует `ZeroDivisionError`, и мы его отловим.\n",
"\n",
"Мы уже с вами обсуждали классы и наследования в классах, и вот у `exception`'ов есть своя иерархия, и сделана она неспроста. Также при помощи родительского класса можно обрабатывать несколько исключений. Давайте, например, посмотрим на иерархию классов `LookupError`."
]
},
{
"cell_type": "markdown",
"id": "8ce76d09",
"metadata": {},
"source": [
"```\n",
"+-- LookupError\n",
" +-- IndexError\n",
" +-- KeyError\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "047d3573",
"metadata": {},
"source": [
"Этот класс является родительским для исключений `IndexError` и `KeyError`. Мы можем это проверить при помощи известных нам функций `issubclass`. Рассмотрим следующий пример."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "42250fda",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"issubclass(KeyError, LookupError)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "b891412f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"issubclass(IndexError, LookupError)"
]
},
{
"cell_type": "markdown",
"id": "a7b4d748",
"metadata": {},
"source": [
"У нас есть некая структура данных, это наша база данных, которая хранит по цветам надписи на футболках. Нам необходимо получать доступ к этим надписям по цветам.\n",
"\n",
"Пользователя просим ввести цвет, просим ввести номер по порядку, а затем обращаемся к нашей структуре данных по ключу, а затем по индексу. И если пользователь введет, например, некорректный цвет, то будет сгенерировано исключение `KeyError`, а если пользователь введет недопустимый индекс, то будет вызвано исключение `IndexError`. Все эти исключения мы можем обработать при помощи базового класса `LookupError`. Иногда это очень удобно и требуется для как раз обработки пользовательских исключений. "
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "be5fda81",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"введите цвет: red\n",
"введите номер по порядку: 1\n",
"вы выбрали: flower\n"
]
}
],
"source": [
"database = {\n",
" \"red\": [\"fox\", \"flower\"],\n",
" \"green\": [\"peace\", \"M\", \"python\"]\n",
"}\n",
"\n",
"try:\n",
" color = input(\"введите цвет: \")\n",
" number = input(\"введите номер по порядку: \")\n",
" label = database[color][int(number)]\n",
" print(\"вы выбрали:\", label)\n",
"\n",
"except LookupError:\n",
" # except (IndexError, KeyError):\n",
" print(\"Объект не найден\")"
]
},
{
"cell_type": "markdown",
"id": "a0060f21",
"metadata": {},
"source": [
"Также у исключений есть дополнительный блок `finally`. Рассмотрим проблему. Например, мы открываем файл, читаем строки, обрабатываем как-то эти строки, и в процессе работы нашей программы возникает исключение, которое мы не ждем, например. В таком случае файл закрыт не будет. У нас эти открытые файловые дескрипторы могут накапливаться, что, в принципе, не хорошо. Таким же образом могут накапливаться открытые сокеты, или не освобождаться память, всё что угодно.\n",
"\n",
"Для контроля таких ситуаций существуют, во-первых, контекстные менеджеры, а во-вторых, можно использовать блок `finally` в исключениях. Выглядит это таким образом, как представлено на слайде."
]
},
{
"cell_type": "markdown",
"id": "0a0ce1a0",
"metadata": {},
"source": [
"```python\n",
"f = open(\"/etc/hosts\")\n",
"\n",
"try:\n",
" for line in f:\n",
" print(line.rstrip(\"\\n\"))\n",
" 1 / 0\n",
" \n",
"except OSError:\n",
" print(\"ошибка\")\n",
" \n",
"finally:\n",
" f.close()\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "991a0c2a",
"metadata": {},
"source": [
"Мы пишем блок `finally` и вызываем метод `close` для нашего объекта `f`. В данном случае блок `finally` будет выполнен в любом случае. Возникло исключение или не возникло — блок `finally` будет выполнен.\n",
"\n",
"Итак, мы поговорили с вами про исключения, посмотрели на то, как они выглядят, как выглядит `stack trace`, обсудили, как ведет себя Python при генерации исключений. Также посмотрели на то, какие типы исключений бывают - это пользовательские исключения и исключения стандартной библиотеки. Также рассмотрели иерархию исключений в стандартной библиотеке и поговорили про их обработку. Мы продолжим обсуждать то, как устроены исключения, на следующей лекции."
]
}
],
"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.8.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

@ -0,0 +1,78 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "33f6f796",
"metadata": {},
"source": [
"# Реализация простого класса для чтения из файла #"
]
},
{
"cell_type": "markdown",
"id": "3190d19d",
"metadata": {},
"source": [
"Первое задание у нас для разогрева. Ваша задача написать Python-модуль `solution.py`, внутри которого определен класс `FileReader`.\n",
"\n",
"Инициализатор этого класса принимает аргумент - путь до файла на диске.\n",
"\n",
"У класса должен быть метод `read`, возвращающий содержимое файла в виде строки.\n",
"\n",
"Еще один момент - внутри метода `read` вы должны обрабатывать исключение `IOError`, возникающее, когда файла, с которым был инициализирован класс, на самом деле нет на жестком диске. В случае возникновения такой ошибки метод `read` должен возвращать пустую строку \"\".\n",
"\n",
"То есть класс должен работать следующим образом:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "07c2ccec",
"metadata": {},
"outputs": [],
"source": [
"from solution import FileReader"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "2536881e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n"
]
}
],
"source": [
"reader = FileReader(\"example.txt\")\n",
"print(reader.read())"
]
}
],
"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
}

@ -0,0 +1,64 @@
"""Реализация класса File с магическими методами"""
from os.path import join
from tempfile import gettempdir
class File:
"""Класс для работы с файлами"""
def __init__(self, file_path: "str"):
"""Конструктор базового класса"""
self.file_path: "str" = file_path
self.current_position: "int" = 0
def write(self, line: "str") -> None:
"""Запись строки в файл"""
with open(self.file_path, "w", encoding="utf-8") as des:
des.write(line)
def read(self) -> "str":
"""Чтение файла"""
try:
with open(self.file_path, encoding="utf-8") as des:
content: "str" = des.read()
except IOError:
content = ""
return content
def __add__(self, obj: "File") -> "File":
"""Сложение двух объектов"""
new_file = type(self)(join(gettempdir(), "temp.txt"))
new_file.write(self.read() + obj.read())
return new_file
def __str__(self) -> "str":
"""Строковое представление объекта"""
return self.file_path
def __iter__(self) -> "File":
"""Метод возращающий итератор"""
return self
def __next__(self) -> "str":
with open(self.file_path, "r", encoding="utf-8") as des:
des.seek(self.current_position)
line: "str" = des.readline()
if not line:
self.current_position = 0
raise StopIteration("EOF")
self.current_position = des.tell()
return line

@ -0,0 +1,41 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "f31366bb",
"metadata": {},
"source": [
"# Документация #"
]
},
{
"cell_type": "markdown",
"id": "47575f52",
"metadata": {},
"source": [
"[Магические методы](https://docs.python.org/3/reference/datamodel.html \"3. Data model\")"
]
}
],
"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
}

@ -0,0 +1,309 @@
{
"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<ipython-input-8-d4aa693aba62>\u001b[0m in \u001b[0;36m<module>\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
}

@ -0,0 +1,294 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e5872b4b",
"metadata": {},
"source": [
"# Контекстные менеджеры #"
]
},
{
"cell_type": "markdown",
"id": "2219c4bc",
"metadata": {},
"source": [
"В этой лекции мы поговорим с вами о контекстных менеджерах. С контекстными менеджерами вы уже работаете и работали, когда открывали, например, файлы. Мы с вами конкретно не останавливались на том, как это происходит внутри, но вы знаете, что если использовать контекстный менеджер `with` с открытием файла, вам не нужно заботиться о том, чтобы его потом закрыть, то есть контекстный менеджер делает это за вас. Вы открываете файл, записываете открытый файл в переменную `f`, записываете какие-то данные, и потом он сам как-то закрывается, вам не нужно писать `f.close()`."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "1673e912",
"metadata": {},
"outputs": [],
"source": [
"with open(\"access_log.log\", \"a\") as f:\n",
" f.write(\"New Access\")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "daa038dd",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"New Access\n"
]
}
],
"source": [
"! type access_log.log"
]
},
{
"cell_type": "markdown",
"id": "c19a4d03",
"metadata": {},
"source": [
"Контекстные менеджеры позволяют вам делать именно это. Они позволяют определить поведение, которое происходит в начале и в конце блока исполнения, блока `with`. Часто бывает необходимо, как, например, в случае с файлами, отрывать и закрывать какой-то ресурс в обязательном порядке."
]
},
{
"cell_type": "markdown",
"id": "d671994d",
"metadata": {},
"source": [
"Например, вам нужно после открытия сокета его закрыть или закрыть обязательно какое-то соединение. Чтобы об этом не заботиться, об этом не помнить, можно использовать контекстный менеджер. Также, например, они используются при работе с транзакциями. Вам нужно обязательно либо закончить транзакцию, либо ее откатить, и вы можете определить контекстный менеджер, который будет вам управлять поведением открытия и закрытия блока кода. Чтобы определить свой контекстный менеджер, как вы могли догадаться, нужно написать свой класс с магическими методами. Эти магические методы `__enter__` и `__exit__`, которые как раз говорят о том, что происходит в начале и в конце контекстного менеджера. Давайте попробуем написать аналог контекстного менеджера стандартного для открытия файлов, назовем его `open_file`. Обратите внимание, название класса с маленькой буквы, потому что это контекстный менеджер, это не `CamelCase`."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "8b871709",
"metadata": {},
"outputs": [],
"source": [
"class open_file:\n",
" def __init__(self, filename, mode):\n",
" self.f = open(filename, mode)\n",
" \n",
" def __enter__(self):\n",
" return self.f\n",
" \n",
" def __exit__(self, *args):\n",
" self.f.close()\n"
]
},
{
"cell_type": "markdown",
"id": "43d49b39",
"metadata": {},
"source": [
"Итак, у нас контекстный менеджер используется точно так же, как и стандартный, мы вызываем `open_file`, в этот момент создается объект, то есть вызывается метод `__init__`, и мы записываем в переменную класса `f`, открытый файл с каким-то именем и открытый с каким-то `mode`'ом. Отлично. Потом эта переменная `f` записывается из метода `__enter__`. То есть из метода `__enter__` у нас возвращается что-то, если нам нужно это потом записать с помощью оператора `as`. Мы можем ничего не возвращать из `__enter__`, и тогда у нас нет смысла использовать `as`. Что логично в методе `__exit__` у нас определяется поведение, которое происходит при выходе из блока контекстного менеджера. В данном случае нам нужно закрыть обязательно файл. То есть когда у нас закончится этот блок, то есть где-то здесь, у нас произойдет закрытие файла, и вам не нужно об этом заботиться каждый раз, когда вы используете контекстный менеджер.\n",
"\n",
"Итак, мы открыли файл и записали в него какую-то строчку. Если попробовать прочитать этот файл, окажется, что она действительно там, файл у нас открылся и закрылся сам."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "3e6d969b",
"metadata": {},
"outputs": [],
"source": [
"with open_file(\"test.log\", \"w\") as f:\n",
" f.write(\"Inside `open_file` context manager\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "a983869d",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['Inside `open_file` context manager']\n"
]
}
],
"source": [
"with open_file(\"test.log\", \"r\") as f:\n",
" print(f.readlines())"
]
},
{
"cell_type": "markdown",
"id": "da15917a",
"metadata": {},
"source": [
"Это очень удобно, и вы можете определять свое собственное поведение при выходе из блока. Еще одна важная особенность контекстных менеджеров - они позволяют вам управлять исключениями, которые произошли внутри блока. Мы можем эти исключения обрабатывать и определять какое-то поведение. Например, мы можем определить контекстный менеджер `suppress_exception`, который будет работать с `exception`'ами, которые произошли внутри."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "7ef93a74",
"metadata": {},
"outputs": [],
"source": [
"class suppress_exception:\n",
" def __init__(self, exc_type):\n",
" self.exc_type = exc_type\n",
" \n",
" def __enter__(self):\n",
" return\n",
" \n",
" def __exit__(self, exc_type, exc_value, traceback):\n",
" if exc_type == self.exc_type:\n",
" print(\"Nothing happend.\")\n",
" \n",
" return True"
]
},
{
"cell_type": "markdown",
"id": "c7eeca4c",
"metadata": {},
"source": [
"Обратите внимание, в данном случае мы не используем `as`, оператор `as`, поэтому нам не важно, что возвращается из `enter`'а, просто его определили и написали `return`. Могли написать просто `pass`. Итак, у нас есть контекстный менеджер `suppress_exception`, который принимает `exception`, то есть `exc_type`, класс `exception`'а. Мы этот `exc_type` записываем и потом будем проверять, произошло ли действительно это исключение или какое-то другое. Поведение таково, что если исключение произошло от того типа, который нам интересен, мы делаем вид, что ничего не произошло. То есть мы подавляем это исключение в `suppress mode`. Мы просто выводим, что ничего не произошло, и возвращаем `true`. Нужно обязательно вернуть `true` из `exit`'а при исключении, чтобы воспроизведение кода продолжилось и `exception` не был выброшен. Таким образом, мы можем поделить на `0` и `exception` засаппрессится, ничего не произойдет."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "2fe27132",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Nothing happend.\n"
]
}
],
"source": [
"with suppress_exception(ZeroDivisionError):\n",
" really_big_number = 1 / 0"
]
},
{
"cell_type": "markdown",
"id": "fc919dcd",
"metadata": {},
"source": [
"Часто бывает это очень полезно и удобно делать. Что интересно, такой контекстный менеджер уже есть в стандартной библиотеке в `contextlib`'е, и вы можете его использовать и можете посмотреть, как он на самом деле работает внутри и окажется, что он работает примерно так же."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "73c3ecb9",
"metadata": {},
"outputs": [],
"source": [
"import contextlib\n",
"\n",
"with contextlib.suppress(ValueError):\n",
" raise ValueError"
]
},
{
"cell_type": "markdown",
"id": "08097626",
"metadata": {},
"source": [
"Давайте попробуем написать свой собственный контекстный менеджер в качестве примера. Это будет контекстный менеджер, который считает время, проведенное внутри него. То есть у нас при открытии блока что-то произошло, и при закрытии мы должны вывести время, которое произошло внутри контекстного менеджера. Давайте напишем наш класс, назовем его `timer` и определим сразу методы `__enter__` и `__exit__`, потому что именно они говорят нам о том, что это контекстный менеджер. Отлично."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "bdca5011",
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"\n",
"class timer():\n",
" def __init__(self):\n",
" self.start = time.time()\n",
" \n",
" def current_time(self):\n",
" return time.time() - self.start\n",
" \n",
" def __enter__(self):\n",
" return self\n",
" \n",
" def __exit__(self, *args):\n",
" print(f\"Elapsed: {self.current_time()}\")"
]
},
{
"cell_type": "markdown",
"id": "6abe882b",
"metadata": {},
"source": [
"Как мы будем использовать наш класс? Мы будем использовать оператор `with timer`. Потом что-то должно случиться внутри контекстного менеджера, и мы должны вывести время, в которое это все дело происходило. Итак, чтобы нам считать время, которое прошло внутри контекстного менеджера, нам нужно где-то завести переменную, которая берет начало, собственно, которая и записывает время в начале выполнения операции. Происходит это, конечно, в методе `__init__`, потому что у нас создается объект класса. И давайте с помощью встроенного модуля `time` сохраним в переменную `start` текущее время. То есть в момент инициализации, то есть вот здесь вот, когда у нас вызвался таймер, у нас запишется текущее время. В `__enter__` мы просто вернем ничего, и в `__exit__` нам интересно вывести время, которое прошло с момента начала. Для этого мы просто напишем `time`, `time` и на `self.start`. То есть выведем время, которое как раз прошло. И чтобы у нас контекстный менеджер разрешился не мгновенно, давайте напишем здесь `time.sleep` и будем спать в течение одной секунды. Отлично. Да, у нас действительно вывелось время, давайте сделаем это немного посимпатичнее, как-то так, да. Давайте попробуем модифицировать немного контекстный менеджер, чтобы что-то возвращать из `return`'а, чтобы можно было, например, нам смотреть, сколько времени прошло на текущий момент, если у нас несколько, допустим, операций `sleep`. Здесь мы хотим, например, `as t` и вывести current_time и сделать еще один time.sleep. Что нам для этого нужно сделать? Например, мы можем вернуть самого себя в `__enter__`'е и определить метод `current_time`, который будет возвращать время, прошедшее с начала выполнения. Делает он точно то же самое, `time.time self.start`. Ну и здесь можно тоже заменить тогда на `self.current_time`. Давайте отформатируем строчку, чтобы было понятно, что именно выводится у нас. Итак. Запускаем наш контекстный менеджер."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "6dd41c72",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Current: 1.0140066146850586\n",
"Elapsed: 2.028013229370117\n"
]
}
],
"source": [
"with timer() as t:\n",
" time.sleep(1)\n",
" print(f\"Current: {t.current_time()}\")\n",
" time.sleep(1)"
]
},
{
"cell_type": "markdown",
"id": "7c052354",
"metadata": {},
"source": [
"Итак, у нас вначале выводится время текущее, то есть прошла одна секунда, один `sleep`, и итоговое время две секунды с небольшим. Собственно, как мы и ожидали. Мы написали с вами контекстный менеджер, который считает время, проведенное внутри него и плюс еще позволяет вам вывести время, которое прошло с момента начала внутри самого контекстного менеджера. Итак, контекстные менеджеры позволяют вам определить поведение при входе и выходе из блока кода `with`, что часто бывает полезно, например, при закрытии каких-то ресурсов или, например, при транзакционной какой-то деятельности."
]
}
],
"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
}

@ -0,0 +1,673 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d6cf097e",
"metadata": {},
"source": [
"# Магические методы #"
]
},
{
"cell_type": "markdown",
"id": "6db9e5ec",
"metadata": {},
"source": [
"Привет. Добро пожаловать на четвёртый блок нашего с вами курса. В прошлом блоке вы разбирали объектно-ориентированное программирование на Python'е. А в этом блоке мы поподробнее познакомимся о том, как на самом деле всё работает внутри - как создаются объекты, как создаются классы, что происходит при выполнении определённых методов, и так далее.\n",
"\n",
"Давайте начнём с магических методов, с частью из которых вы уже знакомы. Итак, магический метод — это метод, определённый внутри класса, который начинается и заканчивается с двух подчёркиваний. Например, магическим методом является метод `__init__`, который отвечает за инициализацию созданного объекта. Давайте определим класс `User`, который будет переопределять магический метод `__init__`. Мы просто будем записывать полученые имя и email в атрибуты класса. Ну и можно определить, например, метод, который возвращает словарь."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "f8cc84e5",
"metadata": {},
"outputs": [],
"source": [
"class User:\n",
" def __init__(self, name, email):\n",
" self.name = name\n",
" self.email = email\n",
" \n",
" def get_email_data(self):\n",
" return {\n",
" \"name\": self.name,\n",
" \"email\": self.email\n",
" }"
]
},
{
"cell_type": "markdown",
"id": "bb1f0133",
"metadata": {},
"source": [
"Теперь при создании класса мы передадим атрибуты, которые запишутся в соответствующие значения нашего объекта. Ну и мы можем вызвать какой-то метод. С этим вы уже должны быть знакомы, потому что работали с классами уже довольно долго."
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "156d080e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'name': 'Jane Doe', 'email': 'janedoe@example.com'}\n"
]
}
],
"source": [
"jane = User(\"Jane Doe\", \"janedoe@example.com\")\n",
"print(jane.get_email_data())"
]
},
{
"cell_type": "markdown",
"id": "05b3d341",
"metadata": {},
"source": [
"Ещё одним магическим методом является метод `__new__`, который отвечает за то, что происходит в момент создания объекта класса. Метод `__new__` возвращает только что созданный объект класса и может как-то переопределять поведение при создании. Например, можно создать класс `Singleton`, который гарантирует то, что объект может быть создан только один от этого класса."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "81dbbd40",
"metadata": {},
"outputs": [],
"source": [
"class Singleton:\n",
" instance = None\n",
" \n",
" def __new__(cls):\n",
" if cls.instance is None:\n",
" cls.instance = super().__new__(cls)\n",
" \n",
" return cls.instance"
]
},
{
"cell_type": "markdown",
"id": "700b1e8e",
"metadata": {},
"source": [
"Например, мы можем попытаться создать два объекта `a` и `b`, и окажется, что это один и тот же объект, потому что мы проверяем, был ли уже создан какой-то объект, и, собственно, если он уже создан, мы не будем создавать новый объект."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "15a2b9d0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"a = Singleton()\n",
"b = Singleton()\n",
"\n",
"a is b"
]
},
{
"cell_type": "markdown",
"id": "0714f906",
"metadata": {},
"source": [
"Существует также метод `__del__`, который определяет поведение при удалении объекта. Однако, он работает не всегда очевидно. Он вызывается не когда мы удаляем объект оператором `del`, а когда количество ссылок на наш объект стало равно нулю и вызывается `garbage collector`. Это не всегда происходит тогда, когда мы думаем это должно произойти, поэтому переопределять его нежелательно. Будьте внимательны.\n",
"\n",
"Собственно, на этой лекции мы просмотрим какой-то набор магических методов, который чаще всего используется и переопределяется. Посмотрим, как они себя ведут и как писать классы, которые их используют.\n",
"\n",
"Одним из магических методов является метод `__str__`, который определяет поведение, например, при вызове функции `print`. Метод `__str__` должен определить какое-то человекочитаемое описание нашего класса, которое может вывести потом пользователь где-то, например, в интерфейсе. Классическим вариантом метода `__str__` может быть выведение имени и email."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "224f2c77",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Jane Doe <janedoe@example.com>\n"
]
}
],
"source": [
"class User:\n",
" def __init__(self, name, email):\n",
" self.name = name\n",
" self.email = email\n",
" \n",
" def __str__(self):\n",
" return f\"{self.name} <{self.email}>\"\n",
" \n",
"jane = User(\"Jane Doe\", \"janedoe@example.com\")\n",
"print(jane)"
]
},
{
"cell_type": "markdown",
"id": "506ab28e",
"metadata": {},
"source": [
"Обратите внимание, мы используем тот же самый класс, что и раньше, но теперь, если мы будем принтить наш объект, у нас будет не какой-то `object` типа `user`, а понятное и читаемое название нашего объекта.\n",
"\n",
"Ещё двумя полезными методами магическими являются методы `__hash__` и `equal`,`__eq__`, которые определяют то, как сравниваются наши объекты и что происходит при вызове функции `hash`. Магический метод `__hash__` может, например, переопределить функцию хеширования, которая используется, например, когда мы получаем ключи в словаре."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "aff3ed05",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"class User:\n",
" def __init__(self, name, email):\n",
" self.name = name\n",
" self.email = email\n",
" \n",
" def __hash__(self):\n",
" return hash(self.email)\n",
" \n",
" def __eq__(self, obj):\n",
" return self.email == obj.email\n",
" \n",
"jane = User(\"Jane Doe\", \"jdoe@example.com\")\n",
"joe = User(\"Joe Doe\", \"jdoe@example.com\")\n",
"\n",
"print(jane == joe)"
]
},
{
"cell_type": "markdown",
"id": "bed51c07",
"metadata": {},
"source": [
"В данном случае нашего класса `user` мы можем сказать, что при вызове функции `__hash__` мы хотим хешировать email, то есть у нас в качестве хеша берётся всегда имейл пользователя. Ну а при сравнении мы эти email просто сравниваем, при сравнении двух объектов. Таким образом, если мы создадим двух юзеров с разными именами, но одинаковыми имейлами, при вызове функции сравнения, то есть когда мы используем `==`, Python будет говорить нам, что это один и тот же объект, потому что вызывается метод `__eq__`, который сравнивает только email. Точно так же функция `__hash__` возвращает теперь одно и то же значение, потому что используется значение email, которое в данном случае одинаковое.\n",
"\n",
"Но если мы попробуем создать словарь, где в качестве ключа будет использоваться наш объект юзера, то у нас создастся словарь только с одним ключом, а не с двумя объектами, несмотря на то, что мы итерируемся здесь по двум объектам, потому что в качестве хеша используется одно и то же значение, и в качестве `__eq__` у нас сравниваются email."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "8c53d626",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2122083289\n",
"2122083289\n"
]
}
],
"source": [
"print(hash(jane))\n",
"print(hash(joe))"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "6ebd5adf",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{<__main__.User object at 0x038176E8>: 'Joe Doe'}\n"
]
}
],
"source": [
"user_email_map = {user: user.name for user in [jane, joe]}\n",
"\n",
"print(user_email_map)"
]
},
{
"cell_type": "markdown",
"id": "5301ba65",
"metadata": {},
"source": [
"Очень важными магическими методами являются методы, определяющие доступ к атрибутам. Это методы `__getattr__` и `__getattribute__`. Очень важно понимать между ними отличия, потому что очень часто происходит путаница. Итак, метод `__getattr__` определяет поведение, когда наш атрибут, который мы пытаемся получить, не найден. Таким образом, если мы обратимся к атрибуту какого-то объекта и у нас он будет не найден, у нас всегда вызовется метод `__getattr__` и мы можем определить какое-то поведение дефолтное при той ситуации, когда атрибута нет.\n",
"\n",
"Метод `__getattribute__` вызывается в любом случае. Когда мы обращаемся к какому-то атрибуту, у нас всегда вызывается метод `__getattribute__`, и мы можем, например, логировать какие-то обращения к атрибутам или переопределять поведение при поиске соответствующих атрибутов объекта. Например, мы можем возвращать всегда какую-то строчку и ничего не делать, как в данном случае."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "fdfe0e1e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"nope\n",
"nope\n",
"nope\n"
]
}
],
"source": [
"class Researcher:\n",
" def __getattr__(self, name):\n",
" return \"Nothing found :(\"\n",
" \n",
" def __getattribute__(self, name):\n",
" return \"nope\"\n",
" \n",
"obj = Researcher()\n",
"\n",
"print(obj.attr)\n",
"print(obj.method)\n",
"print(obj.DFG2H3J00KLL)"
]
},
{
"cell_type": "markdown",
"id": "d70a8034",
"metadata": {},
"source": [
"Мы определили класс и переопределили метод `__getattribute__`, который всегда возвращает строку и ничего дальше не делает. Таким образом, что бы мы не делали, как бы мы не пытались обращаться к атрибутам, даже которых ещё нет, как в данном случае, у нас всегда выведется эта строка. `__getattr__` работает немного по-другому. `__getattr__`, как я уже говорил, вызывается в том случае, если атрибут не найден."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "2b992fcb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Looking for attr\n",
"Nothing found :(\n",
"Looking for method\n",
"Nothing found :(\n",
"Looking for DFG2H3J00KLL\n",
"Nothing found :(\n"
]
}
],
"source": [
"class Researcher:\n",
" def __getattr__(self, name):\n",
" return \"Nothing found :(\"\n",
" \n",
" def __getattribute__(self, name):\n",
" print(f\"Looking for {name}\")\n",
" return object.__getattribute__(self, name)\n",
" \n",
"obj = Researcher()\n",
"\n",
"print(obj.attr)\n",
"print(obj.method)\n",
"print(obj.DFG2H3J00KLL)"
]
},
{
"cell_type": "markdown",
"id": "981d22e6",
"metadata": {},
"source": [
"В данном случае внутри `__getattribute__`, который вызывается всегда, мы просто логируем, что мы пытаемся найти соответствующий атрибут и продолжаем выполнение, используя класс `object`. Таким образом, если у нас пытается найтись атрибут, которого не существует, у нас вызовется `__getattr__`, что здесь и происходит. У нас ничего не найдено. Отлично."
]
},
{
"cell_type": "markdown",
"id": "aa031adb",
"metadata": {},
"source": [
"`__setattr__`, как вы могли догадаться, определяет поведение при присваивании значения к атрибуту. Например, вместо того, чтобы присвоить значение, мы можем опять же вернуть какую-то строчку и ничего не делать. В данном случае, если мы попытаемся присвоить значение атрибуту, у нас ничего не произойдёт и атрибут не создастся."
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "7b83ad70",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Not gonna set math!\n"
]
}
],
"source": [
"class Ignorant:\n",
" def __setattr__(self, name, value):\n",
" print(f\"Not gonna set {name}!\")\n",
"\n",
"obj = Ignorant()\n",
"obj.math = True"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "fb3f8c31",
"metadata": {},
"outputs": [
{
"ename": "AttributeError",
"evalue": "'Ignorant' object has no attribute 'math'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-20-f41a5f04e4f0>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmath\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mAttributeError\u001b[0m: 'Ignorant' object has no attribute 'math'"
]
}
],
"source": [
"print(obj.math)"
]
},
{
"cell_type": "markdown",
"id": "852a889f",
"metadata": {},
"source": [
"Ну а `__delattr__` управляет поведением, когда мы пытаемся удалить какой-то атрибут объекта. Мы можем не удалять, а, например, переопределить как-то поведение или добавить какую-то функциональность. Например, если мы хотим каскадно удалить какие-то объекты, связанные с нашим классом."
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "684b1176",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Goodbye attr, you were 10!\n"
]
}
],
"source": [
"class Polite:\n",
" def __delattr__(self, name):\n",
" value = getattr(self, name)\n",
" print(f\"Goodbye {name}, you were {value}!\")\n",
" object.__delattr__(self, name)\n",
"\n",
"obj = Polite()\n",
"obj.attr = 10\n",
"del obj.attr"
]
},
{
"cell_type": "markdown",
"id": "dffcc1d4",
"metadata": {},
"source": [
"В данном случае мы просто продолжаем удаление с помощью класса `object`, ну и как-то логируем то, что у нас происходит удаление.\n",
"\n",
"Ещё одним методом является метод `__call__`, который в соответствии со своим названием определяет поведение, когда наш класс вызывается. Например, с помощью метода `__call__` мы можем определить logger, который будем потом использовать в качестве декоратора. Да, декоратором может быть не только функция, но и класс."
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "f16c0418",
"metadata": {},
"outputs": [],
"source": [
"class Logger:\n",
" def __init__(self, filename):\n",
" self.filename = filename\n",
" def __call__(self, func):\n",
" with open(self.filename, \"w\") as f:\n",
" f.write(\"Oh Danny boy...\")\n",
" \n",
" return func\n",
"\n",
"logger = Logger(\"log.txt\")\n",
"\n",
"@logger\n",
"def completely_useless_function():\n",
" pass"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "2df4685b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Oh Danny boy...\n"
]
}
],
"source": [
"completely_useless_function()\n",
"\n",
"with open('log.txt') as f:\n",
" print(f.read())"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "0decbc80",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Oh Danny boy...\n"
]
}
],
"source": [
"! type log.txt"
]
},
{
"cell_type": "markdown",
"id": "c51d3828",
"metadata": {},
"source": [
"В данном случае при инициализации класса мы запоминаем `filename`, который ему передан, и каждый раз, когда мы будем вызывать наш класс, мы будем возвращать какую-то новую функцию в соответствии с протоколом декораторов и записывать значения, записывать какую-то строчку о том, что у нас наша функция вызвана. В данном случае, мы просто определяем пустую функцию, декоратор которой записывает все её вызовы. Ну и когда мы вызовем нашу функцию, у нас в нашем файле появится строка."
]
},
{
"cell_type": "markdown",
"id": "7c12bb61",
"metadata": {},
"source": [
"Классическим примером на перегрузку операторов в других языках является перегрузка оператора сложения. В данном случае за операцию сложения в Python'е отвечает оператор `__add__`. Существуют также другие операторы вроде `__sub__`, который определяет поведение при вычитании, что логично. И мы можем определить наш класс, который будет перегружать операцию сложения."
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "6e7dab94",
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"\n",
"class NoisyInt:\n",
" def __init__(self, value):\n",
" self.value = value\n",
" def __add__(self, obj):\n",
" noise = random.uniform(-1, 1)\n",
" return self.value + obj.value + noise\n",
"\n",
"a = NoisyInt(10)\n",
"b = NoisyInt(20)"
]
},
{
"cell_type": "markdown",
"id": "7b189df8",
"metadata": {},
"source": [
"В данном случае мы можем написать какой-то `NoisyInt`, который будет работать почти как `integer`, но добавлять какой-то шум, например, при сложении. Мы определим два наших Int'а со значением 10 и 20, и в операции сложение, то есть в методе `__add__` мы будем добавлять какое-то случайное значение от минус единицы до единицы. Таким образом, когда мы попытаемся сложить два наших числа, у нас выведется число в окрестности искомого, то есть у нас будет 29.5, например, вместо 30."
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "5eb86601",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"29.838970125536626\n",
"29.283700512158727\n",
"30.074755836157177\n"
]
}
],
"source": [
"for _ in range(3):\n",
" print(a + b)"
]
},
{
"cell_type": "markdown",
"id": "84513ba8",
"metadata": {},
"source": [
"Это просто пример, вы можете переопределять операцию сложения так, как вам удобно, как вам подходит для вашей задачи. \n",
"\n",
"Предлагаю вам попробовать попрактиковаться и самостоятельно написать контейнер с помощью методов `__getitem__` и `__setitem__`."
]
},
{
"cell_type": "markdown",
"id": "9bed86fa",
"metadata": {},
"source": [
"Отлично, надеюсь, у вас получилось. Ну а в качестве примера я реализовал свой собственный класс `PascalList`, который имитирует поведение списков в Паскале. Как вы знаете, в Python'e списки нумеруются с нуля, ну а в Паскале — с единицы. Мы можем переопределить методы `__getitem__` и `__setitem__` так, чтобы они работали как в Паскале. Методы `__getitem__` и `__setitem__` определяют поведение, когда мы работаем с нашим классом с помощью квадратных скобочек, обращаясь по какому-то индексу, то есть как в случае с list'ами, со списками, или в случае со словарями."
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "805d5032",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1\n"
]
}
],
"source": [
"class PascalList:\n",
" def __init__(self, original_list=None):\n",
" self.container = original_list or []\n",
" \n",
" def __getitem__(self, index):\n",
" return self.container[index - 1]\n",
" \n",
" def __setitem__(self, index, value):\n",
" self.container[index - 1] = value\n",
" \n",
" def __str__(self):\n",
" return self.container.__str__()\n",
" \n",
"numbers = PascalList([1, 2, 3, 4, 5])\n",
"print(numbers[1])"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "ff758ccd",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1, 2, 3, 4, 25]\n"
]
}
],
"source": [
"numbers[5] = 25\n",
"\n",
"print(numbers)"
]
},
{
"cell_type": "markdown",
"id": "1475fc66",
"metadata": {},
"source": [
"И в данном примере мы определяем класс `PascalList`, который принимает какой-то список, запоминает его и каждый раз, когда мы пытаемся достучаться до какого-то индекса, он обращается к этому индексу минус единица. Таким образом, если мы попытаемся взять первый элемент, у нас, на самом деле, возьмётся нулевой элемент, как в Python'e. Ну и, например, мы можем создать `PascalList` от одного до пяти, и, обратившись по первому индексу, мы получим элемент один, как и сделали бы в Паскале. Ну и, например, к пятому мы можем переопределить значение в конце.\n",
"\n",
"Собственно, на этом всё, мы с вами обсудили какой-то набор магических методов. Более полно можно посмотреть про них в документации. Их огромное количество, и они управляют поведением класса в Python'e и тем, как работают объекты, созданные от этих классов."
]
}
],
"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.8.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

@ -0,0 +1,95 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ee353f1f",
"metadata": {},
"source": [
"# Тест по методам #"
]
},
{
"cell_type": "markdown",
"id": "036697e8",
"metadata": {},
"source": [
"##### Вас зовут:\n",
"___"
]
},
{
"cell_type": "markdown",
"id": "247cf0a2",
"metadata": {},
"source": [
"##### 1. Какой метод класса возвращает новый созданный объект?\n",
"\n",
"- [ ] `__get__`\n",
"- [ ] `__new__`\n",
"- [ ] `__init__`\n",
"- [ ] `__create__`"
]
},
{
"cell_type": "markdown",
"id": "0771b868",
"metadata": {},
"source": [
"##### 2. Какой метод отвечает за происходящее в конце блока контекстного менеджера?\n",
"\n",
"- [ ] `__exit__`\n",
"- [ ] `__del__`\n",
"- [ ] `__end__`\n",
"- [ ] `__delete__`"
]
},
{
"cell_type": "markdown",
"id": "6a8d5488",
"metadata": {},
"source": [
"##### 3. Какой метод отвечает за отображении объекта при вызове функции print?\n",
"\n",
"- [ ] `__print__`\n",
"- [ ] `__read__`\n",
"- [ ] `__unicode__`\n",
"- [ ] `__str__`"
]
},
{
"cell_type": "markdown",
"id": "c9af7188",
"metadata": {},
"source": [
"##### 4. Какой метод отвечает за обращение к объекту по индексу?\n",
"\n",
"- [ ] `__getattr__`\n",
"- [ ] `__get__`\n",
"- [ ] `__getitem__`\n",
"- [ ] `__getattribute__`\n",
"- [ ] `__getindex__`"
]
}
],
"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
}

@ -0,0 +1,86 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ee353f1f",
"metadata": {},
"source": [
"# Тест по методам #"
]
},
{
"cell_type": "markdown",
"id": "247cf0a2",
"metadata": {},
"source": [
"1. Какой метод класса возвращает новый созданный объект?\n",
"\n",
"- [ ] `__get__`\n",
"- [x] `__new__`\n",
"- [ ] `__init__`\n",
"- [ ] `__create__`"
]
},
{
"cell_type": "markdown",
"id": "0771b868",
"metadata": {},
"source": [
"2. Какой метод отвечает за происходящее в конце блока контекстного менеджера?\n",
"\n",
"- [x] `__exit__`\n",
"- [ ] `__del__`\n",
"- [ ] `__end__`\n",
"- [ ] `__delete__`"
]
},
{
"cell_type": "markdown",
"id": "6a8d5488",
"metadata": {},
"source": [
"3. Какой метод отвечает за отображении объекта при вызове функции print?\n",
"\n",
"- [ ] `__print__`\n",
"- [ ] `__read__`\n",
"- [ ] `__unicode__`\n",
"- [x] `__str__`"
]
},
{
"cell_type": "markdown",
"id": "3d9e03ee",
"metadata": {},
"source": [
"4. Какой метод отвечает за обращение к объекту по индексу?\n",
"\n",
"- [ ] `__getattr__`\n",
"- [ ] `__get__`\n",
"- [x] `__getitem__`\n",
"- [ ] `__getattribute__`\n",
"- [ ] `__getindex__`"
]
}
],
"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
}

@ -0,0 +1,159 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9e725e75",
"metadata": {},
"source": [
"# Файл с магическими методами #"
]
},
{
"cell_type": "markdown",
"id": "4899791d",
"metadata": {},
"source": [
"В этом задании вам нужно создать интерфейс для работы с файлами. Класс `File` должен поддерживать несколько необычных операций.\n",
"\n",
"Класс инициализируется полным путем."
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "f6f38438",
"metadata": {},
"outputs": [],
"source": [
"from file import File"
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "285d10f8",
"metadata": {},
"outputs": [],
"source": [
"obj = File(\"/tmp/file.txt\")"
]
},
{
"cell_type": "markdown",
"id": "d5b3cceb",
"metadata": {},
"source": [
"Класс должен поддерживать метод `write`."
]
},
{
"cell_type": "code",
"execution_count": 34,
"id": "457f1257",
"metadata": {},
"outputs": [],
"source": [
"obj.write(\"line\\n\")"
]
},
{
"cell_type": "markdown",
"id": "0e5ca18a",
"metadata": {},
"source": [
"Объекты типа `File` должны поддерживать сложение."
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "aee4e554",
"metadata": {},
"outputs": [],
"source": [
"first = File(\"/tmp/first\")\n",
"second = File(\"/tmp/second\")\n",
"\n",
"new_obj = first + second"
]
},
{
"cell_type": "markdown",
"id": "370a538c",
"metadata": {},
"source": [
"В этом случае создается новый файл и файловый объект, в котором содержимое второго файла добавляется к содержимому первого файла. Новый файл должен создаваться в директории, полученной с помощью [tempfile.gettempdir](https://docs.python.org/3/library/tempfile.html \"11.6. tempfile — Generate temporary files and directories\"). Для получения нового пути можно использовать [os.path.join](https://docs.python.org/3/library/os.path.html#os.path.join \"1.2. os.path — Common pathname manipulations\").\n",
"\n",
"Объекты типа `File` должны поддерживать протокол итерации, причем итерация проходит по строкам файла."
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "8041194e",
"metadata": {},
"outputs": [],
"source": [
"for line in File(\"/tmp/file.txt\"):\n",
" ..."
]
},
{
"cell_type": "markdown",
"id": "c177c4f2",
"metadata": {},
"source": [
"И наконец, при выводе файла с помощью функции `print` должен печататься его полный путь, переданный при инициализации."
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "0bc40684",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"/tmp/file.txt\n"
]
}
],
"source": [
"obj = File(\"/tmp/file.txt\")\n",
"\n",
"print(obj)"
]
},
{
"cell_type": "markdown",
"id": "b54fb2db",
"metadata": {},
"source": [
"Опишите свой класс в скрипте."
]
}
],
"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
}

@ -0,0 +1,41 @@
"""Реализация дескриптора с комиссией"""
class Value:
"""Значение"""
def __init__(self):
"""Конструктор класса"""
self.amount: "float" = 0.0
def __get__(self, obj, obj_type) -> "float":
"""Возвращение значения"""
return self.amount
def __set__(self, obj, value):
"""Присваивание значения"""
self.amount = value - value * obj.commission
class Account:
"""Счет"""
amount: "Value" = Value()
def __init__(self, commission):
"""Конструктор класса"""
self.commission: "float" = commission
def __str__(self) -> "str":
"""Строковое представление класса"""
return f"{self.commission}"
def __repr__(self) -> "str":
"""Строковое представление класса"""
return f"Account({self.commission})"

@ -0,0 +1,90 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "88b3ed28",
"metadata": {},
"source": [
"# Дескриптор с комиссией #"
]
},
{
"cell_type": "markdown",
"id": "84b8adbb",
"metadata": {},
"source": [
"Часто при зачислении каких-то средств на счет с нас берут комиссию. Давайте реализуем похожий механизм с помощью дескрипторов. Напишите дескриптор `Value`, который будет использоваться в нашем классе `Account`.\n",
"\n",
"```python\n",
"class Account:\n",
" amount = Value()\n",
" \n",
" def __init__(self, commission):\n",
" self.commission = commission\n",
"```\n",
"\n",
"У аккаунта будет атрибут `commission`. Именно эту комиссию и нужно вычитать при присваивании значений в `amount`."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "52c62ed9",
"metadata": {},
"outputs": [],
"source": [
"from descriptor import Account"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "b297b20c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"90.0\n"
]
}
],
"source": [
"new_account = Account(0.1)\n",
"new_account.amount = 100\n",
"\n",
"print(new_account.amount)"
]
},
{
"cell_type": "markdown",
"id": "f0eac97f",
"metadata": {},
"source": [
"Опишите дескриптор в файле."
]
}
],
"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
}

@ -0,0 +1,800 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "f647bc43",
"metadata": {},
"source": [
"# Магические методы #"
]
},
{
"cell_type": "markdown",
"id": "fc2cfee7",
"metadata": {},
"source": [
"Настало время поговорить о дескрипторах. С помощью дескрипторов в Python реализована практически вся магия при работе с объектами, классами и методами.\n",
"\n",
"Чтобы определить свой собственный дескриптор, нужно определить класс и задать методы `__get__`, `__set__` или `__delete__`."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "69fc5558",
"metadata": {},
"outputs": [],
"source": [
"class Descriptor:\n",
" def __get__(self, obj, obj_type):\n",
" print(\"get\")\n",
" \n",
" def __set__(self, obj, value):\n",
" print(\"set\")\n",
" \n",
" def __delete__(self, obj):\n",
" print(\"delete\")"
]
},
{
"cell_type": "markdown",
"id": "6f32656e",
"metadata": {},
"source": [
"После этого мы можем создать какой-то новый класс и в атрибут этого класса записать объект типа дескриптор."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "22ef5e35",
"metadata": {},
"outputs": [],
"source": [
"class Class:\n",
" attr = Descriptor()"
]
},
{
"cell_type": "markdown",
"id": "56d62171",
"metadata": {},
"source": [
"Таким образом, наш атрибут будет являться дескриптором. Что это значит? У него будет переопределено поведение при доступе к атрибуту, при присваивании значений или при удалении.\n",
"\n",
"Метод `__get__`, как вы могли догадаться, определяет поведение при доступе к атрибуту. Метод `__set__` будет переопределять какое-то поведение, если мы попытаемся в наш атрибут что-то присвоить, а метод `__delete__` будет говорить о том, что будет происходить, если мы удалим наш атрибут.\n",
"\n",
"Мы создадим объект класса `Class` и посмотрим, что будет происходить при обращении к атрибуту."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "6350d18c",
"metadata": {},
"outputs": [],
"source": [
"instance = Class()"
]
},
{
"cell_type": "markdown",
"id": "e678e3f9",
"metadata": {},
"source": [
"Если мы просто попытаемся вывести наш атрибут, у нас вызовется метод `__get__`."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "1c4f3b2c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"get\n"
]
}
],
"source": [
"instance.attr"
]
},
{
"cell_type": "markdown",
"id": "e3ff5546",
"metadata": {},
"source": [
"Если мы запишем в него какое-то значение, у нас вызывается метод `__set__`."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "c8ff0399",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"set\n"
]
}
],
"source": [
"instance.attr = 10"
]
},
{
"cell_type": "markdown",
"id": "ae92ca69",
"metadata": {},
"source": [
"А если мы его удаляем, вызывается метод `__delete__`."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "38c4d579",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"delete\n"
]
}
],
"source": [
"del instance.attr"
]
},
{
"cell_type": "markdown",
"id": "be2f46c0",
"metadata": {},
"source": [
"Таким образом, Python позволяет вам переопределять поведение при доступе к атрибуту. Это очень мощная концепция, мощный механизм, который позволяет вам незаметно от пользователя определять различные поведения в ваших классах.\n",
"\n",
"Например, мы можем определить дескриптор `Value`, который будет переопределять поведение при присваивании значения в него."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "bfcc07af",
"metadata": {},
"outputs": [],
"source": [
"class Value:\n",
" def __init__(self):\n",
" self.value = None\n",
" \n",
" @staticmethod\n",
" def _prepare_value(value):\n",
" return value * 10\n",
" \n",
" def __get__(self, obj, obj_type):\n",
" return self.value\n",
" \n",
" def __set__(self, obj, value):\n",
" self.value = self._prepare_value(value)"
]
},
{
"cell_type": "markdown",
"id": "0f54defa",
"metadata": {},
"source": [
"Мы определим наш класс с атрибутом, который будет являться дескриптором, и при присваивании значений в дескриптор у нас будет происходить модифицированное поведение."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "c6e4992a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"100\n"
]
}
],
"source": [
"class Class:\n",
" attr = Value()\n",
" \n",
"instance = Class()\n",
"instance.attr = 10\n",
"\n",
"print(instance.attr)"
]
},
{
"cell_type": "markdown",
"id": "cc9892f2",
"metadata": {},
"source": [
"То есть наш метод `__set__` говорит о том, что, когда мы присваиваем значение в наш дескриптор, мы не просто сохраняем это значение, но мы как-то его препроцессим. В данном случае просто умножаем на десять. Таким образом, когда мы присваиваем десятку в наш атрибут, который является дескриптором, у нас, на самом деле, сохраняется сотня. В данном случае мы переопределили только два метода `__get__` и `__set__` этого уже достаточно для того, чтобы наш класс являлся дескриптором. Вы можете переопределить любой из трех методов, и класс уже будет являться дескриптором.\n",
"\n",
"Если у вас переопределен только метод `__get__`, то это `non-data` дескриптор, если `__set__` или `__delete__` — то это `data` дескриптор. Это говорит о том, в каком порядке они будут искаться, вызываться при поиске атрибутов."
]
},
{
"cell_type": "markdown",
"id": "fdbaee85",
"metadata": {},
"source": [
"### Напишем дескриптор, который пишет в файл все присваиваемые ему значения ###"
]
},
{
"cell_type": "markdown",
"id": "2dd70eb1",
"metadata": {},
"source": [
"Давайте, чтобы подробнее разобраться с тем, как работают дескрипторы, напишем, как всегда, свой дескриптор. И в данном случае это будет дескриптор, который будет записывать все значения, которые ему присваиваются, в файл.\n",
"\n",
"Мы можем представить, что это какая-то важная информация, которую всегда нужно сохранять. Можете это сохранять в данном случае в файл или, например, сохранять куда-нибудь на сервер, делать реплику.\n",
"\n",
"Давайте создадим наш класс `ImportantValue` и переопределим его методы `__get__`. Метод `__get__` принимает `obj` и `obj_type`, то есть объект, с которым вызвал дескриптор его тип, и метод `__set__`, который принимает объект, и значение, которое нужно присвоить. "
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "957da1db",
"metadata": {},
"outputs": [],
"source": [
"class ImportantValue:\n",
" def __get__(self, obj, obj_type):\n",
" pass\n",
" \n",
" def __set__(self, obj, value):\n",
" pass"
]
},
{
"cell_type": "markdown",
"id": "240e5f9a",
"metadata": {},
"source": [
"Таким образом, если мы создадим класс с какой-то важной информацией, например, это может быть класс `Account`, и важная информация — это, например, `amount` это какое-то, например, денежное значение, которое нам нужно всегда сохранять. И мы можем сделать это дескриптором."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "35057bd3",
"metadata": {},
"outputs": [],
"source": [
"class Account:\n",
" amount = ImportantValue()"
]
},
{
"cell_type": "markdown",
"id": "6902c993",
"metadata": {},
"source": [
"В данном случае наш `amount` является дескриптором с переопределенным поведением. Однако пока ничего не происходит в этом переопределенном поведении. Давайте это исправим. Определим метод `__init__`, и наш дескриптор должен принимать какое-то значение, которые мы должны сохранить. В данном случае он принимает `amount` и, допустим, просто его сохраняет."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "b842c9cc",
"metadata": {},
"outputs": [],
"source": [
"class ImportantValue:\n",
" def __init__(self, amount):\n",
" self.amount = amount\n",
" \n",
" def __get__(self, obj, obj_type):\n",
" pass\n",
" \n",
" def __set__(self, obj, value):\n",
" pass"
]
},
{
"cell_type": "markdown",
"id": "589335ed",
"metadata": {},
"source": [
"Пусть у нас будет 100 каких-то единиц на счету."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "e7a70ad0",
"metadata": {},
"outputs": [],
"source": [
"class Account:\n",
" amount = ImportantValue(100)"
]
},
{
"cell_type": "markdown",
"id": "3128afb5",
"metadata": {},
"source": [
"Если мы будем менять значение нашего атрибута, мы хотим все это логировать. Будем всегда логировать в один и тот же файл, просто записывать. Не забудем сохранить все-таки само значение.\n",
"\n",
"Когда мы пытаемся к нему обратиться, нужно вернуть это значение из `amount`."
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "f1c0da60",
"metadata": {},
"outputs": [],
"source": [
"class ImportantValue:\n",
" def __init__(self, amount):\n",
" self.amount = amount\n",
" \n",
" def __get__(self, obj, obj_type):\n",
" return self.amount\n",
" \n",
" def __set__(self, obj, value):\n",
" with open(\"log.txt\", \"w\") as f:\n",
" f.write(str(value))\n",
" \n",
" self.amount = value"
]
},
{
"cell_type": "markdown",
"id": "505b86ae",
"metadata": {},
"source": [
"Мы создали наш дескриптор. Давайте посмотрим, что будет происходить, когда мы попытаемся присвоить значение в `amount`. Для этого нам нужно создать объект класса `Account`. Пусть это будет `bobs_account`. И у Боба на счету какое-то количество единиц."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "da7c4d5a",
"metadata": {},
"outputs": [],
"source": [
"class Account:\n",
" amount = ImportantValue(100)\n",
" \n",
"bobs_account = Account()"
]
},
{
"cell_type": "markdown",
"id": "5947625a",
"metadata": {},
"source": [
"Таким образом, если мы попытаемся это количество единиц, количество каких-то денег изменить, то есть мы сделаем `amount` не 100, например, а 150, эти изменения должны залогироваться в файл. Давайте проверим, есть ли они там. Мы просто читаем наш файл и смотрим, что туда записалось."
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "78fb9277",
"metadata": {},
"outputs": [],
"source": [
"bobs_account.amount = 150"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "0981db7e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"150"
]
}
],
"source": [
"! cat log.txt"
]
},
{
"cell_type": "markdown",
"id": "698ed728",
"metadata": {},
"source": [
"Отлично! В нашем файле 150, потому что, когда мы присваивали значения в наш дескриптор, мы не только эти значения запоминали, но и записывали в файл. Таким образом, если мы запишем, например, 200, у нас в файле будет 200."
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "82f2fd22",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"200\n"
]
}
],
"source": [
"bobs_account.amount = 200\n",
"\n",
"with open(\"log.txt\", \"r\") as f:\n",
" print(f.read())"
]
},
{
"cell_type": "markdown",
"id": "18fbc691",
"metadata": {},
"source": [
"Мы можем не перезаписывать этот файл, а, например, дополнять его. Таким образом, у нас каждый раз, когда мы будем присваивать, файл будет изменяться. Мы можем логировать все записи в этот атрибут."
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "074c0f7b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"200150200250\n"
]
}
],
"source": [
"class ImportantValue:\n",
" def __init__(self, amount):\n",
" self.amount = amount\n",
" \n",
" def __get__(self, obj, obj_type):\n",
" return self.amount\n",
" \n",
" def __set__(self, obj, value):\n",
" with open(\"log.txt\", \"a\") as f:\n",
" f.write(str(value))\n",
" \n",
" self.amount = value\n",
" \n",
"class Account:\n",
" amount = ImportantValue(100)\n",
" \n",
"bobs_account = Account()\n",
"bobs_account.amount = 150\n",
"bobs_account.amount = 200\n",
"bobs_account.amount = 250\n",
"\n",
"with open(\"log.txt\", \"r\") as f:\n",
" print(f.read())"
]
},
{
"cell_type": "markdown",
"id": "fda9028f",
"metadata": {},
"source": [
"## Функции и методы ##"
]
},
{
"cell_type": "markdown",
"id": "b920bcd4",
"metadata": {},
"source": [
"Что ж, давайте продолжим. И на самом деле, несмотря на то, что вы пользовались функциями и методами уже довольно давно, на самом деле функции и методы реализованы с помощью дескрипторов. Чтобы понять, что это действительно так, можно попробовать обратиться к одному и тому же методу с помощью объекта и с помощью класса. И окажется, что, когда мы обращаемся к методу через точку от объекта, у нас возвращается `bound method`. То есть это метод, привязанный уже к какому-то объекту, в данном случае `object`. А если мы обращаемся к методу от `Class`, то у нас это `unbound method`. Это просто функция."
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "e68a9a79",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<bound method Class.method of <__main__.Class object at 0x7ff390566828>>\n",
"<function Class.method at 0x7ff39056e0d0>\n"
]
}
],
"source": [
"class Class:\n",
" def method(self):\n",
" pass\n",
"\n",
"obj = Class()\n",
"\n",
"print(obj.method)\n",
"print(Class.method)"
]
},
{
"cell_type": "markdown",
"id": "c3862d17",
"metadata": {},
"source": [
"Как вы видите, один и тот же метод возвращает разные объекты в зависимости от того, как к нему обращаются. Это и есть поведение дескриптора.\n",
"\n",
"Вам уже знаком декоратор `property`, который позволяет вам использовать функцию как атрибут класса. В данном случае мы можем определить `property full_name`, который на самом деле хоть и является функцией, которая возвращает строчку, используется потом так же, как и обычный атрибут, то есть без вызова скобочек. В данном случае у нас класс `User`, у нас `first_name` и `last_name`, и `full_name` возвращает, очевидно, полное имя. При вызове `full_name` от объекта у нас вызывается функция `full_name`. Однако если мы пытаемся обратиться к `full_name` от класса, у нас получится объект типа `property`. На самом деле, `property` реализовано с помощью дескрипторов, потому что разное поведение в зависимости от того, как у нас вызывается этот объект."
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "28f4c5db",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Amy Jones\n",
"<property object at 0x7ff390568908>\n"
]
}
],
"source": [
"class User:\n",
" def __init__(self, first_name, last_name):\n",
" self.first_name = first_name\n",
" self.last_name = last_name\n",
" \n",
" @property\n",
" def full_name(self):\n",
" return f\"{self.first_name} {self.last_name}\"\n",
"\n",
"amy = User(\"Amy\", \"Jones\")\n",
"\n",
"print(amy.full_name)\n",
"print(User.full_name)"
]
},
{
"cell_type": "markdown",
"id": "72f7b367",
"metadata": {},
"source": [
"И мы можем написать свой собственный класс `property`, который будет эмулировать поведение стандартного `property`. Для этого нам нужно сохранить функцию, которую `property` получает, потому что `property` — это декоратор, он получает функцию. И когда мы обращаемся к нашему объекту, если он вызван от класса, мы просто возвращаем самого себя, а если у нас вызван наш атрибут с объектом, то мы возвращаем соответствующий `getter`, вызываем функцию."
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "3083b097",
"metadata": {},
"outputs": [],
"source": [
"class Property:\n",
" def __init__(self, getter):\n",
" self.getter = getter\n",
" \n",
" def __get__(self, obj, obj_type=None):\n",
" if obj is None:\n",
" return self\n",
" \n",
" return self.getter(obj)"
]
},
{
"cell_type": "markdown",
"id": "5c7975a4",
"metadata": {},
"source": [
"Таким образом, мы можем определить класс и использовать как стандартный декоратор `property`, так и новый только что созданный. В двух видах можем просто его использовать как декоратор с помощью синтаксического сахара, можем использовать как вызов функции."
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "39ec49f2",
"metadata": {},
"outputs": [],
"source": [
"class Class:\n",
" @property\n",
" def original(self):\n",
" return 'original'\n",
" \n",
" @Property\n",
" def custom_sugar(self):\n",
" return 'custom sugar'\n",
" \n",
" def custom_pure(self):\n",
" return 'custom pure'\n",
"\n",
" custom_pure = Property(custom_pure)"
]
},
{
"cell_type": "markdown",
"id": "8508693d",
"metadata": {},
"source": [
"И окажется, что они работают идентично, потому что на самом деле `property` реализован именно с помощью дескриптора."
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "4c4aa005",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"original\n",
"custom sugar\n",
"custom pure\n"
]
}
],
"source": [
"obj = Class()\n",
"\n",
"print(obj.original)\n",
"print(obj.custom_sugar)\n",
"print(obj.custom_pure)"
]
},
{
"cell_type": "markdown",
"id": "e112eb48",
"metadata": {},
"source": [
"Точно так же реализованы `StaticMethod` и `ClassMethod`. И мы можем точно так же написать свою реализацию `ClassMethod` и `StaticMethod`. `StaticMethod` просто сохраняет функцию. И когда она вызывается, когда мы пытаемся получить соответствующий атрибут, мы просто ее возвращаем, потому что это статический метод, нам не нужно передавать туда ни `self`, ни `class`."
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "6e662007",
"metadata": {},
"outputs": [],
"source": [
"class StaticMethod:\n",
" def __init__(self, func):\n",
" self.func = func\n",
" \n",
" def __get__(self, obj, obj_type=None):\n",
" return self.func"
]
},
{
"cell_type": "markdown",
"id": "7984e41f",
"metadata": {},
"source": [
"А `ClassMethod`, когда мы вызываем нашу функцию от объекта, то есть наш `obj_type` равен нулю, равен `None`, то мы передаем соответствующий `obj_type` первым значением. Как видите, как и ожидается от `ClassMethod`. `ClassMethod` принимает первым значением `class`. Именно это и делает наша реализация `ClassMethod`."
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "1b6d514c",
"metadata": {},
"outputs": [],
"source": [
"class ClassMethod:\n",
" def __init__(self, func):\n",
" self.func = func\n",
" \n",
" def __get__(self, obj, obj_type=None):\n",
" if obj_type is None:\n",
" obj_type = type(obj)\n",
" \n",
" def new_func(*args, **kwargs):\n",
" return self.func(obj_type, *args, **kwargs)\n",
" \n",
" return new_func"
]
},
{
"cell_type": "markdown",
"id": "fbd29ba8",
"metadata": {},
"source": [
"## `__slots__` ##"
]
},
{
"cell_type": "markdown",
"id": "e948fdc7",
"metadata": {},
"source": [
"На самом деле с помощью дескрипторов в Python реализовано очень много чего, и, например, есть такая конструкция `__slots__`, которая работает тоже с помощью дескрипторов. `__slots__` позволяет вам определить класс, у которого есть жестко заданный набор атрибутов. Как вы знаете, когда мы создаем класс, у класса создается соответствующий словарь, в который мы записываем атрибуты, которые добавляются в объект. Очень часто это бывает излишне. У вас может быть огромное количество, например, объектов, и вы не хотите создавать каждый раз для каждого объекта словарь. Для этого приходит на помощь конструкция `__slots__`, которая вам позволяет жестко задать количество элементов, которые ваш класс может содержать.\n",
"\n",
"В данном случае мы говорим, что у нас в нашем классе должен быть только атрибут `anakin`. Собственно, он при инициализации и создается. Если мы попытаемся добавить в наш класс, в наш объект какой-то еще один атрибут, у нас ничего не получится, потому что у нас нет, собственно, справочника, нет `dict`, в который мы это записываем. И `__slots__` реализуется с помощью определения дескрипторов для каждого из атрибутов."
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "5b70d468",
"metadata": {},
"outputs": [
{
"ename": "AttributeError",
"evalue": "'Class' object has no attribute 'luke'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-33-d380bc2d2572>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mobj\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mClass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mluke\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"the chosen too\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mAttributeError\u001b[0m: 'Class' object has no attribute 'luke'"
]
}
],
"source": [
"class Class:\n",
" __slots__ = [\"anakin\"]\n",
" \n",
" def __init__(self):\n",
" self.anakin = \"the chosen one\"\n",
"\n",
"obj = Class()\n",
"obj.luke = \"the chosen too\""
]
},
{
"cell_type": "markdown",
"id": "b48293d5",
"metadata": {},
"source": [
"Мы познакомились с вами с дескрипторами, узнали, как они на самом деле работают, и на самом деле с помощью дескрипторов реализована практически вся магия, вся работа с методами и функциями в Python."
]
}
],
"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
}

@ -0,0 +1,42 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "357f2682",
"metadata": {},
"source": [
"# Документация #"
]
},
{
"cell_type": "markdown",
"id": "04c7dd81",
"metadata": {},
"source": [
"- [Data model](https://docs.python.org/3/reference/datamodel.html \"3. Data model\")\n",
"- [Дескрипторы](https://docs.python.org/3/howto/descriptor.html \"Descriptor HowTo Guide\")"
]
}
],
"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
}

@ -0,0 +1,508 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "90fa98ae",
"metadata": {},
"source": [
"# Метаклассы #"
]
},
{
"cell_type": "markdown",
"id": "a49613fc",
"metadata": {},
"source": [
"В этой лекции мы поговорим с вами о мета-классах. Как вы уже знаете, всё в Python'е является объектом, и классы не исключение, а значит эти классы кто-то создаёт. Давайте определим класс с названием Class и его объект. Тип нашего объекта является Class, потому что Class создал наш объект."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "341462e8",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"__main__.Class"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class Class:\n",
" ...\n",
" \n",
"obj = Class()\n",
"type(obj)"
]
},
{
"cell_type": "markdown",
"id": "5c17b22a",
"metadata": {},
"source": [
"Однако, у класса тоже есть тип. Этот тип `type`, потому что `type` создал наш класс."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "03dc5e19",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"type"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(Class)"
]
},
{
"cell_type": "markdown",
"id": "9e88bd36",
"metadata": {},
"source": [
"В данном случае `type` является мета-классом. Он создаёт другие классы. Типом самого `type`, кстати, является он же сам."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7c5cab2a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"type"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(type)"
]
},
{
"cell_type": "markdown",
"id": "27b06d18",
"metadata": {},
"source": [
"Это рекурсивное замыкание, которое реализовано с помощью С внутри. Очень важно понимать разницу между созданием и наследованием."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "fd58b7cb",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"issubclass(Class, type)"
]
},
{
"cell_type": "markdown",
"id": "75d5b828",
"metadata": {},
"source": [
"В данном случае класс не является `subclass`'ом `type`. `Type` его создаёт, но класс не наследуется от него. Класс наследуется от класса объекта."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "410a1c95",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"issubclass(Class, object)"
]
},
{
"cell_type": "markdown",
"id": "114f8dd7",
"metadata": {},
"source": [
"Чтобы понять, как вообще классы задаются, можно написать простую функцию, которая класс возвращает."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "21dc42eb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"False\n"
]
}
],
"source": [
"def dummy_factory():\n",
" class Class:\n",
" pass\n",
" \n",
" return Class\n",
"\n",
"Dummy = dummy_factory()\n",
"print(Dummy() is Dummy())"
]
},
{
"cell_type": "markdown",
"id": "d2107543",
"metadata": {},
"source": [
"В данном случае мы определяем функцию, которая возвращает какой-то новый класс — `dummy_factory`. Классы можно создавать на лету. В данном случае мы создаём два разных объекта и просто их возвращаем.\n",
"\n",
"Однако, на самом деле, Python работает, конечно, не так. Для создания классов используется мета-класс `type`, и вы можете на лету создать `type`, просто вызвав его и передав название класса."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "8186063e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class '__main__.NewClass'>\n",
"<__main__.NewClass object at 0x7f993b956d68>\n"
]
}
],
"source": [
"NewClass = type(\"NewClass\", (), {})\n",
"\n",
"print(NewClass)\n",
"print(NewClass())"
]
},
{
"cell_type": "markdown",
"id": "05936a27",
"metadata": {},
"source": [
"В данном случае мы создаём класс `NewClass` без родителей и без каких-то атрибутов. `NewClass` действительно является классическим классом. Вы можете его вывести, можете создать какой-то объект этого класса. Это настоящий класс, мы создали его на лету без литерала `class`.\n",
"\n",
"Однако, чаще всего классы создаются всё-таки по-другому. Они создаются с помощью мета-классов, и в данном случае давайте определим свой собственный мета-класс, который будет управлять поведением при создании класса."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "c6a34b73",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Creating A\n"
]
}
],
"source": [
"class Meta(type):\n",
" def __new__(cls, name, parents, attrs):\n",
" print(f\"Creating {name}\")\n",
" \n",
" if \"class_id\" not in attrs:\n",
" attrs[\"class_id\"] = name.lower()\n",
" \n",
" return super().__new__(cls, name, parents, attrs)\n",
"\n",
"class A(metaclass=Meta):\n",
" pass"
]
},
{
"cell_type": "markdown",
"id": "596e5a1e",
"metadata": {},
"source": [
"Мы определим класс `Meta`. Для того чтобы он бы мета-классом, он должен наследоваться от другого мета-класса. В данном случае это мета-класс `type`, базовый мета-класс. И, как вы уже знаете, метод `__new__` управляет поведением при создании объекта. В данном случае объектом является другой класс, поэтому мы можем изменять поведение при создании другого класса. Метод `__new__` принимает название класса, его родителей и какие-то атрибуты. Мы можем определить какой-то новый класс `A` и указать, что его мета-классом является наш мета-класс. Именно этот мета-класс и будет управлять поведением при создании нового класса. В данном случае мы выводим строчку о том, что у нас класс создаётся. При определении `class` у нас вызывается мета-класс и функция `__new__`, метод `__new__`. Мы выводим, что у нас наш класс создаётся. Записываем в какой-то атрибут нашего класса, в данном случае в атрибут `class_id`, значение."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "f83fe579",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"A.class_id: 'a'\n"
]
}
],
"source": [
"print(f\"A.class_id: '{A.class_id}'\")"
]
},
{
"cell_type": "markdown",
"id": "c17782d2",
"metadata": {},
"source": [
"Таким образом, мы можем переопределить поведение при создании класса. Например, добавить ему какой-то атрибут или сделать что-нибудь другое."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "1fa3f00a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Initializing — Base\n",
"Initializing — A\n",
"Initializing — B\n"
]
}
],
"source": [
"class Meta(type):\n",
" def __init__(cls, name, bases, attrs):\n",
" print(f\"Initializing — {name}\")\n",
" \n",
" if not hasattr(cls, \"registry\"):\n",
" cls.registry = {}\n",
" else:\n",
" cls.registry[name.lower()] = cls\n",
" \n",
" super().__init__(name, bases, attrs)\n",
"\n",
"class Base(metaclass=Meta): pass\n",
"\n",
"class A(Base): pass\n",
"\n",
"class B(Base): pass"
]
},
{
"cell_type": "markdown",
"id": "b4ba7f87",
"metadata": {},
"source": [
"Например, мы можем определить мета-класс, который переопределяет функцию `__init__`, и в данном случае наш мета-класс будет логировать, запоминать все созданные подклассы. Давайте определим функцию `__init__`, которая будет вызываться при инициализации нашего объекта. В данном случае нашим объектом является класс. При инициализации класса у нас она будет вызываться. Наш `__init__` принимает те же самые аргументы, однако, делает немного другое. Он записывает свой собственный атрибут значения созданных классов. В данном случае, у нас вначале создаётся класс `Base`, мета-классом которого является `Meta`, и у него создаётся `registry`, в который мы потом будем записывать все его подклассы. Каждый раз, когда у нас создаётся какой-то класс, который наследуется от `Base`, мы записываем в наш `registry` соответствующее значение, то есть название созданного класса и ссылку на него, то есть объект `class`. И мы можем вывести теперь все подклассы нашего `Base`, просто обратившись к `registry`, ну или написав обращение к методу `subclasses`."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "d4c2e1a5",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'a': <class '__main__.A'>, 'b': <class '__main__.B'>}\n",
"[<class '__main__.A'>, <class '__main__.B'>]\n"
]
}
],
"source": [
"print(Base.registry)\n",
"print(Base.__subclasses__())"
]
},
{
"cell_type": "markdown",
"id": "b9aae82f",
"metadata": {},
"source": [
"## Абстрактные методы ##"
]
},
{
"cell_type": "markdown",
"id": "2245e2a3",
"metadata": {},
"source": [
"Очень часто при работе с объектно-ориентированной парадигмой в Python'е возникают вопросы про абстрактные методы, потому что абстрактные методы являются центральным понятием, например, в языке программирования C++. В Python'е есть абстрактные методы, вы можете их использовать с помощью стандартной библиотеки `abc`.\n",
"\n",
"В данном случае здесь также работают мета-классы и мы можем определить абстрактный какой-то класс с методом `abstractmethod`."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "a7a09dc8",
"metadata": {},
"outputs": [],
"source": [
"from abc import ABCMeta, abstractmethod\n",
"\n",
"class Sender(metaclass=ABCMeta):\n",
" @abstractmethod\n",
" def send(self):\n",
" \"\"\"Do something\"\"\""
]
},
{
"cell_type": "markdown",
"id": "49c0d57b",
"metadata": {},
"source": [
"О чём говорит наш декоратор `abstractmethod`? Что у нас не получится создать какой-то класс, не определив этот метод. То есть у нас метод абстрактный и мы обязаны его переопределить в классе, который наследуется от нашего класса. В данном случае у нас `Child` не переопределяет метод `send`, и поэтому вызывается ошибка."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "05e82b55",
"metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "Can't instantiate abstract class Child with abstract methods send",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-15-a03e0b46bd6a>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mclass\u001b[0m \u001b[0mChild\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mSender\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mChild\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: Can't instantiate abstract class Child with abstract methods send"
]
}
],
"source": [
"class Child(Sender): pass\n",
"\n",
"Child()"
]
},
{
"cell_type": "markdown",
"id": "3bdd199c",
"metadata": {},
"source": [
"Если мы переопределим метод `send`, то всё будет, как мы хотели, у нас абстрактный метод переопределён, всё работает."
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "c0599a42",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<__main__.Child at 0x7f993b90fef0>"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class Child(Sender):\n",
" def send(self):\n",
" print(\"Sending\")\n",
"\n",
"Child()"
]
},
{
"cell_type": "markdown",
"id": "6ff82354",
"metadata": {},
"source": [
"Ну и, на самом деле, абстрактные методы используются в Python'е довольно редко, чаще всего вызывается просто исключение `NotImplementedError`, которое говорит о том, что этот метод нужно реализовать. Программист, когда видит в определении класса, что вызывается в методе `raise NotImplementedError`, что этот класс нужно в потомке переопределить."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "a7ef9b3f",
"metadata": {},
"outputs": [],
"source": [
"class PythonWay:\n",
" def send(self):\n",
" raise NotImplementedError"
]
},
{
"cell_type": "markdown",
"id": "66529f93",
"metadata": {},
"source": [
"Что ж, мы с вами разобрали, как работают мета-классы. На самом деле мета-класс — это просто класс, который создаёт другие классы, потому что классы тоже являются объектом. Мы посмотрели, как можно написать свой собственный мета-класс и переопределить поведение при создании другого класса. Также вы можете определять собственные абстрактные методы, если вам необходимо сказать о том, что в классе, который наследуется от вашего другого класса, обязательно должен реализован быть какой-то метод, какой-то интерфейс. На этом всё."
]
}
],
"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
}

@ -0,0 +1,39 @@
import requests
class Asteroid:
BASE_API_URL = "https://api.nasa.gov/neo/rest/v1/neo/{}?api_key=DEMO_KEY"
def __init__(self, spk_id):
self.api_url = self.BASE_API_URL.format(spk_id)
def get_data(self):
return requests.get(self.api_url).json()
@property
def name(self):
return self.get_data()["name"]
@property
def diameter(self):
return int(
self.get_data()["estimated_diameter"]["meters"][
"estimated_diameter_max"
]
)
@property
def closest_approach(self):
closest = {"date": None, "distance": float("inf")}
for approach in self.get_data()["close_approach_data"]:
distance = float(approach["miss_distance"]["lunar"])
if distance < closest["distance"]:
closest.update(
{
"date": approach["close_approach_date"],
"distance": distance,
}
)
return closest

@ -0,0 +1,23 @@
import json
import unittest
from unittest.mock import patch
from asteroid import Asteroid
class TestAsteroid(unittest.TestCase):
def setUp(self):
self.asteroid = Asteroid(2099942)
def mocked_get_data(self):
with open("apophis_fixture.txt") as f:
return json.loads(f.read())
@patch("asteroid.Asteroid.get_data", mocked_get_data)
def test_name(self):
self.assertEqual(
self.asteroid.name, "99942 Apophis (2004 MN4)"
)
@patch("asteroid.Asteroid.get_data", mocked_get_data)
def test_diameter(self):
self.assertEqual(self.asteroid.diameter, 682)

@ -0,0 +1,5 @@
import unittest
class TestDivision(unittest.TestCase):
def test_integer_division(self):
self.assertIs(10 / 5, 2)

@ -0,0 +1,11 @@
import unittest
class TestPython(unittest.TestCase):
def test_float_to_int_coercion(self):
self.assertEqual(1, int(1.0))
def test_get_empty_dict(self):
self.assertIsNone({}.get("key"))
def test_trueness(self):
self.assertTrue(bool(10))

@ -0,0 +1,18 @@
import re
import requests
def main(site_url, substring):
site_code = get_site_code(site_url)
matching_substrings = get_matching_substrings(site_code, substring)
print(f"'{substring}' found {len(matching_substrings)} times in {site_url}")
def get_site_code(site_url):
if not site_url.startswith("http"):
site_url = "http://" + site_url
return requests.get(site_url).text
def get_matching_substrings(source, substring):
return re.findall(substring, source)
main("vniitf.ru", "python")

@ -0,0 +1,44 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "5f13d398",
"metadata": {},
"source": [
"# Документация #"
]
},
{
"cell_type": "markdown",
"id": "30ab5463",
"metadata": {},
"source": [
"- [pdb](https://docs.python.org/3/library/pdb.html \"27.3. pdb — The Python Debugger\")\n",
"- [unittest](https://docs.python.org/3/library/unittest.html \"26.4. unittest — Unit testing framework\")\n",
"- [unittest.mock](https://docs.python.org/3/library/unittest.mock.html \"26.5. unittest.mock — mock object library\")\n",
"- [unittest.mock examples](https://docs.python.org/3/library/unittest.mock-examples.html \"26.6. unittest.mock — getting started\")"
]
}
],
"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
}

@ -0,0 +1,615 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a85683d0",
"metadata": {},
"source": [
"# Отладка #"
]
},
{
"cell_type": "markdown",
"id": "696f9c7a",
"metadata": {},
"source": [
"Привет. На этой лекции мы с вами поговорим про отладку в Python. Скорее всего отладкой вы уже занимались, когда пытались выяснить, почему ваша программа не работает или работает некорректно. Если вы программируете в IDE, скорее всего, там уже есть инструментарий для отладки вашего кода, и вы можете запускать вашу программу под отладчиком, ставить какие-то брейкпоинты, следить за переменными, и так далее. Мы с вами разберём классический механизм отладки с помощью Python Debugger'а и делать мы это будем на примере."
]
},
{
"cell_type": "markdown",
"id": "7a9e869b",
"metadata": {},
"source": [
"Мы напишем программу, которая принимает на вход сайт, URL сайта и какую-то строчку, и ищет в коде сайта эту строчку и считает, сколько раз там встретилась эта строка. Давайте напишем нашу программу и попробуем её отладить."
]
},
{
"cell_type": "markdown",
"id": "ec880120",
"metadata": {},
"source": [
"Определим функцию `main`, которая принимает `site_url` и `substring`, и давайте получим вначале `site_code` с помощью функции `get_site_code`, которой передадим `site_url`."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "0f852142",
"metadata": {},
"outputs": [],
"source": [
"def main(site_url, substring):\n",
" site_code = get_site_code(site_url)"
]
},
{
"cell_type": "markdown",
"id": "463acf3b",
"metadata": {},
"source": [
"Давайте определим ниже функцию `get_site_code`, которая принимает `site_url`, и чтобы получить `site_code` мы можем воспользоваться уже знакомой вам библиотекой `requests` и передать туда `site_url` и вернуть текст.\n",
"\n",
"Давайте ещё проверим, что нам, допустим, передан корректный `site_url`. Здесь у нас с этого начинается, со схемы, мы эту схему добавим. Простая проверка, почему бы и нет?"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "edb2cff5",
"metadata": {},
"outputs": [],
"source": [
"def get_site_code(site_url):\n",
" if not site_url.startswith(\"http\"):\n",
" site_url = \"http://\" + site_url\n",
" \n",
" return requests.get(site_url).text"
]
},
{
"cell_type": "markdown",
"id": "27c8dac9",
"metadata": {},
"source": [
"Итак, у нас есть наш `site_code`, осталось импортировать `requests`, чтоб всё работало, теперь нам нужно получить все подстроки, которые мы нашли."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "a488e462",
"metadata": {},
"outputs": [],
"source": [
"import requests"
]
},
{
"cell_type": "markdown",
"id": "8b3e4b8c",
"metadata": {},
"source": [
"Давайте назовем переменнную `matching_substrings` и вызовем функцию `get_matching_substrings`, которой передадим `site_code` и `substring`. Отлично."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "257bb19e",
"metadata": {},
"outputs": [],
"source": [
"def main(site_url, substring):\n",
" site_code = get_site_code(site_url)\n",
" matching_substrings = get_matching_substrings(site_code, substring)"
]
},
{
"cell_type": "markdown",
"id": "34e4025a",
"metadata": {},
"source": [
"Определим чуть ниже эту самую функцию `get_matching_substrings`. Давайте, пусть у нас переменные будут называться `source` и `substring`, потому что мы можем использовать эту функцию в разных местах, не только здесь. Для того чтобы найти вхождения подстроки в строку мы воспользуемся регулярными выражениями стандартной библиотеки, модулем `re`, используем функцию `findall` и передадим в неё `source` и `substring`. Таким образом, мы будeм искать не только подстроки, но можем искать, например, по какому-то регулярному выражению."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "89950f89",
"metadata": {},
"outputs": [],
"source": [
"def get_matching_substrings(source, substring):\n",
" return re.findall(source, substring)"
]
},
{
"cell_type": "markdown",
"id": "9d62a32a",
"metadata": {},
"source": [
"Давайте импортируем `re` для того, чтобы всё работало. Отлично."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "7bc159b2",
"metadata": {},
"outputs": [],
"source": [
"import re"
]
},
{
"cell_type": "markdown",
"id": "302a5e85",
"metadata": {},
"source": [
"Мы получили подстроки и нам нужно их посчитать. Давайте выведем сразу наш результат и мы скажем, что мы нашли подстроку сколько-то раз в таком-то сайте. Давайте отформатируем. Форматируем. Напишем `substring`, найдена она ровно `len(matching_substring)` раз `site_url`. Отлично!"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "c01187fc",
"metadata": {},
"outputs": [],
"source": [
"def main(site_url, substring):\n",
" site_code = get_site_code(site_url)\n",
" matching_substrings = get_matching_substrings(site_code, substring)\n",
" print(f\"'{substring}' found {len(matching_substrings)} times in {site_url}\")"
]
},
{
"cell_type": "markdown",
"id": "dc340754",
"metadata": {},
"source": [
"Это и будет результатом. И давайте вызовем нашу функцию. В этот момент уже начнётся процесс отладки, потому что скорее всего сразу она не заработает. И, например, не заработает она потому, что мы не передали в функцию main параметра."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "277aa27d",
"metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "main() missing 2 required positional arguments: 'site_url' and 'substring'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-8-263240bbee7e>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: main() missing 2 required positional arguments: 'site_url' and 'substring'"
]
}
],
"source": [
"main()"
]
},
{
"cell_type": "markdown",
"id": "ce93873e",
"metadata": {},
"source": [
"Итак, процесс отладки уже начат, мы смотрим на исключения, читаем, что нам исключение это говорит. Давайте исправим это, передадим туда сайт vniitf.ru и python, допустим, поскольку python будем искать в сайте vniitf.ru. Давайте запустим и посмотрим, что у нас не работает дальше. Итак, у нас какая-то проблема в `get_matching_substrings` очевидно."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "3a63069d",
"metadata": {},
"outputs": [
{
"ename": "error",
"evalue": "bad character range 0-% at position 20016 (line 412, column 353)",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31merror\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-9-558214cc4502>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"sinfo\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"python\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<ipython-input-7-7cd9a99164ff>\u001b[0m in \u001b[0;36mmain\u001b[0;34m(site_url, substring)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m<ipython-input-5-cc21a11c7363>\u001b[0m in \u001b[0;36mget_matching_substrings\u001b[0;34m(source, substring)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mre\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m/usr/lib64/python3.6/re.py\u001b[0m in \u001b[0;36mfindall\u001b[0;34m(pattern, string, flags)\u001b[0m\n\u001b[1;32m 220\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 221\u001b[0m Empty matches are included in the result.\"\"\"\n\u001b[0;32m--> 222\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_compile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 223\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 224\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfinditer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib64/python3.6/re.py\u001b[0m in \u001b[0;36m_compile\u001b[0;34m(pattern, flags)\u001b[0m\n\u001b[1;32m 299\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0msre_compile\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misstring\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 300\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"first argument must be string or compiled pattern\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 301\u001b[0;31m \u001b[0mp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msre_compile\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcompile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 302\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mflags\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0mDEBUG\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 303\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_cache\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m>=\u001b[0m \u001b[0m_MAXCACHE\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib64/python3.6/sre_compile.py\u001b[0m in \u001b[0;36mcompile\u001b[0;34m(p, flags)\u001b[0m\n\u001b[1;32m 560\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misstring\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mp\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 561\u001b[0m \u001b[0mpattern\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 562\u001b[0;31m \u001b[0mp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msre_parse\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mparse\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 563\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 564\u001b[0m \u001b[0mpattern\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib64/python3.6/sre_parse.py\u001b[0m in \u001b[0;36mparse\u001b[0;34m(str, flags, pattern)\u001b[0m\n\u001b[1;32m 853\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 854\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 855\u001b[0;31m \u001b[0mp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_parse_sub\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0mSRE_FLAG_VERBOSE\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 856\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mVerbose\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 857\u001b[0m \u001b[0;31m# the VERBOSE flag was switched on inside the pattern. to be\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib64/python3.6/sre_parse.py\u001b[0m in \u001b[0;36m_parse_sub\u001b[0;34m(source, state, verbose, nested)\u001b[0m\n\u001b[1;32m 414\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 415\u001b[0m itemsappend(_parse(source, state, verbose, nested + 1,\n\u001b[0;32m--> 416\u001b[0;31m not nested and not items))\n\u001b[0m\u001b[1;32m 417\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0msourcematch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"|\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 418\u001b[0m \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib64/python3.6/sre_parse.py\u001b[0m in \u001b[0;36m_parse\u001b[0;34m(source, state, verbose, nested, first)\u001b[0m\n\u001b[1;32m 551\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhi\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0mlo\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 552\u001b[0m \u001b[0mmsg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"bad character range %s-%s\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mthis\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mthat\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 553\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0msource\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0merror\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mthis\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mthat\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 554\u001b[0m \u001b[0msetappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mRANGE\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mlo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 555\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31merror\u001b[0m: bad character range 0-% at position 20016 (line 412, column 353)"
]
}
],
"source": [
"main(\"vniitf.ru\", \"python\")"
]
},
{
"cell_type": "markdown",
"id": "2457b754",
"metadata": {},
"source": [
"Совершенно непонятно, что же случилось. По этой ошибке нельзя однозначно сказать, почему наша программа не работает. В данном случае на помощь нам может прийти отладчик. Опять же простейшая отладка заключается в том, что мы смотрим на исключения или на ошибку и, допустим, выводим какие-то переменные, смотрим, что они корректные или некорректные. Можно использовать отладчик. Для того чтобы запустить отладчик в нашей программе, мы можем импортировать `pdb` и сказать, что, пожалуйста, запусти отладку в этом месте. `pdb.set_trace` — это стандартный способ, импорт прямо здесь, `set_trace` прямо здесь, и давайте запустим нашу программу под отладчиком."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "21d4d55d",
"metadata": {},
"outputs": [],
"source": [
"def main(site_url, substring):\n",
" import pdb\n",
" pdb.set_trace()\n",
" \n",
" site_code = get_site_code(site_url)\n",
" matching_substrings = get_matching_substrings(site_code, substring)\n",
" print(f\"'{substring}' found {len(matching_substrings)} times in {site_url}\")"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "894621f2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"> \u001b[0;32m<ipython-input-10-dd0d3292f80f>\u001b[0m(5)\u001b[0;36mmain\u001b[0;34m()\u001b[0m\n",
"\u001b[0;32m 3 \u001b[0;31m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m----> 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 6 \u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 7 \u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\n",
"ipdb> ll\n",
"\u001b[1;32m 1 \u001b[0m\u001b[0;32mdef\u001b[0m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 2 \u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 3 \u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 4 \u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m----> 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[1;32m 6 \u001b[0m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 7 \u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\n",
"ipdb> help\n",
"\n",
"Documented commands (type help <topic>):\n",
"========================================\n",
"EOF cl disable interact next psource rv undisplay\n",
"a clear display j p q s unt \n",
"alias commands down jump pdef quit skip_hidden until \n",
"args condition enable l pdoc r source up \n",
"b cont exit list pfile restart step w \n",
"break continue h ll pinfo return tbreak whatis \n",
"bt d help longlist pinfo2 retval u where \n",
"c debug ignore n pp run unalias \n",
"\n",
"Miscellaneous help topics:\n",
"==========================\n",
"exec pdb\n",
"\n",
"ipdb> ? p\n",
"p expression\n",
" Print the value of the expression.\n",
"ipdb> args\n",
"site_url = 'sinfo'\n",
"substring = 'python'\n",
"ipdb> ll\n",
"\u001b[1;32m 1 \u001b[0m\u001b[0;32mdef\u001b[0m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 2 \u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 3 \u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 4 \u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m----> 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[1;32m 6 \u001b[0m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 7 \u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\n",
"ipdb> next\n",
"> \u001b[0;32m<ipython-input-10-dd0d3292f80f>\u001b[0m(6)\u001b[0;36mmain\u001b[0;34m()\u001b[0m\n",
"\u001b[0;32m 3 \u001b[0;31m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m----> 6 \u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 7 \u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\n",
"ipdb> step\n",
"--Call--\n",
"> \u001b[0;32m<ipython-input-5-cc21a11c7363>\u001b[0m(1)\u001b[0;36mget_matching_substrings\u001b[0;34m()\u001b[0m\n",
"\u001b[0;32m----> 1 \u001b[0;31m\u001b[0;32mdef\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 2 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mre\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\n",
"ipdb> n\n",
"> \u001b[0;32m<ipython-input-5-cc21a11c7363>\u001b[0m(2)\u001b[0;36mget_matching_substrings\u001b[0;34m()\u001b[0m\n",
"\u001b[0;32m 1 \u001b[0;31m\u001b[0;32mdef\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m----> 2 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mre\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\n",
"ipdb> s\n",
"--Call--\n",
"> \u001b[0;32m/usr/lib64/python3.6/re.py\u001b[0m(214)\u001b[0;36mfindall\u001b[0;34m()\u001b[0m\n",
"\u001b[0;32m 212 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_compile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmaxsplit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 213 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m--> 214 \u001b[0;31m\u001b[0;32mdef\u001b[0m \u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 215 \u001b[0;31m \"\"\"Return a list of all non-overlapping matches in the string.\n",
"\u001b[0m\u001b[0;32m 216 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\n",
"ipdb> q\n"
]
},
{
"ename": "BdbQuit",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mBdbQuit\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-11-558214cc4502>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"sinfo\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"python\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<ipython-input-10-dd0d3292f80f>\u001b[0m in \u001b[0;36mmain\u001b[0;34m(site_url, substring)\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m<ipython-input-5-cc21a11c7363>\u001b[0m in \u001b[0;36mget_matching_substrings\u001b[0;34m(source, substring)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mre\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m/usr/lib64/python3.6/re.py\u001b[0m in \u001b[0;36mfindall\u001b[0;34m(pattern, string, flags)\u001b[0m\n\u001b[1;32m 212\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0m_compile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmaxsplit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 213\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 214\u001b[0;31m \u001b[0;32mdef\u001b[0m \u001b[0mfindall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpattern\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflags\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 215\u001b[0m \"\"\"Return a list of all non-overlapping matches in the string.\n\u001b[1;32m 216\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib64/python3.6/bdb.py\u001b[0m in \u001b[0;36mtrace_dispatch\u001b[0;34m(self, frame, event, arg)\u001b[0m\n\u001b[1;32m 51\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_line\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 52\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevent\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'call'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 53\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 54\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevent\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'return'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_return\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib64/python3.6/bdb.py\u001b[0m in \u001b[0;36mdispatch_call\u001b[0;34m(self, frame, arg)\u001b[0m\n\u001b[1;32m 84\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrace_dispatch\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 85\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0muser_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 86\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mquitting\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mBdbQuit\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 87\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrace_dispatch\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 88\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mBdbQuit\u001b[0m: "
]
}
],
"source": [
"main(\"vniitf.ru\", \"python\")"
]
},
{
"cell_type": "markdown",
"id": "997970f2",
"metadata": {},
"source": [
"Итак, мы запустили программу, однако, она не дошла до ошибки, она остановилась именно в том месте, где мы наш отладчик определили. Мы можем посмотреть, где это находится, с помощью команды `long list`. Итак, мы находимся ровно за тем местом, где наш отладчик определён и смотрим на функцию `get_site_code`.\n",
"\n",
"У отладчика довольно много команд, их можно посмотреть с помощью функции `help`. Функция `help` выводит все команды, мы можем также написать знак вопроса вместо функции `help` и, например, мы можем посмотреть про какую-то конкретную команду, что она делает.\n",
"\n",
"Например, давайте посмотрим, что делает команда `p`. Команда `p` — это сокращение от `print`'а и она выводит просто выражение. Мы можем вывести какую-то переменную, которая находится в области нашей видимости. Есть полезная команда `args`, которая говорит о том, какие аргументы переданы функции, в которой мы находимся. Сейчас мы находимся в функции `main` и аргументы переданы правильно и всё корректно. Итак, дальше у нас есть несколько опций. Мы остановились в отладчике и мы можем продолжать как-то наше исполнение кода под отладчиком. Мы можем написать `continue` или `c` и продолжить исполнение дальше до конца, пока у нас не закончится программа или не упадёт исключение или не появится какой-то брейкпоинт.\n",
"\n",
"Однако, нам интересно отлаживать, поэтому это не вариант. Дальше, у нас есть несколько опций. Мы можем написать `step` и провалиться внутрь функции, однако у нас ошибка не внутри функции, а дальше, поэтому мы напишем `next` и перейдём на следующую строку. Следующая строка — это вызов функции `get_matching_substrings`, куда нам и нужно. Чтобы пройти внутрь функции, не пропуская её, мы можем написать `step` и `less` и оказаться внутри функции внутри функции `get_matching_substrings`. Итак, давайте пойдём дальше, и дальше у нас вызов функции `findall`. Именно там и была ошибка. Давайте посмотрим, что же у нас происходит. Итак, мы в `findall` передаём `source` и `substring` и пытаемся найти `substring` в `source`. Давайте зайдём в `findall`, которая определена в стандартной библиотеке и посмотрим, что же там происходит. Итак, на самом деле, внимательный читатель уже заметит, в чём была ошибка, если мы посмотрим на определение `findall`. `findall` принимает вначале паттерн, а потом строку, и ищет этот паттерн в строке, а мы передаём ровно наоборот. Именно это и является ошибкой. Давайте выйдем или продолжим исполнение. Давайте выйдем. Чтобы выйти, нужно написать в `query quit`, и мы просто выйдем из `debugger`'а по исключению.\n",
"\n",
"Итак, давайте поменяем местами и посмотрим, неужели это и была ошибка наша."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "b6bbf6c6",
"metadata": {},
"outputs": [],
"source": [
"def get_matching_substrings(source, substring):\n",
" return re.findall(substring, source)"
]
},
{
"cell_type": "markdown",
"id": "c3d3f48a",
"metadata": {},
"source": [
"Давайте запустим ещё с отладчиком, и я покажу вам ещё один момент, который бывает полезным. Часто вам необходимо отлаживать не в одном месте, а вы хотите, например, продолжить исполнение до какого-то определённого момента. На помощь нам могут прийти брейкпоинты, которые, например, в IDE ставятся очень удобно."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "a5280d4a",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"> \u001b[0;32m<ipython-input-10-dd0d3292f80f>\u001b[0m(5)\u001b[0;36mmain\u001b[0;34m()\u001b[0m\n",
"\u001b[0;32m 3 \u001b[0;31m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m----> 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 6 \u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 7 \u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\n",
"ipdb> ll\n",
"\u001b[1;32m 1 \u001b[0m\u001b[0;32mdef\u001b[0m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 2 \u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 3 \u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 4 \u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m----> 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[1;32m 6 \u001b[0m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[1;32m 7 \u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\n",
"ipdb> b\n",
"ipdb> b 7\n",
"Breakpoint 1 at <ipython-input-10-dd0d3292f80f>:7\n",
"ipdb> cont\n",
"> \u001b[0;32m<ipython-input-10-dd0d3292f80f>\u001b[0m(7)\u001b[0;36mmain\u001b[0;34m()\u001b[0m\n",
"\u001b[0;32m 3 \u001b[0;31m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 5 \u001b[0;31m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[0;32m 6 \u001b[0;31m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\u001b[1;31m1\u001b[0;32m---> 7 \u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0m\n",
"ipdb> q\n"
]
},
{
"ename": "BdbQuit",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mBdbQuit\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-14-558214cc4502>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"sinfo\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"python\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<ipython-input-10-dd0d3292f80f>\u001b[0m in \u001b[0;36mmain\u001b[0;34m(site_url, substring)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<ipython-input-10-dd0d3292f80f>\u001b[0m in \u001b[0;36mmain\u001b[0;34m(site_url, substring)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0msite_code\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_site_code\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mmatching_substrings\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_matching_substrings\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msite_code\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msubstring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"'{substring}' found {len(matching_substrings)} times in {site_url}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m/usr/lib64/python3.6/bdb.py\u001b[0m in \u001b[0;36mtrace_dispatch\u001b[0;34m(self, frame, event, arg)\u001b[0m\n\u001b[1;32m 49\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;31m# None\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 50\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevent\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'line'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 51\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_line\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 52\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevent\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'call'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 53\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib64/python3.6/bdb.py\u001b[0m in \u001b[0;36mdispatch_line\u001b[0;34m(self, frame)\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstop_here\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbreak_here\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0muser_line\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 70\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mquitting\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mBdbQuit\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 71\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrace_dispatch\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 72\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mBdbQuit\u001b[0m: "
]
}
],
"source": [
"main(\"vniitf.ru\", \"python\")"
]
},
{
"cell_type": "markdown",
"id": "9707481e",
"metadata": {},
"source": [
"Здесь можно воспользоваться командой `b`, которая, если просто напишем `b`, она выводит все брейкопоинты, которые мы уже определили. Если мы напишем `b` с номером строки, например, `b 7`, мы поставим брейкопоинт на десятую строку. И когда исполнение интерпретатором программы дойдёт до этой строки, он остановится. Мы можем написать `continue` и остановиться ровно на нашем брейкпоинте. Давайте выйдем, уберём `debugger` и проверим, корректно ли работает наша программа."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "320d9e39",
"metadata": {},
"outputs": [],
"source": [
"def main(site_url, substring): \n",
" site_code = get_site_code(site_url)\n",
" matching_substrings = get_matching_substrings(site_code, substring)\n",
" print(f\"'{substring}' found {len(matching_substrings)} times in {site_url}\")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "f79af11d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"'python' found 19 times in sinfo\n"
]
}
],
"source": [
"main(\"vniitf.ru\", \"python\")"
]
},
{
"cell_type": "markdown",
"id": "0dacbb93",
"metadata": {},
"source": [
"Мы нашли 19 раза в `site_code` vniitf.ru слово python."
]
},
{
"cell_type": "markdown",
"id": "3a274579",
"metadata": {},
"source": [
"Отлично, мы отладили нашу программу и посмотрели, почему она не работает с помощью `debugger`'а. Точно так же из `debugger`'а можно работать не в Jupyter ноутбуке, а в консоли, и можно запускать программу под отладкой с помощью такого механизма."
]
},
{
"cell_type": "markdown",
"id": "2051dc05",
"metadata": {},
"source": [
"Код нашей программы в данном случае называется `wc_web`, и мы можем запустить его под отладчиком. Здесь происходит всё то же самое, мы можем выполнять все те же самые команды и ставить брейкпоинты на какие-то места. Например, мы можем поставить брейкпоинт на шестую строку, продолжить исполнение, дойти до шестой строки, посмотреть аргументы, и так далее."
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "5550ac58",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"> /home/mikhaylovaf/projects/python/4. Углубленный Python/3. Отладка и тестирование/wc_web.py(1)<module>()\n",
"-> import re\n",
"(Pdb) \n",
"--KeyboardInterrupt--\n",
"(Pdb) "
]
}
],
"source": [
"! python -m pdb wc_web.py"
]
},
{
"cell_type": "markdown",
"id": "a2d4ee94",
"metadata": {},
"source": [
"Итак, мы с вами разобрали отладку в Python'е и теперь можем отлаживать наши программы и выяснять, почему они не работают. Удачи."
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "7ee7851b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"'python' found 19 times in sinfo\n"
]
}
],
"source": [
"import re\n",
"import requests\n",
"\n",
"def main(site_url, substring):\n",
" site_code = get_site_code(site_url)\n",
" matching_substrings = get_matching_substrings(site_code, substring)\n",
" print(f\"'{substring}' found {len(matching_substrings)} times in {site_url}\")\n",
" \n",
"def get_site_code(site_url):\n",
" if not site_url.startswith(\"http\"):\n",
" site_url = \"http://\" + site_url\n",
" \n",
" return requests.get(site_url).text\n",
"\n",
"def get_matching_substrings(source, substring):\n",
" return re.findall(substring, source)\n",
"\n",
"main(\"vniitf.ru\", \"python\")"
]
}
],
"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
}

@ -0,0 +1,129 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "400513aa",
"metadata": {},
"source": [
"# Тест по блоку #"
]
},
{
"cell_type": "markdown",
"id": "5f26f612",
"metadata": {},
"source": [
"##### Вас зовут:\n",
"___"
]
},
{
"cell_type": "markdown",
"id": "de522557",
"metadata": {},
"source": [
"##### 1. Для чего нужны контекстные менеджеры?\n",
"\n",
" - [ ] Они управляют переключением контекста между модулями\n",
" - [ ] Они используются для определения логики в начале и конце блока кода\n",
" - [ ] Они управляют переключением контекста между функциями"
]
},
{
"cell_type": "markdown",
"id": "ca2f711d",
"metadata": {},
"source": [
"##### 2. Что такое дескриптор?\n",
"\n",
" - [ ] Описание класса\n",
" - [ ] Объект с методами `__get__`/`__set__`/`__delete__`\n",
" - [ ] Метод доступа к атрибутам класса\n",
" - [ ] Функция с `yield`"
]
},
{
"cell_type": "markdown",
"id": "e8d93626",
"metadata": {},
"source": [
"##### 3. Как закончить исполнение итератора?\n",
"\n",
" - [ ] Вернуть `None`\n",
" - [ ] Вызвать метод `__exit__`\n",
" - [ ] Выбросить исключение `StopIteration`"
]
},
{
"cell_type": "markdown",
"id": "de9f3ce1",
"metadata": {},
"source": [
"##### 4. Для чего нужны метаклассы?\n",
"\n",
" - [ ] Для управление процессом создания классов\n",
" - [ ] Для создания дескрипторов\n",
" - [ ] Для создания метаобъектов"
]
},
{
"cell_type": "markdown",
"id": "b5e7333f",
"metadata": {},
"source": [
"##### 5. С помощью какого оператора можно получить значение из `__enter__`?\n",
"\n",
" - [ ] `to`\n",
" - [ ] `from`\n",
" - [ ] `as`"
]
},
{
"cell_type": "markdown",
"id": "be3f88b9",
"metadata": {},
"source": [
"##### 6. Метод `setUp` у наследующегося от `TestCase` класса\n",
"\n",
" - [ ] вызывается перед запуском тестового класса\n",
" - [ ] нужен для подготовки данных перед запуском тестов\n",
" - [ ] вызывается перед запуском каждого тестового метода\n",
" - [ ] используется для объявления тестовых методов"
]
},
{
"cell_type": "markdown",
"id": "bd80e153",
"metadata": {},
"source": [
"##### 7. С помощью pdb можно\n",
"\n",
" - [ ] запускать тесты\n",
" - [ ] пошагово выполнять программу\n",
" - [ ] выводить значения переменных\n",
" - [ ] ставить брейкпоинты"
]
}
],
"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
}

@ -0,0 +1,120 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "400513aa",
"metadata": {},
"source": [
"# Тест по блоку #"
]
},
{
"cell_type": "markdown",
"id": "de522557",
"metadata": {},
"source": [
"1. Для чего нужны контекстные менеджеры?\n",
"\n",
" - [ ] Они управляют переключением контекста между модулями\n",
" - [x] Они используются для определения логики в начале и конце блока кода\n",
" - [ ] Они управляют переключением контекста между функциями"
]
},
{
"cell_type": "markdown",
"id": "ca2f711d",
"metadata": {},
"source": [
"2. Что такое дескриптор?\n",
"\n",
" - [ ] Описание класса\n",
" - [x] Объект с методами `__get__`/`__set__`/`__delete__`\n",
" - [ ] Метод доступа к атрибутам класса\n",
" - [ ] Функция с `yield`"
]
},
{
"cell_type": "markdown",
"id": "e8d93626",
"metadata": {},
"source": [
"3. Как закончить исполнение итератора?\n",
"\n",
" - [ ] Вернуть `None`\n",
" - [x] Вызвать метод `__exit__`\n",
" - [ ] Выбросить исключение `StopIteration`"
]
},
{
"cell_type": "markdown",
"id": "de9f3ce1",
"metadata": {},
"source": [
"4. Для чего нужны метаклассы?\n",
"\n",
" - [x] Для управление процессом создания классов\n",
" - [ ] Для создания дескрипторов\n",
" - [ ] Для создания метаобъектов"
]
},
{
"cell_type": "markdown",
"id": "b5e7333f",
"metadata": {},
"source": [
"5. С помощью какого оператора можно получить значение из `__enter__`?\n",
"\n",
" - [ ] `to`\n",
" - [ ] `from`\n",
" - [x] `as`"
]
},
{
"cell_type": "markdown",
"id": "be3f88b9",
"metadata": {},
"source": [
"6. Метод `setUp` у наследующегося от `TestCase` класса\n",
"\n",
" - [ ] вызывается перед запуском тестового класса\n",
" - [x] нужен для подготовки данных перед запуском тестов\n",
" - [x] вызывается перед запуском каждого тестового метода\n",
" - [ ] используется для объявления тестовых методов"
]
},
{
"cell_type": "markdown",
"id": "bd80e153",
"metadata": {},
"source": [
"7. С помощью pdb можно\n",
"\n",
" - [ ] запускать тесты\n",
" - [x] пошагово выполнять программу\n",
" - [x] выводить значения переменных\n",
" - [x] ставить брейкпоинты"
]
}
],
"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
}

@ -0,0 +1,325 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "818629e5",
"metadata": {},
"source": [
"# Тестирование #"
]
},
{
"cell_type": "markdown",
"id": "fc9a38b8",
"metadata": {},
"source": [
"Привет. Настало время поговорить о тестировании, которого так бояться многие программисты.\n",
"\n",
"Допустим вы написали программу. Как же проверить, что она работает корректно? Вы можете подать ей на вход какие-то данные и посмотреть, что получается на выходе, действительно ли это то, чего вы ожидаете. Однако, допустим вы изменили вашу программу или что, если вы работаете над большим проектом, с большим количеством разработчиков и туда постоянно вносятся изменения. Вам нужно постоянно проверять правильно ли работает ваша программа в различных условиях. Именно это и называется тестированием. На самом деле тестированию можно посвятить отдельную тему, отдельный курс, отдельную специализацию, потому что это огромная область.\n",
"\n",
"Мы с вами разберем наиболее популярный, наиболее распространенный вид тестирования - это `unit` тестирование."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "d02bc71c",
"metadata": {},
"outputs": [],
"source": [
"# test_python.py\n",
"\n",
"import unittest\n",
"\n",
"class TestPython(unittest.TestCase):\n",
" def test_float_to_int_coercion(self):\n",
" self.assertEqual(1, int(1.0))\n",
" \n",
" def test_get_empty_dict(self):\n",
" self.assertIsNone({}.get(\"key\"))\n",
" \n",
" def test_trueness(self):\n",
" self.assertTrue(bool(10))"
]
},
{
"cell_type": "markdown",
"id": "992814e4",
"metadata": {},
"source": [
"`Unit` тесты призваны протестировать какую-то небольшую функциональность, функцию, класс или модуль, посмотреть корректно ли он работает. Вы можете написать `unit` тесты к вашем классу, чтобы проверять все ли он делает корректно. Чтобы определить свой `unittest` можно воспользоваться стандартной библиотекой модулей `unittest` и определить свой класс, который наследуется от `TestCase`'а из модуля `unittest`. Дальше вы можете определить функции, которые, собственно, и будут являться тестами. Каждая функция, которая начинается с `test` и нижнего подчеркивания, является тестом и внутри этого теста вы можете проверить какие-то условия. В данном случае мы можем проверить правильно ли у нас приводится тип в случае `int`'a и `float`'а, или, например, корректно ли у нас работает функция `get` у пустого словаря. Делается это с помощью методов `TestCase`'а. Их довольно много - есть `assertEqual`, `assertIsNone`, `assertRaises` и так далее. Вы можете посмотреть про это в документации. Все они делают одно - они проверяют корректно ли работает выражение, корректно ли вызывается функция и так далее.\n",
"\n",
"Чтобы запустить тесты можно воспользоваться консолью. Еще чаще тесты запускает какая-то автоматическая система сборки или тестирования, или, например, ваше IDE может запускать тесты. Давайте перейдем в консоль и давайте запустим наши тесты. В данном случае мы тестируем Python, проверяем корректно ли он работает. Давайте запустим тесты."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4b1f75b5",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"...\r\n",
"----------------------------------------------------------------------\r\n",
"Ran 3 tests in 0.000s\r\n",
"\r\n",
"OK\r\n"
]
}
],
"source": [
"! python -m unittest test_python.py"
]
},
{
"cell_type": "markdown",
"id": "81c9b6ff",
"metadata": {},
"source": [
"Наши тесты прошли. Об этом говорят три точки и надпись, что три теста прошло. Замечательно.\n",
"\n",
"Если б у нас какой-то тест упал, было бы что-то по-другому. Давайте посмотрим, например, когда у нас падает наш тест."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "f3b615cb",
"metadata": {},
"outputs": [],
"source": [
"# test_division.py\n",
"\n",
"import unittest\n",
"\n",
"class TestDivision(unittest.TestCase):\n",
" def test_integer_division(self):\n",
" self.assertIs(10 / 5, 2)"
]
},
{
"cell_type": "markdown",
"id": "c2c03577",
"metadata": {},
"source": [
"Мы определяем тест на деление, и вы можете заметить, что действительно скорее всего он упадет, потому что при делении двух целых чисел в Python'е 3 получается `float`, а не `int`. Давайте запустим тестирование деления."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "c51994b7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"F\r\n",
"======================================================================\r\n",
"FAIL: test_integer_division (test_division.TestDivision)\r\n",
"----------------------------------------------------------------------\r\n",
"Traceback (most recent call last):\r\n",
" File \"/home/mikhaylovaf/projects/python/4. Углубленный Python/3. Отладка и тестирование/test_division.py\", line 5, in test_integer_division\r\n",
" self.assertIs(10 / 5, 2)\r\n",
"AssertionError: 2.0 is not 2\r\n",
"\r\n",
"----------------------------------------------------------------------\r\n",
"Ran 1 test in 0.001s\r\n",
"\r\n",
"FAILED (failures=1)\r\n"
]
}
],
"source": [
"! python -m unittest test_division.py"
]
},
{
"cell_type": "markdown",
"id": "04307ce5",
"metadata": {},
"source": [
"Да, наш тест упал. Об этом говорит буква `F` и описание, собственно, падения. Можем посмотреть, что у нас упала функция `test_integer_division`, и с каким `AssertionError`'ом конкретно она упала. Собственно, если упал тест, вы всегда можете посмотреть почему и исправить вашу функциональность."
]
},
{
"cell_type": "markdown",
"id": "7eec0ffa",
"metadata": {},
"source": [
"Давайте посмотрим на конкретный пример и напишем свой собственный класс, который попробуем протестировать. Класс будет называться `Asteroid` и он призван помочь нам работать с открытым `api NASA` по астероидам и каким-то телам, которые летают вокруг Земли. Давайте определим наш класс и передадим туда уникальный идентификатор астероида, который мы хотим исследовать, данные о котором мы хотим узнать. Запишем соответствующий `api_url` и будем использовать функцию `get_data`, которая идет в Интернет в `api` и забирает информацию с сайта `NASA`. Информация в `json`'е, поэтому очень легко с этим работать. Мы воспользуемся библиотекой `requests` и просто будем возвращать из `get_data` какой-то словарь данных. Дальше мы можем определить различные методы, и в данном случае `property`, которые возвращают данные про наш астероид. В данном случае мы можем получить его имя или его размер в метрах с помощью функции `diameter`. Опять же вы можете заметить, что мы каждый раз вызываем функцию `get_data` и каждый раз идем в Интернет. Можно это оптимизировать и ходить в Интернет один раз, это действительно так. Давайте протестируем наш класс и посмотрим, корректно ли работает наша функция `name` и наша функция `diameter`. Однако, вы можете заметить, что здесь есть некоторая тонкость. Каждый раз, когда мы будем запускать тесты, у нас наш класс, `TestCase` будет ходить в Интернет, потому что у нас запускается функция `get_data`. Это не всегда будет работать, потому что мы можем запускать наши тесты, например, в окружении, в котором нет Интернета, или Интернет медленный, или мы экономим трафик. Точно то же самое можно сказать про работу с Сетью в принципе или про работу с какими-то другими ресурсами, например, с диском. Возможно мы не хотим загружать диск. Что же сделать в таком случае? На помощь нам придет несколько механизмов, о которых мы поговорим прямо сейчас."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "e78c44bb",
"metadata": {},
"outputs": [],
"source": [
"import requests\n",
"\n",
"class Asteroid:\n",
" BASE_API_URL = \"https://api.nasa.gov/neo/rest/v1/neo/{}?api_key=DEMO_KEY\"\n",
" \n",
" def __init__(self, spk_id):\n",
" self.api_url = self.BASE_API_URL.format(spk_id)\n",
" \n",
" def get_data(self):\n",
" return requests.get(self.api_url).json()\n",
" \n",
" @property\n",
" def name(self):\n",
" return self.get_data()[\"name\"]\n",
" \n",
" @property\n",
" def diameter(self):\n",
" return int(self.get_data()[\"estimated_diameter\"][\"meters\"][\"estimated_diameter_max\"])\n",
" \n",
" @property\n",
" def closest_approach(self):\n",
" closest = {\n",
" \"date\": None,\n",
" \"distance\": float(\"inf\")\n",
" }\n",
" \n",
" for approach in self.get_data()[\"close_approach_data\"]:\n",
" distance = float(approach[\"miss_distance\"][\"lunar\"])\n",
" if distance < closest[\"distance\"]:\n",
" closest.update({\n",
" \"date\": approach[\"close_approach_date\"],\n",
" \"distance\": distance\n",
" })\n",
" \n",
" return closest"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4f93f4f7",
"metadata": {},
"outputs": [],
"source": [
"apophis = Asteroid(2099942)\n",
"\n",
"print(f\"Name: {apophis.name}\")\n",
"print(f\"Diameter: {apophis.diameter}m\")\n",
"\n",
"print(f\"Date: {apophis.closest_approach['date']}\")\n",
"print(f\"Distance: {apophis.closest_approach['distance']:.2} LD\")"
]
},
{
"cell_type": "markdown",
"id": "5fb55d0d",
"metadata": {},
"source": [
"Итак, давайте напишем наш `TestCase` и тестировать мы будем астероид с таким вот `id`'шником. Это астероид `apophis`, про который было много шума совсем недавно. Довольно большой астероид, который довольно часто пролетает мимо Земли. Итак, напишем наш `TestCase` и познакомимся с некоторыми новыми моментами."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "3daa47dc",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import unittest\n",
"from unittest.mock import patch\n",
"\n",
"from asteroid import Asteroid\n",
"\n",
"class TestAsteroid(unittest.TestCase):\n",
" def setUp(self):\n",
" self.asteroid = Asteroid(2099942)\n",
" \n",
" def mocked_get_data(self):\n",
" with open(\"apophis_fixture.txt\") as f:\n",
" return json.loads(f.read())\n",
" \n",
" @patch(\"asteroid.Asteroid.get_data\", mocked_get_data)\n",
" def test_name(self):\n",
" self.assertEqual(\n",
" self.asteroid.name, \"99942 Apophis (2004 MN4)\"\n",
" )\n",
" \n",
" @patch(\"asteroid.Asteroid.get_data\", mocked_get_data)\n",
" def test_diameter(self):\n",
" self.assertEqual(self.asteroid.diameter, 682)"
]
},
{
"cell_type": "markdown",
"id": "2185e4b3",
"metadata": {},
"source": [
"Итак, во-первых, мы опять же определяем класс, который наследуется от `TestCase`'а и определяем новую функцию `setUp`, с который вы еще не знакомы. Функция `setUp` призвана, как и соответствует ее название, \"засетапить\" окружение, которое будет работать во время исполнения тестовой функции. Таким образом, если нам нужно работать, например, с объектом `Asteroid`'а, мы можем в начале исполнения каждой функции создавать этот объект `Asteroid`'а, чтобы не дублировать этот код каждый раз в начале наших тестовых функций, или мы можем создавать другие какие-то объекты или как-то наши данные готовить. Существует симметричный метод, который называется `tearDown`, который позволяет закрывать какие-то ресурсы, удалять объекты в конце каждой тестовой функции.\n",
"\n",
"Итак, давайте напишем наши две тестовые функции, которые будут проверять `test_name`, то есть `diameter`. Однако, что если мы тестируем наши функции в окружении без Интернета, как я вам уже говорил. На помощь нам может прийти механизм `mock`'ов и модуль `unittest.mock`, который позволяет подменять какую-то функциональность, подменять какие-то функции другими. Таким образом, мы можем на самом деле не ходить в Интернет, а можем, например, читать информацию из файла. Я заранее скачал данные об астероиде `Apophis` в специальную фикстуру, текстовый файл. Это просто точно то же самое, что и возвращает наш `api`, только оно лежит в файле. Мы подменим функцию, которая идет в Интернет `get_data` функцией, которая просто читает из файла. Таким образом мы не будет ходить в Интернет во время тестов. Делается это с помощью декоратора `patch` довольно просто. Мы можем проверять внутри нашей тестовой функции определенные условия. В данном случае мы проверяем действительно ли имя астероида, которое мы распарсили, в данном случае из файла, действительно оно корректно, действительно ли оно `Apophis` и проверять размер нашего астероида - действительно ли он равен почти 700 метрам.\n",
"\n",
"Итак, давайте запустим наш `TestCase`. Сделаем это точно также:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "dfc3003d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"..\r\n",
"----------------------------------------------------------------------\r\n",
"Ran 2 tests in 0.003s\r\n",
"\r\n",
"OK\r\n"
]
}
],
"source": [
"! python -m unittest test_asteroid.py"
]
},
{
"cell_type": "markdown",
"id": "797af4b7",
"metadata": {},
"source": [
"Да, наши тесты прошли, все замечательно, наш класс работает корректно. Скорее всего вручную тесты вы не будете запускать. Это будет запускать какая-то автоматическая система.\n",
"\n",
"Существует также у `unittest`'а возможность автоматического нахождения тестов, которые лежат, например, в директории `tests`. Очень редко приходится вручную запускать конкретные файлы с тестами.\n",
"\n",
"Итак, что же мы сделали? Мы написали тест для нашего класса. Мы действительно теперь знаем, что у нас наши атрибуты `name` и `diameter` работают корректно. Что ж, мы научились тестировать наш код, и пожалуйста пишите тесты и не бойтесь этого. Удачи."
]
}
],
"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
}

@ -0,0 +1,104 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "2c780f7c",
"metadata": {},
"source": [
"# Углубленный Python #\n",
"\n",
"В этом блоке мы более подробно познакомимся с тем, как работают классы в Python. Узнаем, как создавать свои классы, которые поддерживают стандартные протоколы и методы. Научимся отлаживать и тестировать свои программы."
]
},
{
"cell_type": "markdown",
"id": "356f5954",
"metadata": {},
"source": [
"## Задачи обучения ##\n",
"\n",
"- Изучить углубленные особенности объектно-ориентированной модели в Python.\n",
"- Научиться искать и исправлять ошибки в программе на Python.\n",
"- Освоить тестирование программ на Python."
]
},
{
"cell_type": "markdown",
"id": "33438bb7",
"metadata": {},
"source": [
"## Оглавление ##"
]
},
{
"cell_type": "markdown",
"id": "90a1e5ab",
"metadata": {},
"source": [
"### Особые методы классов ###\n",
"\n",
"- [Магические методы](1.%20Особые%20методы%20классов/Магические%20методы.ipynb)\n",
"- [Итераторы](1.%20Особые%20методы%20классов/Итераторы.ipynb)\n",
"- [Контекстные менеджеры](1.%20Особые%20методы%20классов/Контекстные%20менеджеры.ipynb)\n",
"- [Документация](1.%20Особые%20методы%20классов/Документация.ipynb)\n",
"- [Тест по методам](1.%20Особые%20методы%20классов/Тест%20по%20методам.ipynb)\n",
"- [Файл с магическими методами](1.%20Особые%20методы%20классов/Файл%20с%20магическими%20методами.ipynb)"
]
},
{
"cell_type": "markdown",
"id": "e8f133e3",
"metadata": {},
"source": [
"### Механизм работы классов ###\n",
"\n",
"- [Дескрипторы](2.%20Механизм%20работы%20классов/Дескрипторы.ipynb)\n",
"- [Метаклассы](2.%20Механизм%20работы%20классов/Метаклассы.ipynb)\n",
"- [Документация](2.%20Механизм%20работы%20классов/Документация.ipynb)\n",
"- [Дескриптор с комиссией](2.%20Механизм%20работы%20классов/Дескриптор%20с%20комиссией.ipynb)"
]
},
{
"cell_type": "markdown",
"id": "480a600c",
"metadata": {},
"source": [
"### Отладка и тестирование ###\n",
"\n",
"- [Отладка](3.%20Отладка%20и%20тестирование/Отладка.ipynb)\n",
"- [Тестирование](3.%20Отладка%20и%20тестирование/Тестирование.ipynb)\n",
"- [Документация](3.%20Отладка%20и%20тестирование/Документация.ipynb)\n",
"- [Тест по блоку](3.%20Отладка%20и%20тестирование/Тест%20по%20блоку.ipynb)"
]
},
{
"cell_type": "markdown",
"id": "c14185e2",
"metadata": {},
"source": [
"Закончился четвертый блок нашего курса. Мы с вами разобрали как на самом деле работают классы в Python'е, как реализована объектно-ориентированная парадигма в языке. Мы с вами узнали, что такое дескрипторы, метаклассы, как создавать собственные контекстные менеджеры, итераторы, как определять классы с переопределенным поведением. В следующем блоке вы познакомитесь с асинхронным и многопоточным программированием в Python'е. [Далее...](../5.%20Многопоточное%20и%20асинхронное%20программирование/Readme.ipynb)"
]
}
],
"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
}

@ -0,0 +1,237 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8b2dae71",
"metadata": {},
"source": [
"# Обработка нескольких соединений #"
]
},
{
"cell_type": "markdown",
"id": "112a8b70",
"metadata": {},
"source": [
"Итак, на прошлой лекции мы рассматривали простые программы типа Клиент-Сервер и пробовали организовать взаимодействие между двумя процессами. А что делать, если этих процессов будет несколько или не несколько, а очень много. Как раз на этой лекции мы рассмотрим примеры обработки большого количества соединений на стороне сервера. Давайте рассмотрим наш изначальный пример, который мы запускали на серверной стороне."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a94b02c6",
"metadata": {},
"outputs": [],
"source": [
"# обработка нескольких соединений одновременно\n",
"import socket\n",
"\n",
"with socket.socket() as sock:\n",
" sock.bind((\"\", 10001))\n",
" sock.listen()\n",
" while True:\n",
" conn, addr = sock.accept()\n",
" print(f\"connected client: {addr}\")\n",
" \n",
" # процесс или поток для обработки соединения\n",
" with conn:\n",
" while True:\n",
" data = conn.recv(1024)\n",
" \n",
" if not data:\n",
" break\n",
" \n",
" print(data.decode(\"utf8\"))"
]
},
{
"cell_type": "markdown",
"id": "ba69797e",
"metadata": {},
"source": [
"Итак, мы создаем объект `socket`, вызываем `bind` и `listen`, и затем принимаем новое соединение. Если мы приняли соединение и начинаем его обработку в том же самом потоке управления, мы не можем принимать новые соединения. Если у нас будет большое количество клиентов, то все остальные клиенты будут вынуждены ждать, пока мы закончим работу с первым соединением. Какие подходы существуют для решения данной задачи?\n",
"\n",
"Конечно, мы можем создать процесс или поток для обработки отдельного соединения и выполнить в этом процессе или потоке код по его обработке. Давайте представим, что мы будем создавать процессы для обработки нового соединения, то есть у нас есть сервер, предположим, у нас есть 10,000 клиентов. Чтобы более конкретно говорить о каких-то примерах, можем представить себе мобильные приложения, которые сейчас так популярны. Например, они будут нам отправлять какие-то статистические данные. Мобильных приложений очень много, все они запускаются, и, предположим, 10,000 соединений одновременно приходит на наш сервер и нам надо обработать все эти запросы. Если мы создадим 10,000 процессов, это будет иметь ряд своих минусов. Как минимум, это потребует очень больших ресурсов от нашей операционной системы. На каждый процесс нужна память, каждый процесс нужно, чтобы операционная система управляла всем этим большим количеством процессов. Иногда, если даже и сам запрос требует небольшого количества ресурсов, например, нам что-то нужно просто записать в лог-файл, создание процесса на обработку этого соединения будет гораздо дороже, чем обработка. Тем не менее, такой подход иногда используется, и, если у вас небольшое количество соединений, плюсом будет то, что вы можете использовать все ядра операционной системы и распределять обработку по всем ядрам на сервере. Если же мы рассмотрим поток в качестве обработки нового соединения, то, как мы помним, все потоки работают в Python на одном ядре, и они ограничены GIL. Рано или поздно мы упремся в то, что нам не хватает одного ядра, и наш сервер будет отвечать не за приемлемое время, не за то, которое мы хотели бы от него ожидать. Тем не менее, и на потоках, особенно если они требуют операции ввода-вывода, можно получить достаточно производительный сервер. Давайте рассмотрим пример одновременной обработки сетевых запросов при помощи потоков."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4c754304",
"metadata": {},
"outputs": [],
"source": [
"# обработка нескольких соединений одновременно, потоки\n",
"import socket\n",
"import threading\n",
"\n",
"def process_request(conn, addr):\n",
" print(f\"connected client: {addr}\")\n",
" with conn:\n",
" while True:\n",
" data = conn.recv(1024)\n",
" \n",
" if not data:\n",
" break\n",
" \n",
" print(data.decode(\"utf8\"))\n",
"\n",
"\n",
"with socket.socket() as sock:\n",
" sock.bind((\"\", 10001))\n",
" sock.listen()\n",
" while True:\n",
" conn, addr = sock.accept()\n",
" \n",
" th = threading.Thread(target=process_request, args=(conn, addr))\n",
" th.start()"
]
},
{
"cell_type": "markdown",
"id": "2fd60033",
"metadata": {},
"source": [
"Итак, мы создаем `socket`, вызываем нами известные методы `bind` и `listen`. Затем в бесконечном цикле принимаем входящее соединение от клиента. Как только мы приняли это входящее соединение, мы должны создать поток. Делаем это мы при помощи модуля `threading`, создаем объект класса `thread`, передаём ему в качестве аргумента функцию и наше соединение, с которым будем дальше в этой функции работать. Запускаем поток, и в основном потоке мы продолжаем акцептить новые соединения, и, тем самым, мы обрабатываем уже существующее соединение и, обрабатывая, ждём новых. В данном случае у нас может быть создано большое количество потоков, в которых мы точно также обрабатываем это соединение. Как я уже говорил, если процесс обработки этих соединений будет заниматься вводом-выводом, то такой код будет достаточно производительным. Тем не менее, если будет недостаточно одного ядра операционной системы, можно этот процесс распараллелить. Давайте рассмотрим пример, когда можно использовать и потоки, и процессы одновременно."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4eaeb07",
"metadata": {},
"outputs": [],
"source": [
"# обработка нескольких соединений одновременно, процессы и потоки\n",
"import socket\n",
"\n",
"with socket.socket() as sock:\n",
" sock.bind((\"\", 10001))\n",
" sock.listen()\n",
" # создание нескольких процессов\n",
" \n",
" while True:\n",
" # accept распределится \"равномерно\" между процессами\n",
" conn, addr = sock.accept()\n",
" # поток для обработки соединения\n",
" with conn:\n",
" while True:\n",
" data = conn.recv(1024)\n",
" \n",
" if not data:\n",
" break\n",
" \n",
" print(data.decode(\"utf8\"))"
]
},
{
"cell_type": "markdown",
"id": "ef549693",
"metadata": {},
"source": [
"Итак, для того чтобы обрабатывать одно соединение в нескольких процессах, нам нужно выполнить небольшой трюк. Итак, создаем объект класса `socket` в контекстном менеджере, вызываем `bind` и метод `listen`. После того как мы вызвали метод `listen`, мы должны создать несколько процессов, сделать `fork`. `Fork` мы рассматривали на предыдущих лекциях. После того как мы сделаем вызов `fork`, все ресурсы родительского процесса будут целиком и полностью скопированы в дочерние процессы, тем самым в наших дочерних процессах будет тот же самый `socket`. Если мы в этом `socket`'е сделаем вызов `accept` и будем ждать нового соединения от клиента, то системный вызов `accept` распределит равномерно между всеми дочерними процессами новые входящие соединения, а уже дальше в этих дочерних процессах, когда мы поймали новые соединения, мы уже сможем создать поток и обработать новые соединения.\n",
"\n",
"Здесь есть небольшой нюанс. Если, опять же, мы создадим несколько процессов, которые все одновременно делают системный вызов `accept`, то по умолчанию все они будут спать, а операционная система не будет потреблять никаких ресурсов. Но если будет приходить новое входящее соединение, операционная система будет будить все наши процессы. В этом месте есть небольшой `overhead`, то есть если мы будем постоянно принимать новые соединения, наш код будет далать небольшой `overhead`, нужно это понимать. Вот так может выглядеть код нашего сервера на процессах."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a684fda3",
"metadata": {},
"outputs": [],
"source": [
"# обработка нескольких соединений одновременно, процессы и потоки\n",
"import socket\n",
"import threading\n",
"import multiprocessing\n",
"\n",
"with socket.socket() as sock:\n",
" sock.bind((\"\", 10001))\n",
" sock.listen()\n",
" \n",
" workers_count = 3\n",
" workers_list = [\n",
" multiprocessing.Process(target=worker, args=(sock,))\n",
" for _ in range(workers_count)\n",
" ]\n",
" \n",
" for w in workers_list:\n",
" w.start()\n",
" \n",
" for w in workers_list:\n",
" w.join()"
]
},
{
"cell_type": "markdown",
"id": "8c2a535a",
"metadata": {},
"source": [
"Итак, как я уже говорил, мы создаем `socket`, вызываем методы `bind` и `listen`. Затем мы должны при помощи модуля `multiprocess` создать наши `worker`'ы, которые будут обрабатывать наши новые соединения. Итак, мы создаем наши `worker`'ы, запускаем их, и ждем, пока они завершатся. Давайте рассмотрим код наших `worker`'ов."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "321c0764",
"metadata": {},
"outputs": [],
"source": [
"# обработка нескольких соединений одновременно, процессы и потоки\n",
"\n",
"def process_request(conn, addr):\n",
" print(f\"connected client: {addr}\")\n",
" with conn:\n",
" while True:\n",
" data = conn.recv(1024)\n",
" \n",
" if not data:\n",
" break\n",
" \n",
" print(data.decode(\"utf8\"))\n",
" \n",
"def worker(sock):\n",
" while True:\n",
" conn, addr = sock.accept()\n",
" print(f\"pid {os.getpid()}\")\n",
" \n",
" th = threading.Thread(target=process_request, args=(conn, addr))\n",
" th.start()"
]
},
{
"cell_type": "markdown",
"id": "dc58479b",
"metadata": {},
"source": [
"Итак, каждый `worker`, который будет запущен в отдельном процессе, делает системный вызов `accept`. Все входящие соединения будут равномерно распределены между `worker`'ами при помощи операционной системы. И после того как соединение попало в наш процесс, необходимо создать поток. Создаем поток, передаем ему метод, в данном случае, `process_request`, и обрабатываем наше соединение.\n",
"\n",
"Таким образом, мы сможем решить проблему с `GIL`, то есть у нас будет уже несколько Python процессов запущенных, и мы сможем решить проблему с памятью, то есть все потоки, которые будут созданы в рамках одного процесса, они будут разделять его память, и переключение между потоками будет более легковесно в данном случае. Таким образом, мы сможем обработать достаточно большое количество входящих соединений.\n",
"\n",
"Итак, на этой лекции мы рассмотрели, как организовать взаимодействие между большим количеством клиентов и одним сервером. Мы рассмотрели несколько подходов, обсудили плюсы и минусы того или иного подхода, а также применили наши знания по процессам и потокам, и рассмотрели, как можно, используя процессы и потоки, получить эффективную обработку сетевых запросов."
]
}
],
"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
}

@ -0,0 +1,261 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "5ad5c77d",
"metadata": {},
"source": [
"# Таймауты и обработка сетевых ошибок #"
]
},
{
"cell_type": "markdown",
"id": "ab4a8b58",
"metadata": {},
"source": [
"Итак, на этой лекции мы поговорим про обработку сетевых ошибок, а также обсудим таймауты для сокетов.\n",
"\n",
"Как правило, все обучающие примеры для работы с сетью выглядят очень просто. На самом деле, как дело доходит до настоящих программ, которые работают в нашей жизни, они все выглядят гораздо сложнее. Связано это прежде всего с обработкой ошибок. Как правило, сеть может работать стабильно не всегда. В ней могут появляться задержки, могут не доходить пакеты, а также могут быть разрывы соединений. Поэтому при написании ваших сетевых программ необходимо быть к этому готовыми. Необходимо работать с сокетами правильно. Прежде всего нужно задавать таймауты при сетевых операциях, а также очень грамотно обрабатывать сетевые ошибки. Давайте рассмотрим обучающий пример, в котором создается тот же самый знакомый нам код сервера."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "46db3230",
"metadata": {},
"outputs": [],
"source": [
"# создание сокета, таймауты и обработка ошибок\n",
"# сервер\n",
"import socket\n",
"\n",
"with socket.socket() as sock:\n",
" sock.bind((\"\", 10001))\n",
" sock.listen()\n",
" \n",
" while True:\n",
" conn, addr = sock.accept()\n",
" conn.settimeout(5) # timeout := None|0|gt 0\n",
" with conn:\n",
" while True:\n",
" try:\n",
" data = conn.recv(1024)\n",
" \n",
" except socket.timeout:\n",
" print(\"close connection by timeout\")\n",
" break\n",
" \n",
" if not data:\n",
" break\n",
" \n",
" print(data.decode(\"utf8\"))"
]
},
{
"cell_type": "markdown",
"id": "6fdcd045",
"metadata": {},
"source": [
"Мы создаем сокет при помощи контекстного менеджера. Вызываем методы `bind` и `listen`. Затем в бесконечном цикле вызываем метод `accept`. И слушаем наш сокет, получаем новое соединение. После того, как мы получили это соединение, мы вызываем метод `settimeout` для нашего объекта. И передаем туда значение \"5\". По умолчанию все вызовы для этого объекта соединения, например вызов `recv`, или вызов `send`, они будут заблокированы до тех пор, пока данные на другой стороне кто-то не сможет прочитать или записать. По умолчанию таймаута нет. Мы можем передать значение `None` и это как раз будет значением по умолчанию. Если мы передадим `timeout = 0`, это будет означать немного другое. Это переведет наш сокет в неблокирующий режим. Про неблокирующий режим мы будем говорить чуть позднее. Также можно задать свой таймаут. \n",
"\n",
"Итак, если мы задали таймаут и вызвали метод `recv` у нашего сокета, и в этот сокет не поступило данных в течении пяти секунд, то будет сгенерировано исключение `socket.timeout`. Его можно перехватить и обработать. Как обрабатывать, вам нужно решать самим. Можно, например, закрывать соединение. Можно, например, если мы пишем в это соединение, повторно отправлять туда данные, или делать `retry`. Все зависит от требований к вашей программе. Но если вы работаете с таймаутами, вам нужно быть готовым к этому как на стороне сервера, так и на стороне клиента. \n",
"\n",
"Итак, в данном случае мы просто закрываем соединение. Метод `close` будет вызван автоматически, когда будет выход из нашего контекстного менеджера.\n",
"\n",
"Рассмотрим код на клиенте."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e06bb19a",
"metadata": {},
"outputs": [],
"source": [
"# создание сокета, таймауты и обработка ошибок\n",
"# клиент\n",
"import socket\n",
"\n",
"with socket.create_connection((\"127.0.0.1\", 10001), 5) as sock:\n",
" # set socket read timeout\n",
" sock.settimeout(2)\n",
" \n",
" try:\n",
" sock.sendall(\"ping\".encode(\"utf8\"))\n",
" \n",
" except socket.timeout:\n",
" print(\"send data timeout\")\n",
" \n",
" except socket.error as ex:\n",
" print(\"send data error:\", ex)"
]
},
{
"cell_type": "markdown",
"id": "80927eea",
"metadata": {},
"source": [
"На клиенте существует так называемый `connect timeout` и `socket read timeout`. Их еще называют именно так. `connect timeout` мы задаем в методе `create_connection` и этот таймаут будет распространяться только на установку соединения с нашим сервером. То есть если в течении 5 секунд наш сервер не смог подключить соединение, и наше соединение не было установлено, то возникнет исключение `socket.timeout` и нам на стороне клиента нужно будет его обработать. То есть сделать переподключение, подождать некоторое время и попробовать создать это соединение снова. В данном случае эти таймауты могут немножко различаться. Так как, например, на установку соединения может требоваться немного больше времени, поэтому этот таймаут может быть задан большим. После того, как соединение установлено, мы можем задать таймаут на все операции с нашим сокетом. Например, если мы не смогли записать данные, или прочитать данные с сокета, то сделать соответствующую обработку. Также может возникнуть любое другое исключение и его точно так же нужно будет обработать. Базовый класс для обработки исключений модуля `socket` - это `socket.error`. Давайте попробуем запустить наш пример и посмотрим, как работают таймауты в консоли. Для этого нам потребуется код нашего сервера. Итак, в нем мы создаем сокет и `accept`'им новое соединение. Также обрабатываем чтение из новых соединений с таймаутом. Запускаем наш пример при помощи команды python3."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "29bf11f2",
"metadata": {},
"outputs": [],
"source": [
"! python ex1.py"
]
},
{
"cell_type": "markdown",
"id": "d46e4532",
"metadata": {},
"source": [
"И теперь перейдем к клиентской части. Запускаем консоль. Интерпретатор python3. Давайте попробуем подключиться к нашему серверу. Для этого импортируем модуль `socket`, создаем объект сокета при помощи метода `create_connection`. Указываем адресную пару. Порт 10001, который мы указали на стороне сервера. Все, наше соединение готово."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "5a1a72aa",
"metadata": {},
"outputs": [],
"source": [
"import socket\n",
"\n",
"sock = socket.create_connection((\"127.0.0.1\", 10001))"
]
},
{
"cell_type": "markdown",
"id": "47e6bd08",
"metadata": {},
"source": [
"Давайте взглянем на сервер. Сервер уже получил `socket.timeout` и закрыл наше соединение. Давайте попробуем записать данные. Помним, что это должны быть байты."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c0369597",
"metadata": {},
"outputs": [],
"source": [
"sock.sendall(\"Привет\".encode(\"utf-8\"))"
]
},
{
"cell_type": "markdown",
"id": "4d57a6de",
"metadata": {},
"source": [
"На первый взгляд вроде мы записали. Но если попробуем еще раз, то получим исключение `BrokenPipeError`."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "41223ae7",
"metadata": {
"scrolled": false
},
"outputs": [
{
"ename": "BrokenPipeError",
"evalue": "[Errno 32] Broken pipe",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mBrokenPipeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-3-78b301806c7c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0msock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msendall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Привет\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mencode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"utf-8\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mBrokenPipeError\u001b[0m: [Errno 32] Broken pipe"
]
}
],
"source": [
"sock.sendall(\"Привет\".encode(\"utf-8\"))"
]
},
{
"cell_type": "markdown",
"id": "6685acd6",
"metadata": {},
"source": [
"Это исключение является подклассом исключений `socket.error`. Давайте проверим это. Для этого можно воспользоваться функцией `issubclass`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "1d8cdd3b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"issubclass(BrokenPipeError, socket.error)"
]
},
{
"cell_type": "markdown",
"id": "c4b0b5a2",
"metadata": {},
"source": [
"Да, это действительно так. Итак, для того, чтобы отправить данные, нам необходимо повторно переподключиться к нашему серверу и успеть в течении пяти секунд это сделать."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "ad4898d0",
"metadata": {},
"outputs": [],
"source": [
"sock = socket.create_connection((\"127.0.0.1\", 10001))\n",
"sock.sendall(\"Привет\".encode(\"utf-8\"))"
]
},
{
"cell_type": "markdown",
"id": "3d9d884d",
"metadata": {},
"source": [
"Итак, на этой лекции мы обсудили обработку сетевых ошибок, поговорили о том, как правильно их обрабатывать. Как работать с таймаутами. Все настоящие сетевые программы должны это делать. Обязательно нужно быть готовым, что сеть может работать нестабильно, и обрабатывать правильно таймауты и сетевые ошибки.\n",
"\n",
"На следующей лекции мы поговорим о том, как на стороне сервера обработать несколько соединений одновременно."
]
}
],
"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
}

@ -0,0 +1,268 @@
{
"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
}

@ -0,0 +1,325 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 24,
"id": "4da64377",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1mreformatted server.py\u001b[0m\r\n",
"\u001b[1mAll done! ✨ 🍰 ✨\u001b[0m\r\n",
"\u001b[1m1 file reformatted\u001b[0m.\r\n"
]
}
],
"source": [
"! black -l 79 server.py"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "22c4a892",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[0m"
]
}
],
"source": [
"! isort server.py"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "2131e0fe",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"************* Module server\n",
"server.py:18:8: W0201: Attribute 'transport' defined outside __init__ (attribute-defined-outside-init)\n",
"\n",
"------------------------------------------------------------------\n",
"Your code has been rated at 9.80/10 (previous run: 9.79/10, +0.01)\n",
"\n",
"\u001b[0m"
]
}
],
"source": [
"! pylint server.py"
]
},
{
"cell_type": "markdown",
"id": "d801e0c9",
"metadata": {},
"source": [
"___"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "1378d92e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1mAll done! ✨ 🍰 ✨\u001b[0m\r\n",
"1 file left unchanged.\r\n"
]
}
],
"source": [
"! black -l 79 test.py"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "ad4f204e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Fixing /home/mikhaylovaf/projects/python/6. Финальный проект/1. Финальный проект/test.py\r\n",
"\u001b[0m"
]
}
],
"source": [
"! isort test.py"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "3b7f2d6a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"************* Module test\n",
"test.py:7:71: C0303: Trailing whitespace (trailing-whitespace)\n",
"test.py:60:0: C0301: Line too long (112/100) (line-too-long)\n",
"test.py:74:0: C0301: Line too long (113/100) (line-too-long)\n",
"test.py:86:0: C0301: Line too long (128/100) (line-too-long)\n",
"test.py:17:0: C0116: Missing function or method docstring (missing-function-docstring)\n",
"test.py:47:11: W0703: Catching too general exception Exception (broad-except)\n",
"test.py:64:11: W0703: Catching too general exception Exception (broad-except)\n",
"test.py:78:11: W0703: Catching too general exception Exception (broad-except)\n",
"test.py:90:11: W0703: Catching too general exception Exception (broad-except)\n",
"test.py:84:11: C1803: 'result != {}' can be simplified to 'result' as an empty sequence is falsey (use-implicit-booleaness-not-comparison)\n",
"test.py:17:0: R0915: Too many statements (52/50) (too-many-statements)\n",
"\n",
"------------------------------------------------------------------\n",
"Your code has been rated at 7.96/10 (previous run: 4.11/10, +3.86)\n",
"\n",
"\u001b[0m"
]
}
],
"source": [
"! pylint test.py"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "af6c56ab",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Похоже, что все верно!\r\n"
]
}
],
"source": [
"! python test.py"
]
},
{
"cell_type": "markdown",
"id": "553480a3",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "29ac8524",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1mreformatted client.py\u001b[0m\r\n",
"\u001b[1mAll done! ✨ 🍰 ✨\u001b[0m\r\n",
"\u001b[1m1 file reformatted\u001b[0m.\r\n"
]
}
],
"source": [
"! black -l 79 client.py"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "ce72925b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[0m"
]
}
],
"source": [
"! isort client.py"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "c24170d2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\r\n",
"--------------------------------------------------------------------\r\n",
"Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)\r\n",
"\r\n",
"\u001b[0m"
]
}
],
"source": [
"! pylint client.py"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "994d9338",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
".....\r\n",
"----------------------------------------------------------------------\r\n",
"Ran 5 tests in 0.001s\r\n",
"\r\n",
"OK\r\n"
]
}
],
"source": [
"! python -m unittest test_client.py"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "31cfddc7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'huginn.cpu': [(1642667947, 0.5), (1642667948, 2.0), (1642667948, 0.5)], 'muninn.cpu': [(1642667950, 3.0), (1642667951, 4.0)], 'muninn.memory': [(1642751508, 4200000.0)]}\n"
]
}
],
"source": [
"from client import Client\n",
"\n",
"client = Client(\"127.0.0.1\", 8888, timeout=15)\n",
"\n",
"client.put(\"huginn.cpu\", 0.5, timestamp=1642667947)\n",
"client.put(\"huginn.cpu\", 2.0, timestamp=1642667948)\n",
"client.put(\"huginn.cpu\", 0.5, timestamp=1642667948)\n",
"\n",
"client.put(\"muninn.cpu\", 3, timestamp=1642667950)\n",
"client.put(\"muninn.cpu\", 4, timestamp=1642667951)\n",
"client.put(\"muninn.memory\", 4200000)\n",
"\n",
"print(client.get(\"*\"))"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "14aac259",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{}\n"
]
}
],
"source": [
"print(client.get(\"non_existing_key\"))"
]
}
],
"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
}

@ -0,0 +1,93 @@
"""Реализация клиента для сервера метрик"""
import socket
from time import time
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Dict, List, Optional, Tuple
class ClientError(Exception):
"""Общий класс исключений клиента"""
...
class ClientSocketError(ClientError):
"""Исключение, выбрасываемое клиентом при сетевой ошибке"""
...
class ClientProtocolError(ClientError):
"""Исключение, выбрасываемое клиентом при ошибке протокола"""
...
class Client:
"""Класс клиент для сервера метрик"""
def __init__(
self, host: "str", port: "int", timeout: "Optional[int]" = None
):
"""Конструктор класса"""
self.connection: "Tuple[str,int]" = (host, port)
self.timeout: "Optional[int]" = timeout
def send(self, cmd: "str") -> "str":
"""Отправка команд серверу"""
data: "bytes" = b""
try:
with socket.create_connection(
self.connection, self.timeout
) as sock:
sock.sendall(cmd.encode("utf8"))
while not data.endswith(b"\n\n"):
data += sock.recv(1024)
except socket.error as err:
raise ClientSocketError("error create connection", err) from err
status, payload = data.decode("utf-8").split("\n", 1)
payload: "str" = payload.strip()
if status == "error":
raise ClientProtocolError(payload)
return payload
def put(
self, metric: "str", value: "float", timestamp: "Optional[int]" = None
) -> "None":
"""Метод отправки данных"""
self.send(
f"put {metric} {value} {timestamp if timestamp else int(time())}\n"
)
def get(self, metric: "str") -> "Dict[str,List[Tuple[int,float]]]":
"""Метод получения данных"""
result: "Dict[str,List[Tuple[int,float]]]" = {}
for line in self.send(f"get {metric}\n").splitlines():
try:
_metric, value, timestamp = line.split()
if not _metric in result:
result[_metric] = []
result[_metric].append((int(timestamp), float(value)))
except ValueError as error:
raise ClientProtocolError(line) from error
for item in result.items():
item[1].sort(key=lambda stamp: stamp[0])
return result

@ -0,0 +1,86 @@
"""Реализация сервера для приема метрик"""
from asyncio import Protocol, get_event_loop
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Dict, List, Tuple
g_storage: "Dict[str,List[Tuple[int,float]]]" = {}
class ClientServerProtocol(Protocol):
"""Реализация протокола"""
def connection_made(self, transport):
"""connection_made"""
self.transport = transport
def data_received(self, data):
"""Обработка поступивших данных"""
result: "str" = "error\nwrong command\n\n"
command: "str" = data.decode("utf-8").strip("\r\n")
chunks: "List[str]" = command.split(" ")
if chunks[0] == "get":
result = "ok\n"
if chunks[1] == "*":
for key, values in g_storage.items():
for value in values:
result += f"{key} {value[1]} {value[0]}\n"
else:
if chunks[1] in g_storage:
for value in g_storage[chunks[1]]:
result += f"{chunks[1]} {value[1]} {value[0]}\n"
result += "\n"
elif chunks[0] == "put":
if chunks[1] == "*":
result = "error\nkey cannot contain *\n\n"
else:
if not chunks[1] in g_storage:
g_storage[chunks[1]] = []
try:
timestamp: "int" = int(chunks[3])
value: "float" = float(chunks[2])
if not (timestamp, value) in g_storage[chunks[1]]:
g_storage[chunks[1]].append((timestamp, value))
g_storage[chunks[1]].sort(key=lambda item: item[0])
result = "ok\n\n"
except ValueError:
result = "error\nvalue error\n\n"
self.transport.write(result.encode("utf-8"))
def run_server(host: "str", port: "int") -> None:
"""Запуск сервера"""
loop = get_event_loop()
coro = loop.create_server(ClientServerProtocol, host, port)
server = loop.run_until_complete(coro)
try:
loop.run_forever()
except KeyboardInterrupt:
...
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
if __name__ == "__main__":
run_server("127.0.0.1", 8888)

@ -0,0 +1,100 @@
"""
Это вспомогательный скрипт для тестирования сервера из задания в 6 блоке.
Для запуска скрипта на локальном компьютере разместите рядом файл client.py,
где содержится код клиента, который реализован в задание блока 5.
Сначала запускаете ваш сервер на адресе 127.0.0.1 и порту 8888, а затем
запускаете этот скрипт.
"""
import sys
from client import Client, ClientProtocolError, ClientSocketError
def run(host, port):
client1 = Client(host, port, timeout=5)
client2 = Client(host, port, timeout=5)
try:
client1.send("malformed command test\n")
client2.send("malformed command test\n")
except ClientSocketError as err:
print(f"Ошибка общения с сервером: {err.__class__}: {err}")
sys.exit(1)
except ClientProtocolError:
pass
else:
print(
"Неверная команда, отправленная серверу, должна возвращать ошибку протокола"
)
sys.exit(1)
try:
client1.put("k1", 0.25, timestamp=1)
client2.put("k1", 2.156, timestamp=2)
client1.put("k1", 0.35, timestamp=3)
client2.put("k2", 30, timestamp=4)
client1.put("k2", 40, timestamp=5)
client1.put("k2", 40, timestamp=5)
except Exception as err:
print(f"Ошибка вызова client.put(...) {err.__class__}: {err}")
sys.exit(1)
expected_metrics = {
"k1": [(1, 0.25), (2, 2.156), (3, 0.35)],
"k2": [(4, 30.0), (5, 40.0)],
}
try:
metrics = client1.get("*")
if metrics != expected_metrics:
print(
f"client.get('*') вернул неверный результат. Ожидается: {expected_metrics}. Получено: {metrics}"
)
sys.exit(1)
except Exception as err:
print(f"Ошибка вызова client.get('*') {err.__class__}: {err}")
sys.exit(1)
expected_metrics = {"k2": [(4, 30.0), (5, 40.0)]}
try:
metrics = client2.get("k2")
if metrics != expected_metrics:
print(
f"client.get('k2') вернул неверный результат. Ожидается: {expected_metrics}. Получено: {metrics}"
)
sys.exit(1)
except Exception as err:
print(f"Ошибка вызова client.get('k2') {err.__class__}: {err}")
sys.exit(1)
try:
result = client1.get("k3")
if result != {}:
print(
f"Ошибка вызова метода get с ключом, который еще не был добавлен. Ожидается: пустой словарь. Получено: {result}"
)
sys.exit(1)
except Exception as err:
print(
f"Ошибка вызова метода get с ключом, который еще не был добавлен: {err.__class__} {err}"
)
sys.exit(1)
print("Похоже, что все верно!")
if __name__ == "__main__":
run("127.0.0.1", 8888)

@ -0,0 +1,157 @@
"""
Это unittest для тестирования вашего класса Client из задания.
Для запуска теста на локальном компьютере разместите код unittest-та
и код решения в одном каталоге. Запустите тест при помощи команды:
python -m unittest test_week5.py
Обратите внимание на то, что ваш модуль должен называться client.py.
Это не обязательное требование, если вы назвали мобуль по-другому, то
просто измените его импорт в строке 25 на:
from you_module_name import Client, ClientError
Модуль должен содержать классы Client и ClientError.
Этот unittest поможет вам выполнить задание.
Успехов!
"""
import unittest
from unittest.mock import patch
from collections import deque
# импорт модуля с решением
from client import Client, ClientError
class ServerSocketException(Exception):
pass
class ServerSocket:
"""Mock socket module"""
def __init__(self):
self.response_buf = deque()
self.rsp_map = {
b'put test 0.5 1\n': b'ok\n\n',
b'put test 2.0 2\n': b'ok\n\n',
b'put test 0.4 2\n': b'ok\n\n',
b'put load 301 3\n': b'ok\n\n',
b'get key_not_exists\n': b'ok\n\n',
b'get test\n': b'ok\n'
b'test 0.5 1\n'
b'test 0.4 2\n\n',
b'get get_client_error\n': b'error\nwrong command\n\n',
b'get *\n': b'ok\n'
b'test 0.5 1\n'
b'test 0.4 2\n'
b'load 301 3\n\n',
}
def sendall(self, data):
return self.send(data)
def send(self, data):
if data in self.rsp_map:
self.response_buf.append(self.rsp_map[data])
else:
raise ServerSocketException(f"запрос не соответствует протоколу: {data}")
def recv(self, bytes_count):
try:
rsp = self.response_buf.popleft()
except IndexError:
raise ServerSocketException("нет данных в сокете для чтения ответа")
return rsp
@classmethod
def create_connection(cls, *args, **kwargs):
return cls()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def __getattr__(self, feature):
"""ignore socket.connect, soket.bind, etc..."""
pass
class TestClient(unittest.TestCase):
@classmethod
@patch("socket.create_connection", ServerSocket.create_connection)
@patch("socket.socket", ServerSocket.create_connection)
def setUpClass(cls):
cls.client = Client("127.0.0.1", 10000, timeout=2)
@patch("socket.create_connection", ServerSocket.create_connection)
@patch("socket.socket", ServerSocket.create_connection)
def test_client_put(self):
metrics_for_put = [
("test", 0.5, 1),
("test", 2.0, 2),
("test", 0.4, 2),
("load", 301, 3),
]
for metric, value, timestamp in metrics_for_put:
try:
self.client.put(metric, value, timestamp)
except ServerSocketException as exp:
message = exp.args[0]
self.fail(f"Ошибка вызова client.put("
f"'{metric}', {value}, timestamp={timestamp})\n{message}")
@patch("socket.create_connection", ServerSocket.create_connection)
@patch("socket.socket", ServerSocket.create_connection)
def test_client_get_key(self):
try:
rsp = self.client.get("test")
except ServerSocketException as exp:
message = exp.args[0]
self.fail(f"Ошибка вызова client.get('test')\n{message}")
metrics_fixture = {
"test": [(1, .5), (2, .4)],
}
self.assertEqual(rsp, metrics_fixture)
@patch("socket.create_connection", ServerSocket.create_connection)
@patch("socket.socket", ServerSocket.create_connection)
def test_client_get_all(self):
try:
rsp = self.client.get("*")
except ServerSocketException as exp:
message = exp.args[0]
self.fail(f"Ошибка вызова client.get('*')\n{message}")
metrics_fixture = {
"test": [(1, .5), (2, .4)],
"load": [(3, 301.0)]
}
self.assertEqual(rsp, metrics_fixture)
@patch("socket.create_connection", ServerSocket.create_connection)
@patch("socket.socket", ServerSocket.create_connection)
def test_client_get_not_exists(self):
try:
rsp = self.client.get("key_not_exists")
except ServerSocketException as exp:
message = exp.args[0]
self.fail(f"Ошибка вызова client.get('key_not_exists')\n{message}")
self.assertEqual({}, rsp, "check rsp eq {}")
@patch("socket.create_connection", ServerSocket.create_connection)
@patch("socket.socket", ServerSocket.create_connection)
def test_client_get_client_error(self):
try:
self.assertRaises(ClientError,
self.client.get, "get_client_error")
except ServerSocketException as exp:
message = exp.args[0]
self.fail(f"Некорректно обработано сообщение сервера об ошибке: {message}")

@ -0,0 +1,47 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8178efeb",
"metadata": {},
"source": [
"# Ваши впечатления о курсе #"
]
},
{
"cell_type": "markdown",
"id": "cd79e073",
"metadata": {},
"source": [
"Поздравляем Вас с прохождением курса \"Основы программирования на Python\"!\n",
"\n",
"Расскажите, как вам курс? Что было полезного и интересного? Мы будем вам очень признательны, если вы заполните небольшой опрос, который займет 5-7 минут. Ваши ответы помогут нам усовершенствовать курс!\n",
"\n",
"Также нам всегда очень приятно получать вашу обратную связь в виде отзывов и историй.\n",
"\n",
"Спасибо вам, что были с нами!"
]
}
],
"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
}

@ -0,0 +1,234 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "cb9b2246",
"metadata": {},
"source": [
"# Сервер для приема метрик #"
]
},
{
"cell_type": "markdown",
"id": "db5acdef",
"metadata": {},
"source": [
"В предыдущем блоке вы разработали клиентское сетевое приложение — клиента для сервера метрик, который умеет отправлять и получать всевозможные метрики. Пришло время финального задания — нужно реализовать серверную часть самостоятельно.\n",
"\n",
"Как обычно вам необходимо разработать программу в одном файле-модуле. Сервер должен соответствовать протоколу, который был описан в задании к предыдущему блоку. Он должен уметь принимать от клиентов команды `put` и `get`, разбирать их, и формировать ответ согласно протоколу. По запросу `put` требуется сохранять метрики в структурах данных в памяти процесса. По запросу `get` сервер обязан отдавать данные в правильной последовательности.\n",
"\n",
"На верхнем уровне вашего модуля должна быть объявлена функция `run_server(host, port)` — она принимает адрес и порт, на которых должен быть запущен сервер.\n",
"\n",
"Для проверки правильности решения мы воспользуемся своей реализацией клиента и будем отправлять на ваш сервер `put` и `get` запросы, ожидая в ответ правильные данные от сервера (согласно объявленному протоколу). Все запросы будут выполняться с таймаутом — сервер должен отвечать за приемлемое время.\n",
"\n",
"Сервер должен быть готов к неправильным командам со стороны клиента и отдавать клиенту ошибку в формате, оговоренном в протоколе. В таком случае работа сервера не должна завершаться аварийно.\n",
"\n",
"В последнем блоке мы с вами разбирали пример tcp-сервера на `asyncio`:\n",
"\n",
"```python\n",
"import asyncio\n",
"\n",
"\n",
"class ClientServerProtocol(asyncio.Protocol):\n",
" def connection_made(self, transport):\n",
" self.transport = transport\n",
"\n",
" def data_received(self, data):\n",
" resp = process_data(data.decode())\n",
" self.transport.write(resp.encode())\n",
"\n",
"\n",
"loop = asyncio.get_event_loop()\n",
"coro = loop.create_server(\n",
" ClientServerProtocol,\n",
" \"127.0.0.1\", 8181\n",
")\n",
"\n",
"server = loop.run_until_complete(coro)\n",
"\n",
"try:\n",
" loop.run_forever()\n",
" \n",
"except KeyboardInterrupt:\n",
" pass\n",
"\n",
"server.close()\n",
"\n",
"loop.run_until_complete(server.wait_closed())\n",
"loop.close()\n",
"```\n",
"\n",
"Данный код создает tcp-соединение для адрса `127.0.0.1:8181` и слушает все входящие запросы. При подключении клиента будет создан новый экземпляр класса `ClientServerProtocol`, а при поступлении новых данных вызовется метод этого объекта - `data_received`. Внутри `asyncio.Protocol` спрятана вся магия обработки запросов через корутины, остается реализовать протокол взаимодействия между клиентом и сервером.\n",
"\n",
"Этот код может использоваться как основа для реализации сервера. Это не обязательное требование. Для реализации задачи вы можете использовать любые вызовы из стандартной библиотеки Python 3. Сервер должен обрабатывать запросы от нескольких клиентов одновременно.\n",
"\n",
"В процессе разработки сервера для тестирования работоспособности вы можете использовать клиент, написанный в предыдущем блоке.\n",
"\n",
"Давайте еще раз посмотрим на текстовый протокол в действии при использовании утилиты `telnet`:\n",
"\n",
"```\n",
"$: telnet 127.0.0.1 8888\n",
"Trying 127.0.0.1...\n",
"Connected to localhost.\n",
"Escape character is '^]'.\n",
"> get test_key\n",
"< ok\n",
"< \n",
"> got test_key\n",
"< error\n",
"< wrong command\n",
"< \n",
"> put test_key 12.0 1503319740\n",
"< ok\n",
"< \n",
"> put test_key 13.0 1503319739\n",
"< ok\n",
"< \n",
"> get test_key \n",
"< ok\n",
"< test_key 13.0 1503319739\n",
"< test_key 12.0 1503319740\n",
"< \n",
"> put another_key 10 1503319739\n",
"< ok\n",
"< \n",
"> get *\n",
"< ok\n",
"< test_key 13.0 1503319739\n",
"< test_key 12.0 1503319740\n",
"< another_key 10.0 1503319739\n",
"< \n",
"```\n",
"\n",
"Также вы можете воспользоваться вспомогательным скриптом, который использует реализацию клиента в пятом блоке, для локального тестирования написанного вами сервера:\n",
"\n",
"```python\n",
"\"\"\"\n",
"Это вспомогательный скрипт для тестирования сервера из задания в 6 блоке.\n",
"\n",
"Для запуска скрипта на локальном компьютере разместите рядом файл client.py,\n",
"где содержится код клиента, который реализован в задание блока 5.\n",
"\n",
"Сначала запускаете ваш сервер на адресе 127.0.0.1 и порту 8888, а затем \n",
"запускаете этот скрипт.\n",
"\n",
"\"\"\"\n",
"\n",
"import sys\n",
"\n",
"from client import Client, ClientProtocolError, ClientSocketError\n",
"\n",
"\n",
"def run(host, port):\n",
"\n",
" client1 = Client(host, port, timeout=5)\n",
" client2 = Client(host, port, timeout=5)\n",
"\n",
" try:\n",
" client1.send(\"malformed command test\\n\")\n",
" client2.send(\"malformed command test\\n\")\n",
"\n",
" except ClientSocketError as err:\n",
" print(f\"Ошибка общения с сервером: {err.__class__}: {err}\")\n",
" sys.exit(1)\n",
"\n",
" except ClientProtocolError:\n",
" pass\n",
"\n",
" else:\n",
" print(\n",
" \"Неверная команда, отправленная серверу, должна возвращать ошибку протокола\"\n",
" )\n",
" sys.exit(1)\n",
"\n",
" try:\n",
" client1.put(\"k1\", 0.25, timestamp=1)\n",
" client2.put(\"k1\", 2.156, timestamp=2)\n",
" client1.put(\"k1\", 0.35, timestamp=3)\n",
" client2.put(\"k2\", 30, timestamp=4)\n",
" client1.put(\"k2\", 40, timestamp=5)\n",
" client1.put(\"k2\", 40, timestamp=5)\n",
"\n",
" except Exception as err:\n",
" print(f\"Ошибка вызова client.put(...) {err.__class__}: {err}\")\n",
" sys.exit(1)\n",
"\n",
" expected_metrics = {\n",
" \"k1\": [(1, 0.25), (2, 2.156), (3, 0.35)],\n",
" \"k2\": [(4, 30.0), (5, 40.0)],\n",
" }\n",
"\n",
" try:\n",
" metrics = client1.get(\"*\")\n",
" if metrics != expected_metrics:\n",
" print(\n",
" f\"client.get('*') вернул неверный результат. Ожидается: {expected_metrics}. Получено: {metrics}\"\n",
" )\n",
" sys.exit(1)\n",
"\n",
" except Exception as err:\n",
" print(f\"Ошибка вызова client.get('*') {err.__class__}: {err}\")\n",
" sys.exit(1)\n",
"\n",
" expected_metrics = {\"k2\": [(4, 30.0), (5, 40.0)]}\n",
"\n",
" try:\n",
" metrics = client2.get(\"k2\")\n",
" if metrics != expected_metrics:\n",
" print(\n",
" f\"client.get('k2') вернул неверный результат. Ожидается: {expected_metrics}. Получено: {metrics}\"\n",
" )\n",
" sys.exit(1)\n",
"\n",
" except Exception as err:\n",
" print(f\"Ошибка вызова client.get('k2') {err.__class__}: {err}\")\n",
" sys.exit(1)\n",
"\n",
" try:\n",
" result = client1.get(\"k3\")\n",
" if result != {}:\n",
" print(\n",
" f\"Ошибка вызова метода get с ключом, который еще не был добавлен. Ожидается: пустой словарь. Получено: {result}\"\n",
" )\n",
" sys.exit(1)\n",
"\n",
" except Exception as err:\n",
" print(\n",
" f\"Ошибка вызова метода get с ключом, который еще не был добавлен: {err.__class__} {err}\"\n",
" )\n",
" sys.exit(1)\n",
"\n",
" print(\"Похоже, что все верно!\")\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" run(\"127.0.0.1\", 8888)\n",
"\n",
"```\n",
"\n",
"Успехов в разработке!"
]
}
],
"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
}

@ -0,0 +1,70 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "09a9d448",
"metadata": {},
"source": [
"# Финальный проект #"
]
},
{
"cell_type": "markdown",
"id": "aef4130c",
"metadata": {},
"source": [
"На последнем блоке курса вам предстоит реализовать полноценное серверное приложение для получения метрик от множества клиентов."
]
},
{
"cell_type": "markdown",
"id": "e4c42e55",
"metadata": {},
"source": [
"## Задачи обучения ##\n",
"\n",
"- Создать своё серверное сетевое приложение."
]
},
{
"cell_type": "markdown",
"id": "31c4d4fe",
"metadata": {},
"source": [
"## Оглавление ##"
]
},
{
"cell_type": "markdown",
"id": "878ed185",
"metadata": {},
"source": [
"### Финальный проект ###\n",
"\n",
"- [Сервер для приема метрик](1.%20Финальный%20проект/Сервер%20для%20приема%20метрик.ipynb)\n",
"- [Ваши впечатления о курсе](1.%20Финальный%20проект/Ваши%20впечатления%20о%20курсе.ipynb)"
]
}
],
"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
}
Loading…
Cancel
Save