Каппес Даниил Олегович

ПРАКТИКУМ PYTHON · ЗАНЯТИЕ 10

Свои функции: с параметрами и с возвратом значения

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

Чему научимся

Зачем нужны свои функции

Допустим, надо нарисовать звёздный прямоугольник 5 × 7 — а потом ещё два таких же. Код повторяется трижды, и если размеры изменятся, править придётся в трёх местах.

for _ in range(5):
    print('*' * 7)
print()
for _ in range(5):
    print('*' * 7)
print()
for _ in range(5):
    print('*' * 7)

Вместо повторения кусок выносят в функцию — и дальше зовут по имени:

def draw_box():
    for _ in range(5):
        print('*' * 7)

draw_box()
print()
draw_box()

Объявление начинается словом def (от define — определять), дальше идут имя, круглые скобки и двоеточие. Тело функции — блок с отступом, как у if и циклов.

Объявление функции не вызывает её. Написав def, вы только объяснили Python, что делать; чтобы это случилось, функцию надо вызвать по имени со скобками. И объявление должно стоять выше вызова, иначе — NameError: name … is not defined.

Имена функциям дают по тем же правилам, что и переменным: латиница, цифры и подчёркивание, не с цифры, в стиле lower_case_with_underscores. Поскольку функция что-то делает, в её имени обычно есть глагол: draw_box(), print_check(), get_days().

Если тело функции пока не написано, ставят заглушку pass — команду, которая ничего не делает. Без неё пустой блок вызовет ошибку.

10.1

Звёздный прямоугольник

Напишите функцию draw_box(), которая выводит звёздный прямоугольник размерами 14 × 10 в соответствии с образцом: рамка из звёздочек, внутри пусто.

Код-заготовка

# объявление функции
def draw_box():
    pass

# основная программа
draw_box()   # вызов функции
Формат выходных данных
14 строк по 10 символов.
Вывод
**********
*        *
*        *
*        *
*        *
*        *
*        *
*        *
*        *
*        *
*        *
*        *
*        *
**********
Подсказка 1

Строк всего 14: первая и последняя сплошные, а между ними двенадцать одинаковых.

Подсказка 2

Средняя строка — это звёздочка, восемь пробелов и снова звёздочка.

Разбор
  1. Печатаем верхнюю границу: '*' * 10.
  2. Двенадцать раз печатаем строку '*' + ' ' * 8 + '*'.
  3. Печатаем нижнюю границу.
Решение
def draw_box():
    print('*' * 10)              # верхняя граница
    for _ in range(12):          # двенадцать средних строк
        print('*' + ' ' * 8 + '*')
    print('*' * 10)              # нижняя граница

draw_box()

Ширина внутренней части — 8, а не 10: две звёздочки по краям тоже занимают место. Такую арифметику удобно проверять на бумаге: 1 + 8 + 1 = 10.

10.2

Звёздный треугольник

Напишите функцию draw_triangle(), которая выводит звёздный прямоугольный треугольник с катетами, равными 10.

Код-заготовка

# объявление функции
def draw_triangle():
    pass

# основная программа
draw_triangle()   # вызов функции
Формат выходных данных
10 строк: от одной звёздочки до десяти.
Вывод
*
**
***
****
*****
******
*******
********
*********
**********
Подсказка 1

Длина строки совпадает с её номером.

Подсказка 2

Если считать с единицы, номер и есть количество звёздочек.

Разбор
  1. Цикл идёт от 1 до 10 включительно.
  2. На каждом шаге печатаем '*' * i.
Решение
def draw_triangle():
    for i in range(1, 11):   # номер строки — он же длина строки
        print('*' * i)

draw_triangle()

Функция без параметров всегда рисует одно и то же. Через страницу мы дадим ей параметры — и одна и та же функция станет рисовать треугольники любого размера и любым символом.

Функции с параметрами

Функция становится по-настоящему полезной, когда умеет работать с разными данными. Для этого в скобках объявления перечисляют параметры:

def draw_box(height, width):
    for i in range(height):
        print('*' * width)

draw_box(5, 7)
draw_box(3, 3)

На место параметров при вызове подставляют значения — их называют аргументами. Аргументом может быть и переменная:

n = 3
m = 9
draw_box(n, m)

Разница в словах простая: параметр — это переменная в объявлении функции, аргумент — то, что передали при вызове.

Изменения параметра внутри функции не затрагивают переменную, которую передали. Если внутри написать height = 2, снаружи n останется прежним: функция работает со своей копией значения.

Параметров может быть сколько угодно; при вызове их указывают в том же порядке, что и в объявлении. Перепутать порядок — обычная ошибка, и Python её не заметит: draw_box(7, 5) нарисует другой прямоугольник, но не ошибку.

