Функция в Python — именованный фрагмент кода, который вызывают с нужными данными. Инструкция def определяет функцию, параметры принимают входные значения, а return передаёт результат вызывающему коду. Для расчёта длительности удобно вернуть число секунд: его потом можно сложить с другим интервалом или вывести на экран.
Блоки с кнопкой запуска самостоятельны: определение функции повторяется там, где оно нужно.
Как определить и вызвать функцию через def
Пусть занятие длится 2 минуты 15 секунд. Функция переведёт этот интервал в секунды:
def duration_seconds(minutes, seconds):
total = minutes * 60 + seconds
return total
lesson = duration_seconds(2, 15)
print(lesson)
print(lesson + 30)
135
165
duration_seconds — имя функции. После имени стоят параметры в скобках и двоеточие. Две строки с отступом образуют тело: сначала вычисляется total, затем результат возвращается наружу. Строка lesson = ... уже без отступа и выполняется после определения.
Само определение не выполняет расчёт. Его запускает выражение duration_seconds(2, 15). Если написать только имя duration_seconds без скобок, получится объект функции, а не число секунд. Синтаксис определения описан в учебнике Python.
return передаёт 135, print выводит 135
lesson. Консоль получает его только после отдельного вызова print(lesson).Параметры и аргументы: в чём разница
Параметры — имена в определении: minutes и seconds. Аргументы — значения конкретного вызова: 2 и 15. При позиционной передаче значения сопоставляются слева направо; при именованной имя указано явно.
def duration_seconds(minutes, seconds):
return minutes * 60 + seconds
print(duration_seconds(2, 15))
print(duration_seconds(minutes=2, seconds=15))
print(duration_seconds(seconds=15, minutes=2))
print(duration_seconds(15, 2))
135
135
135
902
Последний вызов синтаксически верен, но означает 15 минут 2 секунды. Имена аргументов помогают избежать такой перестановки. Допустим и смешанный вызов duration_seconds(2, seconds=15).
Один параметр нельзя передать дважды. duration_seconds(2, minutes=3, seconds=15) вызовет TypeError: значение minutes уже пришло первым позиционным аргументом. Если обязательный аргумент пропущен, как в duration_seconds(2), Python тоже сообщит TypeError. Когда число входных значений заранее неизвестно, нужны отдельные приёмы с args и kwargs.
Чем return отличается от print
return завершает текущий вызов и задаёт его значение. print пишет текст в поток вывода; собственный результат print — None. Поэтому функция, которая только печатает число, неудобна для последующих расчётов.
def show_duration(minutes, seconds):
print(minutes * 60 + seconds)
def duration_seconds(minutes, seconds):
return minutes * 60 + seconds
printed = show_duration(2, 15)
returned = duration_seconds(2, 15)
print("printed:", printed)
print("returned:", returned)
assert printed is None
assert returned == 135
135
printed: None
returned: 135
Первая строка вывода появилась внутри show_duration. Она не стала значением переменной printed. Попытка посчитать printed + 30 закончится ошибкой типов.
Без return обычная функция возвращает None. То же происходит при пустом return и при достижении конца тела. Точные правила приведены в справочнике по return. Если функция должна считать длительность, возвращай число; печатай его уже там, где результат показывают пользователю.
Почему None появляется только на некоторых входах
Причина может быть в ветке, для которой забыли результат:
def broken_duration(minutes, seconds):
if minutes > 0:
return minutes * 60 + seconds
print(broken_duration(2, 15))
print(broken_duration(0, 15))
135
None
Интервал 0 минут 15 секунд допустим, но условие для него ложно. Исправление здесь — убрать ненужную проверку minutes > 0 и возвращать выражение для обоих случаев. Отрицательные значения лучше отклонять явно; ниже есть законченный вариант с проверкой входа.
Значения по умолчанию
Если остаток секунд часто равен нулю, запиши seconds=0 в определении:
def duration_seconds(minutes, seconds=0):
return minutes * 60 + seconds
print(duration_seconds(2))
print(duration_seconds(2, 15))
print(duration_seconds(minutes=0, seconds=45))
120
135
45
Значение по умолчанию используется только при отсутствии аргумента. Явный None не означает «возьми ноль»: при duration_seconds(2, None) выражение попыталось бы сложить число с None.
В def remember(value, history=[]) список создаётся при выполнении определения и переиспользуется в следующих вызовах без history. Если каждому вызову нужен новый список, создавай его внутри:
def remember(value, history=None):
if history is None:
history = []
history.append(value)
return history
print(remember(135))
print(remember(60))
shared = []
remember(135, shared)
remember(60, shared)
print(shared)
[135]
[60]
[135, 60]
Первые два вызова независимы. Последние два изменяют один список, потому что мы сами передали shared. Это различие разобрано в Python FAQ; свойства таких объектов — в статье об изменяемости.
Где живут локальные переменные
Внутреннее имя total не становится внешней переменной. Даже если снаружи есть переменная с тем же именем, обычное присваивание в функции создаёт локальную привязку:
total = 999
def duration_seconds(minutes, seconds):
total = minutes * 60 + seconds
return total
result = duration_seconds(2, 15)
print(result)
print(total)
135
999
Каждый вызов получает собственные локальные имена. Результат доступен снаружи через return, поэтому здесь нет причины добавлять global. Правила поиска имён и областей видимости закреплены в модели выполнения Python.
Локальность имени не делает копию переданного объекта. В предыдущем примере history.append(...) изменял общий список shared: два имени ссылались на один объект.
Законченная функция с проверкой входа
Зафиксируем договор: minutes — целое неотрицательное число, seconds — целый остаток от 0 до 59. Строки из формы и значения True/False этот вариант не принимает. Преобразование пользовательского ввода лучше выполнять до вызова, отдельно обрабатывая ошибки преобразования типов.
def duration_seconds(minutes, seconds=0):
"""Вернуть длительность в секундах из минут и остатка секунд."""
if type(minutes) is not int or type(seconds) is not int:
raise TypeError("minutes и seconds должны быть целыми числами")
if minutes < 0 or not 0 <= seconds < 60:
raise ValueError("нужны minutes >= 0 и 0 <= seconds < 60")
return minutes * 60 + seconds
assert duration_seconds(0) == 0
assert duration_seconds(2, 15) == 135
assert duration_seconds(1) == 60
assert duration_seconds(seconds=59, minutes=0) == 59
for arguments, expected_error in [
((-1, 0), ValueError),
((0, 60), ValueError),
(("2", 15), TypeError),
((True, 0), TypeError),
]:
try:
duration_seconds(*arguments)
except expected_error:
continue
raise AssertionError(f"ожидалась ошибка для {arguments!r}")
print("Проверены обычные, граничные и ошибочные входы")
Проверены обычные, граничные и ошибочные входы
Здесь type(value) is int выбран намеренно: условие принимает именно встроенный int, исключая bool и подклассы. При недопустимых аргументах функция выбрасывает исключение через raise; assert в примере проверяет её поведение.
Если задача допускает 90 секунд как отдельный аргумент, ограничение < 60 нужно изменить: оно описывает выбранный формат входа, а не правило языка. Для получения строки вроде 02:15 переходи к форматированию длительности. Чтобы закрепить def и return, измени функцию так, чтобы она принимала ещё и часы, и добавь проверку: 1 час 2 минуты 3 секунды дают 3 723 секунды.