За семь уроков вы разобрали def и return, параметры со значениями по умолчанию, *args и **kwargs, лямбды и функции высшего порядка, сортировку с key, рекурсию и обработку исключений. По отдельности каждая тема понятна, но настоящая задача обычно требует смешать несколько приёмов сразу и разложить решение на маленькие функции. Этот урок — без новой теории: только частые ошибки модуля и три задачи, где всё изученное складывается вместе.
Частые ошибки модуля
Забыли return — функция молча возвращает None
def square(x):
y = x * x
result = square(4)
print(result)
y, но без return y наружу ничего не передаётся. Если функция должна что-то отдавать вызывающему коду — в ней обязательно должен быть return с этим значением.Изменяемое значение по умолчанию копится между вызовами
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a"))
print(add_item("b"))
items=[] создаётся один раз при определении функции, и все вызовы без своего аргумента делят один и тот же список. Надёжнее задать None и создавать список внутри функции: if items is None: items = [].Рекурсия без базового случая
def countdown(n):
print(n)
countdown(n - 1)
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".