10.3

Треугольник с параметрами

Напишите функцию draw_triangle(fill, base), которая принимает два параметра: fill — символ-заполнитель и base — величину основания равнобедренного треугольника, а затем выводит его.

Гарантируется, что основание — нечётное число.

Формат входных данных
Две строки: символ-заполнитель и основание.
Формат выходных данных
Треугольник из указанного символа.
Ввод 1
*
9
Вывод 1
*
**
***
****
*****
****
***
**
*
Ввод 2
#
5
Вывод 2
#
##
###
##
#
Подсказка 1

Это задача 5.9, но с двумя отличиями: символ задаётся параметром и всё завёрнуто в функцию.

Подсказка 2

Высота пика — (base + 1) // 2.

Разбор
  1. Объявляем функцию с двумя параметрами.
  2. Считаем высоту пика.
  3. Первый цикл наращивает строки до пика, второй убывает обратно.
  4. В основной программе читаем символ и основание и вызываем функцию.
Решение
def draw_triangle(fill, base):
    top = (base + 1) // 2        # самая длинная строка — посередине

    for i in range(1, top + 1):
        print(fill * i)

    for i in range(top - 1, 0, -1):
        print(fill * i)

# основная программа
symbol = input()
size = int(input())
draw_triangle(symbol, size)

Обратите внимание: внутри функции параметры зовутся fill и base, а снаружи переменные — symbol и size. Совпадать имена не обязаны: важен порядок, в котором значения передали.

10.4

Инициалы

Напишите функцию print_fio(name, surname, patronymic), которая принимает имя, фамилию и отчество человека и печатает его инициалы — в порядке «фамилия, имя, отчество», заглавными буквами.

Формат входных данных
Три строки: имя, фамилия, отчество.
Формат выходных данных
Три заглавные буквы подряд.
Ввод 1
Александр
Пушкин
Сергеевич
Вывод 1
ПАС
Ввод 2
тимур
Гуев
ахсарбекович
Вывод 2
ГТА
Подсказка 1

Первая буква слова — это его нулевой символ.

Подсказка 2

Регистр приводится методом upper(); во втором примере имя введено с маленькой буквы, а в ответе она заглавная.

Разбор
  1. Объявляем функцию с тремя параметрами.
  2. Берём нулевой символ каждого слова и переводим в верхний регистр.
  3. Склеиваем три буквы и печатаем: сначала фамилия, потом имя, потом отчество.
Решение
def print_fio(name, surname, patronymic):
    # порядок вывода — фамилия, имя, отчество
    print(surname[0].upper() + name[0].upper() + patronymic[0].upper())

# основная программа
print_fio(input(), input(), input())

Три input() в вызове читаются по порядку — слева направо, — поэтому первым прочитается имя, вторым фамилия, третьим отчество. Именно в этом порядке они и встанут в параметры.

10.5

Сумма цифр

Напишите функцию print_digit_sum(num), которая принимает одно целое число и печатает сумму его цифр.

Формат входных данных
Одно натуральное число.
Формат выходных данных
Сумма цифр.
Ввод 1
12345
Вывод 1
15
Ввод 2
12
Вывод 2
3
Подсказка 1

Разбор числа по цифрам — приём из пятого занятия: остаток от деления на 10 и деление нацело.

Подсказка 2

Параметр можно менять внутри функции: на переменную снаружи это не повлияет.

Разбор
  1. Заводим накопитель.
  2. Пока число больше нуля: прибавляем последнюю цифру и отбрасываем её.
  3. После цикла печатаем сумму.
Решение
def print_digit_sum(num):
    total = 0
    while num > 0:
        total += num % 10   # последняя цифра
        num //= 10          # отбрасываем её
    print(total)

# основная программа
print_digit_sum(int(input()))

Внутри функции num уменьшается до нуля — и это безопасно: снаружи переданное число не изменилось. Параметр всегда работает со своей копией значения.

Локальные и глобальные переменные

Переменная, созданная внутри функции, называется локальной: она принадлежит этой функции, и снаружи её не видно.

def print_texas():
    birds = 5000
    print('В Техасе обитает', birds, 'птиц.')

def print_california():
    print('В Калифорнии обитает', birds, 'птиц.')   # ошибка: birds не видна

Поэтому в разных функциях спокойно живут переменные с одинаковыми именами — они друг о друге не знают. Параметры тоже локальные.

Переменная, объявленная в основной программе, называется глобальной: её видят и программа, и все функции.

birds = 5000          # глобальная

def print_texas():
    print('В Техасе обитает', birds, 'птиц.')

print_texas()

Результат

В Техасе обитает 5000 птиц.

