$ sudo teach IT
Модуль 6 · Функции · Урок 6.8

Практикум: функции

Три задачи на всё, что вы прошли о функциях, плюс разбор ловушек, в которые чаще всего попадают новички.

Практика~40 минутНовичок

За семь уроков вы разобрали def и return, параметры со значениями по умолчанию, *args и **kwargs, лямбды и функции высшего порядка, сортировку с key, рекурсию и обработку исключений. По отдельности каждая тема понятна, но настоящая задача обычно требует смешать несколько приёмов сразу и разложить решение на маленькие функции. Этот урок — без новой теории: только частые ошибки модуля и три задачи, где всё изученное складывается вместе.

Частые ошибки модуля

Забыли return — функция молча возвращает None

def square(x):
    y = x * x

result = square(4)
print(result)
Выведет None, а не 16. Внутри функции число посчиталось и легло в переменную y, но без return y наружу ничего не передаётся. Если функция должна что-то отдавать вызывающему коду — в ней обязательно должен быть return с этим значением.

Изменяемое значение по умолчанию копится между вызовами

def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item("a"))
print(add_item("b"))
Второй вызов выведет ['a', 'b'], а не ['b']. Список items=[] создаётся один раз при определении функции, и все вызовы без своего аргумента делят один и тот же список. Надёжнее задать None и создавать список внутри функции: if items is None: items = [].

Рекурсия без базового случая

def countdown(n):
    print(n)
    countdown(n - 1)
Программа упадёт с RecursionError. Функция вызывает саму себя без условия, при котором это прекратится, поэтому n уходит в минус, пока Python не оборвёт слишком глубокий стек вызовов. У любой рекурсии должна быть проверка, после которой она возвращает результат без нового вызова себя, например if n <= 0: return.

Путают позиционные и именованные аргументы при *args и **kwargs

def profile(name, **info):
    print(name, info)

profile("Аня", age=20, city="Уфа")
Ошибки нет — выведет Аня {'age': 20, 'city': 'Уфа'}. Важен порядок: обычные параметры вроде name идут первыми, *args собирает лишние позиционные аргументы, **kwargs — лишние именованные, и в определении функции он всегда идёт последним.

Приём: разбивайте задачу на маленькие функции

Как только условие задачи описывает больше одного действия — «проверь, потом преобрази, потом собери ответ» — не пытайтесь уместить всё в одну функцию с длинным телом. Заведите отдельную маленькую функцию под каждый шаг и вызывайте их одну из другой: так проще проверить каждый кусочек отдельно и легче найти, где именно ошибка.

def shift_letter(letter, alphabet, shift):
    old_index = alphabet.find(letter)
    new_index = (old_index + shift) % len(alphabet)
    return alphabet[new_index]

print(shift_letter("y", "abcdefghijklmnopqrstuvwxyz", 3))
alphabet.find(letter) — ищет позицию буквы в строке-алфавите. Для "y" это 24 (счёт с нуля).
(old_index + shift) % len(alphabet) — сдвигает позицию вперёд на shift букв. Остаток от деления на длину алфавита (26) нужен, чтобы при выходе за "z" счёт «заворачивался» обратно к "a": 24 + 3 = 27, а 27 % 26 = 1.
alphabet[new_index] — берёт букву по новому индексу. Индекс 1 в алфавите — это "b", поэтому функция вернёт "b".

Функция решает только одну маленькую задачу — сдвиг одной буквы. Вызывая её по очереди для каждого символа строки и склеивая результаты, можно получить сдвиг всего текста. Тот же принцип пригодится и в двух других задачах: разбейте условие на понятные шаги и на каждый заведите свою функцию.

Что важно запомнить

Без return функция всегда отдаёт None, даже если внутри что-то посчитано.
Изменяемое значение по умолчанию (список, словарь) создаётся один раз и делится между вызовами — безопаснее использовать None и создавать объект внутри функции.
У рекурсии обязателен базовый случай, иначе — RecursionError.
alphabet.find(ch) и alphabet[index] — пара для задач «сдвинуть букву»: находим позицию, меняем число, берём букву по новому индексу; % заворачивает индекс обратно в границы строки.
Большую задачу проще решать через несколько маленьких функций, каждая из которых отвечает за один шаг, чем через одну длинную.

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

7 вопросов

Шифр Цезаря

Напишите функцию caesar_encrypt(text, shift), которая шифрует текст сдвигом каждой буквы по алфавиту вперёд на shift позиций (шифр Цезаря). В text встречаются только строчные латинские буквы и пробелы, пробелы шифровать не нужно — они остаются на месте.

Используйте строку-алфавит "abcdefghijklmnopqrstuvwxyz": находите позицию буквы в ней через .find(), прибавляйте shift, берите остаток от деления на 26, чтобы сдвиг заворачивался с конца алфавита в начало, и возвращайте букву по новому индексу.

Например, caesar_encrypt("hello", 3) должна вернуть "khoor", а caesar_encrypt("xyz", 3) — "abc".

Проверка email

Premium

Сжатие строки

Premium