Иногда возникает необходимость написать небольшую функцию, которая будет использоваться один раз, да и то в сочетании с какими-нибудь другими функциями или методами. В таком случае совсем необязательно создавать эту функцию с помощью def
и присваивать ей имя. Можно воспользоваться специальными lambda-функциями, которые создаются в одну строчку и могут существовать без имени (их ещё назвают анонимными).
Для начала создадим какую-нибудь не-анонимную функцию, чтобы познакомиться с синтаксисом. Пусть это будет функция sq
, которая принимает на вход какое-то число x
и возвращает его квадрат.
sq = lambda x: x ** 2 # готово
Использовать эту функцию можно как функции, заданные через def
:
sq(10)
100
sq(-7)
49
Если функция принимает на вход более одного аргумента, они просто перечисляются через запятую после lambda
:
my_sum = lambda x, y: x + y
my_sum(0, 7)
7
my_sum(6, 7)
13
Теперь посмотрим на сочетание lambda-функций с встроенными функциями Python. Lambda-функции часто используют в сочетании с функциями filter()
и map()
, которые позволяют отфильтровывать значения списков/кортежей или преобразовывать их элементы (более быстрая и удобная альтернатива списковым включениям).
Если вы помните, когда мы обсуждали списки, мы говорили про метод .index()
, который возвращает индекс какого-то элемента по его значению. Проблема в том, что в случае списка с повторяющимися значениями он возвращает только первое совпадение:
L = [0, 2, 7, 5, 4, 3, 2]
L.index(2) # только первая 2
1
Если мы захотим таким образом вернуть все элементы, удовлетворяющие некоторому условию, ничего не получится (понадобятся циклы, условия, списковые включения). А можно просто написать lambda-функцию, которая будет возвращать значения True или False в зависимости от соответствия условию, а потом передать полученный результат функции filter()
, которая отберет элементы с True:
list(filter(lambda x: x > 3, L)) # элементы списка L больше 3
[7, 5, 4]
list(filter(lambda x: x % 2 == 0, L)) # четные элементы списка L
[0, 2, 4, 2]
Условия можно совмещать:
list(filter(lambda x: (x > 3) & (x < 7), L)) # 3 < x < 7
[5, 4]
Обратите внимание: перед filter()
всегда добавляется list()
. Это нужно для того, чтобы увидеть результаты явно и получить их в виде списка. Иначе мы просто получим «закрытый» объект типа filter()
( вспомните историю про zip()
).
filter(lambda x: x > 3, L)
<filter at 0x10dde1eb8>
Теперь попробуем совместить lambda-функцию и функцию map()
, которая позволяет получать новый список на основе старого, преобразовывая его элементы:
list(map(lambda x: x ** 2, L)) # квадраты элементов списка L
[0, 4, 49, 25, 16, 9, 4]
Что происходит, когда мы просим Python выполнить недопустимую операцию? Например, возвести строку в квадрат, найти натуральный логарифм от отрицательного числа, вывести на экран элемент списка с несуществующим индексом... Мы получаем сообщение об ошибке (на самом деле, исключение):
x = input("Enter a number: ") # ввели не число
n = int(x) # превратить tuy в целое число невозможно
Enter a number: tuy
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-12-131599087ac0> in <module>() 1 x = input("Enter a number: ") ----> 2 n = int(x) ValueError: invalid literal for int() with base 10: 'tuy'
В примере выше мы получили исключение типа ValueError
, которое свидетельствует о том, что Python не может выполнить запрашиваемое действие с указанным значением. Несмотря на то, что на экран выводится краткое пояснение ошибки, полученное сообщение очень специфическое и вряд ли поможет пользователю. Как быть? Написать программу, которая будет «ловить» ошибки (исключения) и в случае, если ошибка допущена, выводить пользователю более доступное сообщение о том, что именно пошло не так.
Структура конструкций с исключениями напоминает устройство конструкций if-else: есть «развилка», если действие допустимо, оно выполняется (движемся по первой ветке «развилки»), если нет – не выполняется, но ошибки при этом не высвечивается. Конструкция для «ловли» исключений – конструкция try-except. В ветке с try указывается набор действий, которые Python в любом случае пытается выполнить, в except – набор действий, которые должны быть исполнены, если реализовать операции в try не получилось. В отличие от конструкции if-else, где у else нет никакого условия, except не может существовать сам по себе. При нём всегда есть тип исключения, на который программа должна реагировать. В нашем случае это ValueError
:
x = input("Enter a number: ")
try:
n = int(x)
print("Ok") # если все хорошо
except ValueError:
print("Incorrect input. Not a number.") # если пользователь ввел нечто, что привело к ValueError
Enter a number: tyu Incorrect input. Not a number.
Иногда помимо основных действий добавляют оператор pass
, который ничего не делает, а просто означает отсутсиве действия. Если в код выше мы допишем pass
, ничего не изменится:
x = input("Enter a number: ")
try:
n = int(x)
print("Ok")
except ValueError:
print("Incorrect input. Not a number.")
pass
Может возникнуть вопрос, а зачем тогда он вообще нужен? Например, затем, чтобы обозначить отсутствие действия и избежать добавления ненужных строк кода. Если мы хотим, чтобы в случае столкновения с ValueError
Python НЕ ДЕЛАЛ НИЧЕГО (и не выводил никаких предупреждений на экран), оставив после выражения с except
пустоту, мы получим ошибку синтаксиса. А если просто допишем pass
, то всё будет, как надо:
x = input("Enter a number: ")
Enter a number: егн
try:
n = int(x)
print("Ok")
except ValueError:
pass # молча
В конструкцию try-except можно включать более одного except
, и эти исключения могут быть разными. Рассмотрим пример: пользователь вводит число x
, а ему возвращается число 1/x
(обратное). Какие проблемы могут возникнуть? Во-первых, пользователь может ввести не число (как в примере выше). Во-вторых, пользователь может ввести 0, а делить на 0, как известно, нельзя.
1/0
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-16-9e1622b385b6> in <module>() ----> 1 1/0 ZeroDivisionError: division by zero
Получили исключение типа ZeroDivisionError
. Теперь мы можем расширить нашу конструкцию и по-разному реагировать на разные типы ошибок:
x = input("Enter a number: ")
try:
n = int(x)
res = 1/n
print("Ok")
print(res)
except ValueError:
print("Not a valid number.")
except ZeroDivisionError:
print("Cannot divide by zero.")
Enter a number: uio Not a valid number.
assert
для отладки кода¶Допустим, нам нужно написать функцию, которая склеивает список строк в одну строку с помощью дефиса. Мы знаем, что, например, результат применения функции к списку ['a', 'b']
должен быть 'a-b'
. Напишем функцию my_merge()
:
def my_merge(L):
return "-".join(L)
Теперь проверим, что она работает так, как нужно: из списка ['a', 'b']
делает строку 'a-b'
. Сравним возращаемое функцией значение и строку 'a-b'
, и если результат не совпадает с этой строкой, будем выводить сообщение "Something went wrong".
assert my_merge(["a", "b"]) == "a-b", "Something went wrong"
В нашем случае все работает корректно, и сообщения об ошибке мы не увидели. Что было бы, если бы мы забыли нашу задачу и написали бы функцию неправильно, указав в .join()
символ '+'
в качестве разделителя?
def my_merge(L):
return "+".join(L)
assert my_merge(["a", "b"]) == "a-b", "Something went wrong"
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-4-1228fe994359> in <module>() ----> 1 assert my_merge(["a", "b"]) == "a-b", "Something went wrong" AssertionError: Something went wrong
Мы получили сообщение об ошибке, причём это сообщение, написанное пользователем, всплывает как «настоящее» встроенное уведомление об ошибке в Python. Для тестирования работы функции обычно пишут несколько условий с assert
, чтобы учесть разные типы ошибок, связанные с работой функции. Это может быть и неверный формат выдачи результатов, и ошибки, возникающие в случае, когда аргументы функции равны определенным значениям и прочее (вспомните автоматические тесты в домашних заданиях по курсу, они как раз включаеют разные варианты работы функции).