Но если внутри функции создать переменную с тем же именем, глобальная станет недоступной: обращение пойдёт к локальной. А чтобы функция могла изменить глобальную переменную, её объявляют словом global — но так делают редко и осознанно.

Глобальные переменные — зло. Их значение может изменить любая строка программы, поэтому такую ошибку трудно искать; функция, опирающаяся на глобальную переменную, не переносится в другую программу; и чтобы понять кусок кода, приходится читать всю программу целиком. Обмен данными лучше вести через параметры и возвращаемые значения.

Исключение — глобальные константы: значения, которые не меняются. Такие в модуле math есть и у самого Python: math.pi и math.e.

Функции с возвратом значения

До сих пор наши функции печатали результат сами. Но чаще функция должна вернуть значение туда, откуда её вызвали, — и уже там его напечатают, положат в переменную или используют в вычислении.

Возвращает значение инструкция return:

def convert_to_celsius(temp):
    return (5 / 9) * (temp - 32)

temp = float(input())
celsius = convert_to_celsius(temp)
print(celsius)

return не только отдаёт значение, но и завершает работу функции: строки после него не выполнятся. Поэтому вместо промежуточной переменной обычно пишут выражение сразу:

def convert_to_celsius(temp):
    result = (5 / 9) * (temp - 32)
    return result       # то же самое, но на шаг длиннее

Инструкций return в функции может быть несколько — сработает первая, до которой дойдёт выполнение:

def convert_grade(grade):
    if grade >= 90:
        return 5
    elif grade >= 80:
        return 4
    elif grade >= 70:
        return 3
    elif grade >= 60:
        return 2
    else:
        return 1

Мы давно пользуемся функциями с возвратом: int() возвращает число, len() — длину, input() — строку. Разница только в том, что теперь такие функции пишем мы сами.

10.6

Конвертер километров

Напишите функцию convert_to_miles(km), которая принимает расстояние в километрах и возвращает расстояние в милях. Формула: мили = километры · 0.6214.

Код-заготовка

# объявление функции
def convert_to_miles(km):
    pass

# считываем данные
num = int(input())

# вызываем функцию
print(convert_to_miles(num))
Формат входных данных
Одно целое число — километры.
Формат выходных данных
Одно число — мили.
Ввод 1
1
Вывод 1
0.6214
Ввод 2
5
Вывод 2
3.107
Ввод 3
10
Вывод 3
6.214
Подсказка 1

Функция ничего не печатает — печатает основная программа. Значит, вместо print() внутри нужен return.

Подсказка 2

Всё тело функции — одна строка.

Разбор
  1. Умножаем параметр на 0.6214.
  2. Округляем результат до четырёх знаков — иначе вылезет хвост вещественных чисел: 5 * 0.6214 даёт 3.1069999999999998.
  3. Возвращаем результат инструкцией return.
Решение
def convert_to_miles(km):
    # round() убирает хвост: без него 5 км дали бы 3.1069999999999998
    return round(km * 0.6214, 4)

# считываем данные
num = int(input())

# вызываем функцию
print(convert_to_miles(num))

Если бы функция печатала сама, её нельзя было бы использовать в вычислениях: convert_to_miles(5) + convert_to_miles(3) не сработало бы. Возвращённое значение годится куда угодно, напечатанное — никуда. А четыре знака в round() взяты не наугад: столько же их в самом множителе 0.6214, и лишней точности в ответе всё равно не откуда взяться.

10.7

Количество дней в месяце

Напишите функцию get_days(month), которая принимает номер месяца и возвращает количество дней в нём.

Гарантируется, что аргумент от 1 до 12. Год считаем невисокосным.

Формат входных данных
Одно целое число — номер месяца.
Формат выходных данных
Количество дней.
Ввод 1
1
Вывод 1
31
Ввод 2
2
Вывод 2
28
Ввод 3
9
Вывод 3
30
Подсказка 1

Случаев всего три: февраль, месяцы по 30 дней и все остальные.

Подсказка 2

Месяцы по 30 дней — это 4, 6, 9 и 11.

Разбор
  1. Февраль проверяем первым: 28 дней.
  2. Затем месяцы с 30 днями — четыре номера через or.
  3. Всё остальное — 31 день.
Решение
def get_days(month):
    if month == 2:
        return 28
    if month == 4 or month == 6 or month == 9 or month == 11:
        return 30
    return 31

# считываем данные
num = int(input())

# вызываем функцию
print(get_days(num))

elif здесь не нужен: return сам завершает функцию, и до следующей проверки дело не дойдёт. Это частый приём — ранний выход вместо длинного каскада.

10.8

Делители 1

Напишите функцию get_factors(num), принимающую натуральное число и возвращающую список всех его делителей.

Вспомните о списочных выражениях.

