К содержанию

Модуль 5 · Урок 5.2

Аргументы, несколько результатов и lambda

Значения по умолчанию, именованные аргументы, кортеж результатов, функция как значение и договорённость функции.

7 задач

Цену со скидкой считают постоянно, но скидка есть не всегда. Писать каждый раз 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))
200.0 180.0 180.0

discount=0 в определении — значение по умолчанию. В вызове именованные аргументы уточняют, какому параметру соответствует значение. Вот как параметры получают значения в трёх вызовах:

По позиции значение идёт в параметр на том же месте, по имени — в параметр с этим именем. Если значения нет, discount берёт значение по умолчанию 0.

Обычные позиционные аргументы записывают перед именованными. Одному параметру нельзя передать значение дважды:

def final_price(price, discount=0):    return price * (100 - discount) / 100 print(final_price(200, price=100))
TypeError: final_price() got multiple values for argument 'price'

Значения по умолчанию вычисляются при выполнении определения, а не заново для каждого вызова. Поэтому изменяемый список в параметре по умолчанию будет общим между вызовами:

def add_item_bad(item, items=[]):    items.append(item)    return items print(add_item_bad(1))print(add_item_bad(2))
[1] [1, 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))
[1] [2]

None обозначает отсутствие переданного списка. В таком случае создаётся новый. Если пользователь явно передал список, функция изменит именно его. Эта договорённость должна быть понятна из описания.

Несколько результатов

Функция может вернуть несколько значений — на самом деле один кортеж.

def quotient_and_remainder(a, b):    return a // b, a % b q, r = quotient_and_remainder(23, 5)print(q, r)
4 3

Фактически возвращается один кортеж, который распаковывается. Для этой конкретной операции уже существует 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())
10 0

Звёздочка без имени заставляет следующие параметры передавать по имени: 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))
7 3

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))
14 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([]))
2 0

Возврат стоит после цикла. Если поставить его внутри тела, функция закончится после первого элемента. Для пустого списка корректно возвращается 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)
20 10

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 описывает договорённость.

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1

Что вернут final_price(300) и final_price(300, discount=50)?

ОтветСкрыть ответ
Ответ

300.0 и 150.0: без второго аргумента скидка по умолчанию 0.

Вопрос 2

Что напечатает total, best = total_and_best([3, 9, -1]), а затем print(total, best)?

ОтветСкрыть ответ
Ответ

11 9: функция вернула кортеж (11, 9), и он разложился на два имени.

Вопрос 3

Чем отличаются rule = len и rule = len('abc')?

ОтветСкрыть ответ
Ответ

В первом случае rule — сама функция len, её можно вызвать. Во втором — результат вызова, число 3.

Вопрос 4

Функцию add_item_bad из урока вызвали трижды подряд: add_item_bad(1), add_item_bad(2, []), add_item_bad(3). Что вернёт каждый вызов?

ОтветСкрыть ответ
Ответ

[1], [2] и [1, 3]: второй вызов работает со своим переданным списком, а третий — снова с общим списком по умолчанию.

Вопрос 5

После rule = len что дадут rule("abc") и чем rule отличается от rule()?

ОтветСкрыть ответ
Ответ

3; rule — сама функция len, а rule() — её вызов без аргумента, который вызовет TypeError.

Практика

Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.

Напиши функцию: отрицательные элементы

Напиши функцию count_negative(numbers), которая возвращает количество отрицательных элементов и не меняет список. Вызови её для [0, -3, 4, -7] и для [], напечатав оба результата через запятую в одном print и запиши, что напечатала программа.

Запишите два числа через пробел: для первого списка, затем для пустого.

Что напечатает программа?

def final_price(price, discount=0):    return price * (100 - discount) / 100 print(final_price(500, 20))print(final_price(discount=50, price=80))

Запишите напечатанные строки через пробел в порядке вывода.

Что напечатает программа?

def quotient_and_remainder(a, b):    return a // b, a % b print(quotient_and_remainder(-7, 3))

Запишите кортеж так, как его печатает Python.

Что напечатает программа?

def total_of(*values):    return sum(values) print(total_of(5))print(total_of(*[1, 2, 3]))

Запишите напечатанные строки через пробел в порядке вывода.

Что напечатает программа?

print((lambda a, b: a % b)(17, 5))

Введите число.

Что напечатает программа?

def digit_sum(number):    number = abs(number)    total = 0    while number > 0:        total += number % 10        number //= 10    return total print(digit_sum(-9051))

Введите число.

Напиши функцию: делящиеся на d

Напиши функцию count_divisible(numbers, d=3), которая возвращает количество чисел, делящихся на d. Вызови её для [3, 5, 9, 12, 14] без второго аргумента и с d=7, напечатав оба результата в одном print и запиши, что напечатала программа.

Запишите два числа через пробел: с d по умолчанию, затем с d=7.