Чему научитесь
- Давать имя действию и вызывать его вместо повторного кода
- Передавать данные аргументами и возвращать результат
- Писать функции-проверки и вызывать их прямо в условии
- Понимать, что видно внутри функции, а что нет
Как устроено занятие
Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.
Дальше 15 задач лестницей: разминка, основа, со звёздочкой. Занятие засчитывается, когда решено 10 — остальные не пропадают и учитываются отдельно.
После занятия — вторая часть, ещё 15 задач на те же приёмы в новых сюжетах.
Сколько это займёт
Примерно час-полтора вместе с задачами. Сроков нет: можно закрыть вкладку и вернуться когда удобно — прогресс сохранится.
Когда одно и то же пишется дважды
Задача: даны два числа, у какого больше сумма цифр. Разбор цифр вы писали не раз — но теперь его надо написать два раза подряд, для одного числа и для другого.
А если чисел не два, а сто? Или если сумму цифр надо посчитать и для числа, и для его квадрата?
Здесь и появляется функция — способ дать действию имя и потом пользоваться им как готовым:
def digit_sum(value): # имя и что принимает
total = 0
while value > 0:
total += value % 10
value //= 10
return total # что отдаёт
print(digit_sum(1234)) # 10
print(digit_sum(99)) # 18
Три части: def с именем, список аргументов в скобках, return с результатом. Тело — с отступом, как у цикла.
Выигрыш не только в объёме. Разбор цифр теперь написан в одном месте: если в нём ошибка, чинить надо одну строку, а не пять копий. И читать стало легче — digit_sum(x) говорит, что происходит, а десять строк цикла надо разбирать.
Аргументы и возврат
Функция получает данные через аргументы и отдаёт результат через return.
def biggest(a, b, c): # три аргумента
best = a
if b > best:
best = b
if c > best:
best = c
return best
return не просто отдаёт значение — он немедленно завершает функцию. Строки после него не выполнятся. Этим часто пользуются:
def is_prime(value):
if value < 2:
return False # дальше проверять нечего
d = 2
while d * d <= value:
if value % d == 0:
return False # нашли делитель — ответ готов
d += 1
return True
Функция без return возвращает None. Это самая частая ошибка темы: внутри стоит print вместо return, снаружи результат пытаются использовать — и получают None.
def bad(x):
print(x * 2) # напечатал, но не вернул
y = bad(5) # напечатается 10, но в y окажется None
Правило простое: функция считает и возвращает, печатает — основная программа. Так функцию можно использовать где угодно, в том числе внутри условия.
Проверка: что окажется в y
def twice(x):
print(x * 2)
y = twice(5)
print(y)
Что напечатает программа?
Функция как проверка
Отдельный и самый полезный вид функций — те, что отвечают «да» или «нет». Возвращают они True или False, а вызывают их прямо в условии:
count = 0
for x in range(a, b + 1):
if is_prime(x):
count += 1
Сравните с тем, как это выглядело бы без функции: внутрь цикла пришлось бы вписать ещё один цикл с проверкой делителей, и главная мысль — «считаем простые» — потерялась бы среди подробностей.
Имена таким функциям принято давать вопросительные: is_prime, is_palindrome, has_digit. Тогда условие читается почти по-русски.
Две проверки лучше одной большой
Если условие сложное, его разбивают:
if is_palindrome(x) and is_prime(x):
Порядок здесь не безразличен. and не станет вычислять вторую часть, если первая уже ложна, поэтому дешёвую проверку ставят первой: если число не палиндром, простоту считать незачем. На больших отрезках это заметно ускоряет программу.
Что видно внутри функции
Переменные, созданные внутри функции, снаружи не существуют:
def compute():
total = 42
compute()
print(total) # ошибка: имя total не определено
Это не ограничение, а удобство: имена внутри функции можно выбирать свободно, не боясь задеть что-то снаружи. Одинаковые имена в разных функциях друг другу не мешают.
Наоборот тоже почти верно: прочитать внешнюю переменную функция может, а вот изменить простым присваиванием — нет. Присваивание создаст новую, свою:
count = 0
def broken():
count = 5 # создалась своя переменная
broken()
print(count) # по-прежнему 0
А вот список изменить можно — и это ловушка. В функцию передаётся ссылка, а не копия:
def spoil(items):
items[0] = 100 # меняется тот самый список
Та же история, что с b = a в занятии про списки. Если менять исходные данные не нужно, передавайте копию: spoil(a[:]).
Общее правило: всё нужное принимайте аргументами, результат отдавайте через return. Функция, которая ничего не берёт снаружи и ничего снаружи не портит, работает предсказуемо.
Проверка: сколько раз вызвана
Сколько раз будет вызвана функция is_prime?
for x in range(10, 20):
if is_prime(x) and is_prime(x + 2):
count += 1
Известно, что среди чисел от 10 до 19 простых четыре: 11, 13, 17, 19. Введите целое число.
Три ошибки этого занятия
print вместо return. Функция печатает, но ничего не возвращает, и снаружи получается None. Признак: в выводе появилось слово None или программа падает при попытке что-то сделать с результатом.
Забытый return в одной из веток. Условие разветвилось, а return написан не везде — и в этой ветке функция тихо возвращает None. Проверяйте, что каждый путь через функцию заканчивается возвратом.
Функция печатает сама. Если результат печатается внутри, функцию нельзя использовать в условии или сложить с чем-то. Печать — дело основной программы.
Как проверять себя
- вызовите функцию отдельно на двух-трёх значениях и напечатайте результат: ошибка в ней найдётся быстрее, чем в целой программе;
- крайние аргументы: 1, 0, самое большое допустимое значение;
- дважды подряд:
f(f(x))— если функция не портит ничего снаружи, это должно работать.
Практика: пятнадцать задач
Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.
Почти все задачи устроены одинаково: одно и то же вычисление применяется к десяткам чисел или ко всем числам отрезка. Написать их без функции можно — но получится длинно и неудобно, а в задачах со звёздочкой придётся дублировать целый кусок.
Проверяющая система не видит, есть ли в решении def. Но вы это увидите сами: если в программе один и тот же кусок встречается дважды, значит функция напрашивалась.
Функция, которая ничего не вернула
Задача: «дано число ; посчитайте, сколько чисел от 1 до имеют чётную сумму цифр».
Ученик написал:
def digit_sum(value):
total = 0
while value > 0:
total += value % 10
value //= 10
print(total)
n = int(input())
count = 0
for x in range(1, n + 1):
if digit_sum(x) % 2 == 0:
count += 1
print(count)
Программа завершается с ошибкой TypeError: unsupported operand type(s) for %: NoneType and int. Объясните, что произошло, почему в выводе при этом появились лишние числа, и как исправить.