EduBrick
классная работа

Функции

Программирование на Python: от нуля до олимпиад

0/18решено 0 из 18до зачёта осталось 10

Чему научитесь

  • Давать имя действию и вызывать его вместо повторного кода
  • Передавать данные аргументами и возвращать результат
  • Писать функции-проверки и вызывать их прямо в условии
  • Понимать, что видно внутри функции, а что нет

Как устроено занятие

Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.

Дальше 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. Но вы это увидите сами: если в программе один и тот же кусок встречается дважды, значит функция напрашивалась.

лестница задач
развёрнутый ответ

Функция, которая ничего не вернула

Задача: «дано число nn; посчитайте, сколько чисел от 1 до nn имеют чётную сумму цифр».

Ученик написал:

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. Объясните, что произошло, почему в выводе при этом появились лишние числа, и как исправить.

Войдите, чтобы ответить.