{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Python для сбора данных\n", "\n", "*Алла Тамбовцева, НИУ ВШЭ*\n", "\n", "## Lambda-функции, исключения, assert." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Lambda-функции в Python\n", "\n", "Иногда возникает необходимость написать небольшую функцию, которая будет использоваться один раз, да и то в сочетании с какими-нибудь другими функциями или методами. В таком случае совсем необязательно создавать эту функцию с помощью `def` и присваивать ей имя. Можно воспользоваться специальными lambda-функциями, которые создаются в одну строчку и могут существовать без имени (их ещё назвают *анонимными*).\n", "\n", "Для начала создадим какую-нибудь не-анонимную функцию, чтобы познакомиться с синтаксисом. Пусть это будет функция `sq`, которая принимает на вход какое-то число `x` и возвращает его квадрат." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "sq = lambda x: x ** 2 # готово" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Использовать эту функцию можно как функции, заданные через `def`:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "100" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sq(10)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "49" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sq(-7)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если функция принимает на вход более одного аргумента, они просто перечисляются через запятую после `lambda`:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "my_sum = lambda x, y: x + y" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "7" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_sum(0, 7)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "13" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_sum(6, 7)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь посмотрим на сочетание lambda-функций с встроенными функциями Python. Lambda-функции часто используют в сочетании с функциями `filter()` и `map()`, которые позволяют отфильтровывать значения списков/кортежей или преобразовывать их элементы (более быстрая и удобная альтернатива списковым включениям).\n", "Если вы помните, когда мы обсуждали списки, мы говорили про метод `.index()`, который возвращает индекс какого-то элемента по его значению. Проблема в том, что в случае списка с повторяющимися значениями он возвращает только первое совпадение:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "L = [0, 2, 7, 5, 4, 3, 2]\n", "L.index(2) # только первая 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если мы захотим таким образом вернуть все элементы, удовлетворяющие некоторому условию, ничего не получится (понадобятся циклы, условия, списковые включения). А можно просто написать lambda-функцию, которая будет возвращать значения True или False в зависимости от соответствия условию, а потом передать полученный результат функции `filter()`, которая отберет элементы с True: " ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[7, 5, 4]" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(filter(lambda x: x > 3, L)) # элементы списка L больше 3" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 2, 4, 2]" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(filter(lambda x: x % 2 == 0, L)) # четные элементы списка L" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Условия можно совмещать:" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[5, 4]" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(filter(lambda x: (x > 3) & (x < 7), L)) # 3 < x < 7" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Обратите внимание: перед `filter()` всегда добавляется `list()`. Это нужно для того, чтобы увидеть результаты явно и получить их в виде списка. Иначе мы просто получим «закрытый» объект типа `filter()` ( вспомните историю про `zip()`)." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "filter(lambda x: x > 3, L)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь попробуем совместить lambda-функцию и функцию `map()`, которая позволяет получать новый список на основе старого, преобразовывая его элементы:" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 4, 49, 25, 16, 9, 4]" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(map(lambda x: x ** 2, L)) # квадраты элементов списка L" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Исключения" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Что происходит, когда мы просим Python выполнить недопустимую операцию? Например, возвести строку в квадрат, найти натуральный логарифм от отрицательного числа, вывести на экран элемент списка с несуществующим индексом... Мы получаем сообщение об ошибке (на самом деле, *исключение*):" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Enter a number: tuy\n" ] }, { "ename": "ValueError", "evalue": "invalid literal for int() with base 10: 'tuy'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0minput\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Enter a number: \"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: invalid literal for int() with base 10: 'tuy'" ] } ], "source": [ "x = input(\"Enter a number: \") # ввели не число\n", "n = int(x) # превратить tuy в целое число невозможно" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В примере выше мы получили исключение типа `ValueError`, которое свидетельствует о том, что Python не может выполнить запрашиваемое действие с указанным значением. Несмотря на то, что на экран выводится краткое пояснение ошибки, полученное сообщение очень специфическое и вряд ли поможет пользователю. Как быть? Написать программу, которая будет «ловить» ошибки (исключения) и в случае, если ошибка допущена, выводить пользователю более доступное сообщение о том, что именно пошло не так. \n", "\n", "Структура конструкций с исключениями напоминает устройство конструкций *if-else*: есть «развилка», если действие допустимо, оно выполняется (движемся по первой ветке «развилки»), если нет – не выполняется, но ошибки при этом не высвечивается. Конструкция для «ловли» исключений – конструкция *try-except*. В ветке с *try* указывается набор действий, которые Python в любом случае пытается выполнить, в *except* – набор действий, которые должны быть исполнены, если реализовать операции в *try* не получилось. В отличие от конструкции *if-else*, где у *else* нет никакого условия, *except* не может существовать сам по себе. При нём всегда есть тип исключения, на который программа должна реагировать. В нашем случае это `ValueError`:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Enter a number: tyu\n", "Incorrect input. Not a number.\n" ] } ], "source": [ "x = input(\"Enter a number: \")\n", "try:\n", " n = int(x)\n", " print(\"Ok\") # если все хорошо\n", "except ValueError:\n", " print(\"Incorrect input. Not a number.\") # если пользователь ввел нечто, что привело к ValueError" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Иногда помимо основных действий добавляют оператор `pass`, который ничего не делает, а просто означает отсутсиве действия. Если в код выше мы допишем `pass`, ничего не изменится:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = input(\"Enter a number: \")\n", "try:\n", " n = int(x)\n", " print(\"Ok\")\n", "except ValueError:\n", " print(\"Incorrect input. Not a number.\")\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Может возникнуть вопрос, а зачем тогда он вообще нужен? Например, затем, чтобы обозначить отсутствие действия и избежать добавления ненужных строк кода. Если мы хотим, чтобы в случае столкновения с `ValueError` Python НЕ ДЕЛАЛ НИЧЕГО (и не выводил никаких предупреждений на экран), оставив после выражения с `except` пустоту, мы получим ошибку синтаксиса. А если просто допишем `pass`, то всё будет, как надо:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Enter a number: егн\n" ] } ], "source": [ "x = input(\"Enter a number: \")" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "try:\n", " n = int(x)\n", " print(\"Ok\")\n", "except ValueError:\n", " pass # молча" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В конструкцию *try-except* можно включать более одного `except`, и эти исключения могут быть разными. Рассмотрим пример: пользователь вводит число `x`, а ему возвращается число `1/x` (обратное). Какие проблемы могут возникнуть? Во-первых, пользователь может ввести не число (как в примере выше). Во-вторых, пользователь может ввести 0, а делить на 0, как известно, нельзя." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "ename": "ZeroDivisionError", "evalue": "division by zero", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;36m1\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero" ] } ], "source": [ "1/0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Получили исключение типа `ZeroDivisionError`. Теперь мы можем расширить нашу конструкцию и по-разному реагировать на разные типы ошибок:" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Enter a number: uio\n", "Not a valid number.\n" ] } ], "source": [ "x = input(\"Enter a number: \")\n", "try:\n", " n = int(x)\n", " res = 1/n\n", " print(\"Ok\")\n", " print(res)\n", "except ValueError:\n", " print(\"Not a valid number.\")\n", "except ZeroDivisionError:\n", " print(\"Cannot divide by zero.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Конструкции с `assert` для отладки кода" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Допустим, нам нужно написать функцию, которая склеивает список строк в одну строку с помощью дефиса. Мы знаем, что, например, результат применения функции к списку `['a', 'b']` должен быть `'a-b'`. Напишем функцию `my_merge()`: " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "def my_merge(L):\n", " return \"-\".join(L)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь проверим, что она работает так, как нужно: из списка `['a', 'b']` делает строку `'a-b'`. Сравним возращаемое функцией значение и строку `'a-b'`, и если результат не совпадает с этой строкой, будем выводить сообщение \"Something went wrong\"." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "assert my_merge([\"a\", \"b\"]) == \"a-b\", \"Something went wrong\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В нашем случае все работает корректно, и сообщения об ошибке мы не увидели. Что было бы, если бы мы забыли нашу задачу и написали бы функцию неправильно, указав в `.join()` символ `'+'` в качестве разделителя?" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def my_merge(L):\n", " return \"+\".join(L)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "ename": "AssertionError", "evalue": "Something went wrong", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0mmy_merge\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"a\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"b\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"a-b\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"Something went wrong\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAssertionError\u001b[0m: Something went wrong" ] } ], "source": [ "assert my_merge([\"a\", \"b\"]) == \"a-b\", \"Something went wrong\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Мы получили сообщение об ошибке, причём это сообщение, написанное пользователем, всплывает как «настоящее» встроенное уведомление об ошибке в Python. Для тестирования работы функции обычно пишут несколько условий с `assert`, чтобы учесть разные типы ошибок, связанные с работой функции. Это может быть и неверный формат выдачи результатов, и ошибки, возникающие в случае, когда аргументы функции равны определенным значениям и прочее (вспомните автоматические тесты в домашних заданиях по курсу, они как раз включаеют разные варианты работы функции)." ] } ], "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.5" } }, "nbformat": 4, "nbformat_minor": 1 }