Модуль 5 · Урок 5.2
Аргументы, несколько результатов и lambda
Значения по умолчанию, именованные аргументы, кортеж результатов, функция как значение и договорённость функции.
Цену со скидкой считают постоянно, но скидка есть не всегда. Писать каждый раз final_price(200, 0) неудобно, а иногда функция должна вернуть сразу два ответа. У функции есть договорённость — что она принимает, что возвращает и меняет ли данные; значения по умолчанию, именованные аргументы и несколько результатов делают её удобной.
Разберём это на цене со скидкой final_price(price, discount=0), а в конце оформим функцией сквозную задачу курса.
Значения по умолчанию
Параметр со значением по умолчанию можно не передавать, а аргумент можно назвать по имени.
def final_price(price, discount=0): return price * (100 - discount) / 100 print(final_price(200))print(final_price(200, 10))print(final_price(discount=10, price=200))discount=0 в определении — значение по умолчанию. В вызове именованные аргументы уточняют, какому параметру соответствует значение. Вот как параметры получают значения в трёх вызовах:
discount берёт значение по умолчанию 0.Обычные позиционные аргументы записывают перед именованными. Одному параметру нельзя передать значение дважды:
def final_price(price, discount=0): return price * (100 - discount) / 100 print(final_price(200, price=100))Значения по умолчанию вычисляются при выполнении определения, а не заново для каждого вызова. Поэтому изменяемый список в параметре по умолчанию будет общим между вызовами:
def add_item_bad(item, items=[]): items.append(item) return items print(add_item_bad(1))print(add_item_bad(2))Второй вызов увидел единицу из первого: список по умолчанию создан один раз. Нажми «Запуск» и пройди программу по шагам: во втором вызове items ведёт к тому же списку, что и в первом, — он создан, когда выполнилось def, и уже содержит 1.
Правильный приём — None по умолчанию и новый список внутри:
def add_item(item, items=None): if items is None: items = [] items.append(item) return items print(add_item(1))print(add_item(2))None обозначает отсутствие переданного списка. В таком случае создаётся новый. Если пользователь явно передал список, функция изменит именно его. Эта договорённость должна быть понятна из описания.
Несколько результатов
Функция может вернуть несколько значений — на самом деле один кортеж.
def quotient_and_remainder(a, b): return a // b, a % b q, r = quotient_and_remainder(23, 5)print(q, r)Фактически возвращается один кортеж, который распаковывается. Для этой конкретной операции уже существует divmod(a, b), но пример показывает общую механику.
return отдаёт два значения как один кортеж (4, 3), а строка вызова раскладывает его: q получает 4, r — 3.Тонкости:звёздочки и косая черта в параметрах
Параметр *values собирает сколько угодно позиционных аргументов в кортеж, **options — именованные аргументы в словарь. Звёздочки здесь — не умножение и не степень:
def total_of(*values): return sum(values) print(total_of(2, 3, 5))print(total_of())Звёздочка без имени заставляет следующие параметры передавать по имени: def show(value, *, label="Результат"):. Косая черта / делает предыдущие параметры только позиционными. Такие формы встречаются в документации; для своих первых функций они не нужны.
Функция как значение
Функцию можно передать как значение; lambda создаёт короткую функцию из одного выражения. Имя функции можно передать другой функции. Без скобок это сама функция; со скобками — результат её вызова.
def last_digit(number): return abs(number) % 10 rule = last_digitprint(rule(47))short_rule = lambda number: abs(number) % 10print(short_rule(-123))lambda number: abs(number) % 10 создаёт короткую функцию с одним выражением. Она делает то же, что last_digit, только у неё нет собственного имени. Она автоматически возвращает результат выражения. Несколько инструкций в lambda не помещают: используй def. Чаще всего lambda передают другой функции — например, как правило сортировки в уроке о сортировке.
rule = last_digit — второе имя той же функции, как b = a у списков. Функция из lambda собственного имени не имеет, её связали с именем short_rule.Договорённость функции
Хорошая функция начинается с договорённости о входе, результате и особых случаях. Сначала сформулируй договорённость: что функция принимает, что возвращает, какие случаи допускаются, меняет ли аргументы. Например: «принимает целое число, возвращает сумму цифр его модуля».
def digit_sum(number): """Вернуть сумму десятичных цифр модуля целого числа.""" number = abs(number) total = 0 while number > 0: total += number % 10 number //= 10 return total print(digit_sum(-5072), digit_sum(0))Первая строка текста в теле функции — строка документации, или docstring. Её может показывать help(digit_sum). Тройные кавычки позволяют записывать многострочный текст; это не особый вид комментария.
return после цикла
Возврат ставят после цикла, когда просмотрены все элементы. Напишем count_even(numbers), возвращающую количество чётных элементов без изменения списка:
def count_even(numbers): count = 0 for number in numbers: if number % 2 == 0: count += 1 return count print(count_even([0, 3, -4, 7]))print(count_even([]))Возврат стоит после цикла. Если поставить его внутри тела, функция закончится после первого элемента. Для пустого списка корректно возвращается 0.
Сквозная задача
Сквозная задача курса: сумма и наибольшее — одной функцией с двумя результатами. Цикл из урока о списках оформим функцией с договорённостью «принимает непустой список чисел, возвращает сумму и наибольшее»:
def total_and_best(numbers): total = 0 best = numbers[0] for x in numbers: total += x if x > best: best = x return total, best total, best = total_and_best([5, -2, 10, 7])print(total, best)return total, best возвращает один кортеж, а строка вызова раскладывает его на два имени. Слово «непустой» в договорённости важно: для пустого списка numbers[0] остановит программу с IndexError. Теперь функцию можно вызвать для любого списка — из ввода, из файла, из другой функции.
Итог
Договорённость функции — что она принимает, что возвращает и меняет ли данные — делает её удобной для любого вызова. Вот что понадобится дальше.
- По умолчанию
def f(a, b=0)—bможно не передавать;f(b=1, a=2)— аргументы по имени. - Список по умолчаниюИзменяемое значение по умолчанию общее для всех вызовов: используй
Noneи новый объект внутри. - Два результата
return a, bвозвращает кортеж, его распаковывают:q, r = f(...). total_and_bestСквозная задача функцией:return total, best, вызов раскладывает результат на два имени.- Функция как значениеФункцию передают без скобок;
lambda x: выражение— короткая функция; docstring описывает договорённость.
Проверьте себя
Ответь сначала сам, потом открой ответ.
Что вернут final_price(300) и final_price(300, discount=50)?
ОтветСкрыть ответ
300.0 и 150.0: без второго аргумента скидка по умолчанию 0.
Что напечатает total, best = total_and_best([3, 9, -1]), а затем print(total, best)?
ОтветСкрыть ответ
11 9: функция вернула кортеж (11, 9), и он разложился на два имени.
Чем отличаются rule = len и rule = len('abc')?
ОтветСкрыть ответ
В первом случае rule — сама функция len, её можно вызвать. Во втором — результат вызова, число 3.
Функцию add_item_bad из урока вызвали трижды подряд: add_item_bad(1), add_item_bad(2, []), add_item_bad(3). Что вернёт каждый вызов?
ОтветСкрыть ответ
[1], [2] и [1, 3]: второй вызов работает со своим переданным списком, а третий — снова с общим списком по умолчанию.
После rule = len что дадут rule("abc") и чем rule отличается от rule()?
ОтветСкрыть ответ
3; rule — сама функция len, а rule() — её вызов без аргумента, который вызовет TypeError.
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Напиши функцию: отрицательные элементы
Напиши функцию count_negative(numbers), которая возвращает количество отрицательных элементов и не меняет список. Вызови её для [0, -3, 4, -7] и для [], напечатав оба результата через запятую в одном print и запиши, что напечатала программа.
Запишите два числа через пробел: для первого списка, затем для пустого.
Подсказка
Счётчик внутри функции, return — после цикла.
Разбор
Например:
def count_negative(numbers): count = 0 for x in numbers: if x < 0: count += 1 return count print(count_negative([0, -3, 4, -7]), count_negative([]))Отрицательные −3 и −7; в пустом списке их нет.
Ответ: 2 0
Что напечатает программа?
def final_price(price, discount=0): return price * (100 - discount) / 100 print(final_price(500, 20))print(final_price(discount=50, price=80))Запишите напечатанные строки через пробел в порядке вывода.
Подсказка
Именованные аргументы можно писать в любом порядке; / даёт дробное число.
Разбор
500 · 80 / 100 = 400.0; 80 · 50 / 100 = 40.0.
Ответ: 400.0 40.0
Что напечатает программа?
def quotient_and_remainder(a, b): return a // b, a % b print(quotient_and_remainder(-7, 3))Запишите кортеж так, как его печатает Python.
Подсказка
// округляет вниз, остаток согласован с частным: −7 = 3 · частное + остаток.
Разбор
−7 // 3 = −3, −7 % 3 = 2; функция возвращает кортеж.
Ответ: (-3, 2)
Что напечатает программа?
def total_of(*values): return sum(values) print(total_of(5))print(total_of(*[1, 2, 3]))Запишите напечатанные строки через пробел в порядке вывода.
Подсказка
*values собирает аргументы в кортеж; *[1, 2, 3] в вызове раскладывает список на отдельные аргументы.
Разбор
Первый вызов получает кортеж (5,), второй — (1, 2, 3).
Ответ: 5 6
Что напечатает программа?
print((lambda a, b: a % b)(17, 5))Введите число.
Подсказка
lambda создаёт функцию, следующие скобки её сразу вызывают.
Разбор
17 % 5 = 2.
Ответ: 2
Что напечатает программа?
def digit_sum(number): number = abs(number) total = 0 while number > 0: total += number % 10 number //= 10 return total print(digit_sum(-9051))Введите число.
Подсказка
Функция сначала берёт модуль, потом складывает цифры.
Разбор
9 + 0 + 5 + 1 = 15.
Ответ: 15
Напиши функцию: делящиеся на d
Напиши функцию count_divisible(numbers, d=3), которая возвращает количество чисел, делящихся на d. Вызови её для [3, 5, 9, 12, 14] без второго аргумента и с d=7, напечатав оба результата в одном print и запиши, что напечатала программа.
Запишите два числа через пробел: с d по умолчанию, затем с d=7.
Подсказка
Значение по умолчанию d=3 подставляется, если второй аргумент не передан.
Разбор
Например:
def count_divisible(numbers, d=3): count = 0 for x in numbers: if x % d == 0: count += 1 return count numbers = [3, 5, 9, 12, 14]print(count_divisible(numbers), count_divisible(numbers, d=7))На 3 делятся 3, 9, 12; на 7 — только 14.
Ответ: 3 1