Формат входных данных
Одно натуральное число.
Формат выходных данных
Список делителей.
Ввод 1
1
Вывод 1
[1]
Ввод 2
10
Вывод 2
[1, 2, 5, 10]
Подсказка 1

Делитель — число, на которое num делится без остатка.

Подсказка 2

Проверять надо все числа от 1 до самого num включительно.

Разбор
  1. Списочное выражение перебирает range(1, num + 1).
  2. Условие отбора: num % d == 0.
  3. Возвращаем получившийся список.
Решение
def get_factors(num):
    return [d for d in range(1, num + 1) if num % d == 0]

# считываем данные
n = int(input())

# вызываем функцию
print(get_factors(n))

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

10.9

Делители 2

Напишите функцию number_of_factors(num), принимающую число и возвращающую количество его делителей.

Используйте уже написанную функцию get_factors(num) из предыдущей задачи.

Формат входных данных
Одно натуральное число.
Формат выходных данных
Количество делителей.
Ввод 1
1
Вывод 1
1
Ввод 2
10
Вывод 2
4
Подсказка 1

Заново перебирать числа не нужно — список делителей уже умеет строить прежняя функция.

Подсказка 2

Осталось узнать его длину.

Разбор
  1. Вызываем get_factors(num) внутри новой функции.
  2. Возвращаем len() от полученного списка.
Решение
def get_factors(num):
    return [d for d in range(1, num + 1) if num % d == 0]

def number_of_factors(num):
    return len(get_factors(num))   # функция вызывает функцию

# считываем данные
n = int(input())

# вызываем функцию
print(number_of_factors(n))

Вот ради чего всё затевалось: готовая функция становится кирпичиком для следующей. Изменится способ искать делители — правка нужна будет только в одном месте.

10.10

Найти все вхождения

Метод find('a') возвращает местоположение только первого вхождения символа. Напишите функцию find_all(target, symbol), которая принимает строку и символ и возвращает список всех позиций этого символа в строке.

Если символ не встречается, функция должна вернуть пустой список.

Формат входных данных
Две строки: текст и искомый символ.
Формат выходных данных
Список позиций.
Ввод 1
abcdabcaaa
a
Вывод 1
[0, 4, 7, 8, 9]
Ввод 2
abcadbcaaa
e
Вывод 2
[]
Ввод 3
abcadbcaaa
d
Вывод 3
[4]
Подсказка 1

Нужны номера символов, а значит, перебор по индексам.

Подсказка 2

Отбираем те индексы, по которым стоит нужный символ.

Разбор
  1. Перебираем индексы range(len(target)).
  2. Оставляем те, где target[i] == symbol.
  3. Возвращаем список этих индексов.
Решение
def find_all(target, symbol):
    return [i for i in range(len(target)) if target[i] == symbol]

# считываем данные
s = input()
char = input()

# вызываем функцию
print(find_all(s, char))

Пустой список получается сам собой: если условие не выполнилось ни разу, в список ничего не попало. Отдельной проверки «а вдруг ничего не нашлось» не требуется.

10.11

Слияние списков

Напишите функцию merge(list1, list2), которая принимает два отсортированных по возрастанию списка целых чисел и объединяет их в один отсортированный список.

Списки могут иметь разную длину. Можно использовать списочный метод sort().

Формат входных данных
Две строки с числами через пробел.
Формат выходных данных
Один отсортированный список.
Ввод 1
1 2 3
5 6 7 8
Вывод 1
[1, 2, 3, 5, 6, 7, 8]
Ввод 2
1 7 10 16
5 6 13 20
Вывод 2
[1, 5, 6, 7, 10, 13, 16, 20]
Подсказка 1

Списки складываются оператором + — получится один список из всех элементов.

Подсказка 2

Осталось его отсортировать. Но помните: sort() ничего не возвращает.

Разбор
  1. Складываем списки в один.
  2. Сортируем его методом sort() — он меняет список на месте.
  3. Возвращаем получившийся список.
Решение
def merge(list1, list2):
    result = list1 + list2   # склеили оба списка
    result.sort()            # sort() меняет список на месте и ничего не возвращает
    return result

# считываем данные
numbers1 = [int(c) for c in input().split()]
numbers2 = [int(c) for c in input().split()]

# вызываем функцию
print(merge(numbers1, numbers2))

Написать return result.sort() было бы ошибкой: метод возвращает пустое значение, и функция отдала бы None вместо списка. Сортировка и возврат — два отдельных шага.

Итог занятия

Главное с десятого занятия. Функция объявляется def, вызывается по имени со скобками, и объявление должно стоять выше вызова. Параметры и локальные переменные живут только внутри функции; менять глобальные переменные из функции можно, но не нужно. return возвращает значение и сразу завершает функцию — а функция, которая печатает вместо возврата, годится только для печати.