Python предоставляет большой набор встроенных функций, некоторыми из которых мы уже пользовались, например, print() для вывода данных на экран или len() для определения количества элементов в коллекции. Исходный код большинства встроенных функций написан на том же языке, что и сам интерпретатор Python. И если наиболее используемым интерпретатором является CPython, написанный на C, то и код многих функций написан на C. Но существует поддержка и других языков, например, интерпретатор Jython использует язык программирования Java, а IronPython – C#.

Функции, написанные один раз, можно использовать бесконечное количество раз без необходимости повторного написания их кода. Это позволяет следовать одному из основополагающих принципов программирования DRY (от англ. Don’t Repeat Yourself – Не повторяйтесь), который заключается в том, что следует избегать избыточного дублирования кода.

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

Вызов функции

Мы уже использовали множество функций, вызывая их по имени с помощью круглых скобок и передавая им аргументы.

Например, мы можем определить длину коллекции с помощью функции len() и вывести её на экран, используя функцию print():

socks = ["Синий носок 1", "Синий носок 2", "Красный носок"]
print(len(socks))
# Вывод: 3

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

say_something = print

Здесь переменной say_something присваивается значение объекта print, поэтому теперь её можно вызывать как функцию с помощью скобок:

say_something("Дождь, дождь, дождь,", end="\n||||||||||||||||||\n")
say_something("Поливай нашу рожь!")
# Вывод: Дождь, дождь, дождь,
# Вывод: ||||||||||||||||||
# Вывод: Поливай нашу рожь!

Как и в функции print(), в новой функции say_something() можно использовать параметр end для изменения вывода в конце строки.

Параметры и аргументы

Когда мы говорим о функциях, часто упоминаются два термина: аргументы и параметры.

Параметры – это переменные, которые функция ожидает получить. Например, у функции print() есть параметр end, который определяет, чем заканчивается вывод.

Аргументы – это конкретные значения параметров, которые мы передаём функции. Например, в вызове print("Привет", end="!") строки "Привет" и "!" – это аргументы.

Совсем как в математике: у нас есть функция f(x) = x + 1, где x – это параметр функции, а его конкретное значение, например, 1 – это её аргумент.

Решение задачи с большим количеством повторений

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

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

# 5 "А" класс
students_5A = {
    "Циолковский Константин Эдуардович": {
        "Математика": 5,
        "Русский язык": 5,
    },
    "Третьяков Павел Михайлович": {
        "Математика": 5,
        "Русский язык": 5,
    },
    "Достоевский Фёдор Михайлович": {
        "Математика": 4,
        "Русский язык": 5,
    }
}

# 5 "Б" класс
students_5B = {
    "Королёв Сергей Павлович": {
        "Математика": 5,
        "Русский язык": 5,
    },
    "Романов Пётр Алексеевич": {
        "Математика": 4,
        "Русский язык": 4,
    },
    "Романова Екатерина Алексеевна": {
        "Математика": 4,
        "Русский язык": 4,
    }
}

Для каждого класса создадим пустые списки excellent_students_5A и excellent_students_5B, в который будем добавлять ФИО отличников, перебирая словари в цикле:

excellent_students_5A = []
for student, grades in students_5A.items():
    for grade in grades.values():
        if grade != 5:
            break
    else:
        excellent_students_5A.append(student)

excellent_students_5B = []
for student, grades in students_5B.items():
    for grade in grades.values():
        if grade != 5:
            break
    else:
        excellent_students_5B.append(student)

Здесь оператор break прерывает внутренний цикл for, если оценка не равна 5, а блок else, в котором ФИО ученика добавляется в список, выполняется только в том случае, если цикл не был прерван.

Выведем значения полученных списков на экран и убедимся в том, что всё работает:

print(excellent_students_5A)
# Вывод: ['Циолковский Константин Эдуардович', 'Третьяков Павел Михайлович']
print(excellent_students_5B)
# Вывод: ['Королёв Сергей Павлович']

Однако в школе обычно несколько десятков классов, тогда получается нам придётся столько раз продублировать блок кода с циклом?

Нет, если мы напишем собственную функцию.

Создание функции

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

def имя_функции(параметры):
    тело_функции
    return значение

Для определения функции используется ключевое слово def, за которым идёт имя функции.

Правила именования функций соответствуют правилам именования переменных, однако так как функция производит какое-то действие, то и начинать её имя рекомендуется с глагола, например, get (с англ. – получить), show (с англ. – показать) или open (с англ. – открыть).

Так как мы хотим создать функцию для получения списка отличников, то назовём её get_excellent_students.

Определившись с названием функции, создадим пустую функцию с помощью оператора-заполнителя pass:

def get_excellent_students():
    pass

Оператор pass ничего не делает и обычно применяется в качестве заглушки. Его можно использовать не только при создании функций, но и, например, в циклах или условиях. Такие пустые конструкции ничего не делают, но с их помощью можно заранее определить структуру программы.

В круглых скобках после имени функции указываются параметры функции – переменные, которые принимают значения, переданные при вызове функции. Наша функция get_excellent_students() должна принимать словарь данными по ученикам и их оценках, поэтому назовём его students:

def get_excellent_students(students):
    pass

При этом наличие параметров в функции вовсе не обязательно, а также они могут иметь значения по умолчанию.

Блок кода, который выполняет функция, называется телом функции. Для нас им является написанный ранее цикл, но вместо словаря с заданным классом используется словарь students, который функция принимает при вызове:

def get_excellent_students(students):
    excellent_students = []
    for student, grades in students.items():
        for grade in grades.values():
            if grade != 5:
                break
        else:
            excellent_students.append(student)

Сейчас функция создаёт список отличников в классе, но не возвращает его. Поэтому если вызовем эту функцию, передав словарь students_5A, и выведем результат на экран, то увидим None:

excellent_students_5A = get_excellent_students(students_5A)
print(excellent_students_5A)
# Вывод: None

Это связано с тем, что если функция не возвращает значение с помощью оператора return, то по умолчанию она возвращает None, поэтому переменной excellent_students_5A присваивается значение None.

Функцию, не возвращающую значение, можно использовать для совершения каких-либо действий, например, добавим в неё функцию print() для вывода списка отличников на экран:

def get_excellent_students(students):
    excellent_students = []
    for student, grades in students.items():
        for grade in grades.values():
            if grade != 5:
                break
        else:
            excellent_students.append(student)
    print(excellent_students)

Тогда при вызове функции get_excellent_students() мы не получим сам список отличников, так как функция его не вернёт, но увидим его на экране:

get_excellent_students(students_5A)
# Вывод: ['Циолковский Константин Эдуардович', 'Третьяков Павел Михайлович']

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

def get_excellent_students(students):
    excellent_students = []
    for student, grades in students.items():
        for grade in grades.values():
            if grade != 5:
                break
        else:
            excellent_students.append(student)
    return excellent_students

Теперь для получения списка отличников в классе мы можем просто использовать функцию get_excellent_students (), передав ей словарь с учениками и их оценками:

excellent_students_5A = get_excellent_students(students_5A)
print(excellent_students_5A)
# Вывод: ['Циолковский Константин Эдуардович', 'Третьяков Павел Михайлович']
 
excellent_students_5B = get_excellent_students(students_5B)
print(excellent_students_5B)
# Вывод: ['Королёв Сергей Павлович']

И больше не нужно писать цикл для каждого класса.

Особенности возвращения результата функции

Оператор return может возвращать не только переменную, но и вычисляемое выражение. Например, создадим простую функцию get_circle_area(), которая принимает радиус круга и возвращает его площадь:

def get_circle_area(radius):
    return 3.14 * radius ** 2


area = get_circle_area(3)
print(area)
# Вывод: 28.26
Робот Кеша читает

В Python, согласно стандарту PEP8, функции и классы принято отделять от другого кода двумя пустыми строками. Внутри функции пустые строки используются для разделения логических блоков кода. Эти рекомендации относятся к стилю оформления кода и не влияют на работу программы.

Также оператор return останавливает выполнение функции и любой код, написанный после него, не будет выполнен:

def get_circle_area_2(radius):
    area = 3.14 * radius ** 2
    return area
    print("Эта функция вычисляет площадь круга")  # Не будет выведено на экран


area2 = get_circle_area_2(1)
print(area2)
# Вывод: 3.14

Мы можем использовать это для реализации более сложной логики работы функции:

def get_circle_area_3(radius):
    if radius < 0:
        return

    area = 3.14 * radius ** 2
    return area


area3 = get_circle_area_3(-1)
print(area)
# Вывод: None

Оператор return завершает функцию, поэтому нет необходимости заключать вычисление радиуса круга в блок else. Если функции будет передано отрицательное число, то она вернёт None, так как return без указания возвращаемого значения возвращает None.

Функция может возвращать несколько значений, если перечислить их через запятую. Например, создадим функцию, которая считает не только площадь круга, но и его длину:

def get_circle_parameters(radius):
    area = 3.14 * radius ** 2
    length = 2 * 3.14 * radius
    return area, length

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

area, length = get_circle_parameters(3)
print(area, length)
# Вывод: 28.26 18.84

Но если мы присвоим возвращаемые значения одной переменной, то при выводе её на экран увидим кортеж:

cirlce_parameters = get_circle_parameters(3)
print(cirlce_parameters)
# Вывод: (28.26, 18.84)

То есть на самом деле оператор return всегда возвращает только одно значение: число, список, кортеж или другой тип данных, а значения, перечисленные через запятую после слова return создают кортеж.

Аннотации типов

Ранее мы уже говорили про аннотации типов в Python, однако они могут указывать не только типы переменных при их создании, но и типы параметров и возвращаемого значения. И если параметры функции – это переменные, тип которых указывается через уже знакомую конструкцию имя: тип, то тип возвращаемого значения указывается перед двоеточием и после стрелки: -> тип.

Название типа данных совпадает с названием функции, которая преобразует объект в этот тип данных. Например, тип данных list (список) и функция list() или тип данных str (строка) и функция str(), и так далее.

Коллекции обычно включают в себя данные одного или нескольких типов, которые могут указываться в квадратных скобках после названия типа переменной или возвращаемого значения: коллекция[тип1, тип2,..]. Например, функция get_excellent_students() возвращает список строк, поэтому можем записать возвращаемое значение как list[str].

Однако для словарей в квадратных скобках через запятую указывается сначала тип ключа, а затем тип значения: dict[тип_ключа, тип_значения].

Давайте ещё раз посмотрим на словарь (dict), передаваемый функции get_excellent_students():

students_5A = {
    "Циолковский Константин Эдуардович": {
        "Математика": 5,
        "Русский язык": 5,
    },
    "Третьяков Павел Михайлович": {
        "Математика": 5,
        "Русский язык": 5,
    },
    "Достоевский Фёдор Михайлович": {
        "Математика": 4,
        "Русский язык": 5,
    }
}

Здесь ключами являются строки (str), однако значениями – другие словари (dict), в которых ключи – это строки (str), а значения – целые числа (int). Поэтому полный тип значения students можно записать как dict[str, dict[str, int]].

Теперь используем аннотации типов и укажем значения каких типов принимает и возвращает наша первая функция:

def get_excellent_students(students: dict[str, dict[str, int]]) -> list[str]:
    excellent_students = []
    for student, grades in students.items():
        for grade in grades.values():
            if grade != 5:
                break
        else:
            excellent_students.append(student)
    return excellent_students

Аннотации типов делают код функции более читаемым и понятным, особенно для других программистов или при повторном чтении собственного кода.

Также функция может принимать и возвращать значения разных типов. Например, созданная ранее функция get_circle_area_3() принимает целое число (int) или число с плавающей точкой (float), а возвращает None или или число с плавающей точкой (float). В таком случае используется оператор |, который позволяет перечислить несколько типов данных:

def get_circle_area_3(radius: int | float) -> float | None:
    if radius < 0:
        return

    area = 3.14 * radius ** 2
    return area

Примеры

Пример 1. Проверка надежности пароля

Функция is_strong_password() принимает пароль password и проверяет, соответствует ли он минимальным требованиям безопасности: длина не менее 8 символов, наличие хотя бы одной буквы и одной цифры. Для этого она последовательно проверяет каждый символ пароля на принадлежность к буквам и цифрам, а также длину строки, возвращая True только если все условия выполнены:

def is_strong_password(password: str) -> bool:
    """Проверяет, является ли пароль надежным: длина не менее 8 символов, 
    содержит хотя бы одну букву и одну цифру.
    
    Параметры:
        password: Пароль.
    
    Возвращает:
        True (пароль надёжный) или False (пароль не надёжный).
    """
    # Проверяем наличие хотя бы одной буквы в пароле
    has_letter = any(char.isalpha() for char in password)
    # Проверяем наличие хотя бы одной цифры в пароле
    has_digit = any(char.isdigit() for char in password)
    
    # Пароль считается надёжным, если:
    #   - Длина >= 8 символов
    #   - Содержит хотя бы одну букву
    #   - Содержит хотя бы одну цифру
    return len(password) >= 8 and has_letter and has_digit


print(is_strong_password("qwerty"))  # Слишком короткий и нет цифр
print(is_strong_password("Secure123"))  # Удовлетворяет всем требованиям

Вывод:

False
True

Пример 2. Счастливый билет

Билет, полученный в транспорте, считается счастливым, если сумма первых трёх цифр равняется сумме последних трёх цифр. Например, для билета «123321» сумма 1 + 2 + 3 равна 3 + 2 + 1. Функция is_lucky_ticket() принимает номер билета ticket_number и возвращает True, если билет считается счастливым. Если номер билета некорректен или суммы цифр не совпадают, функция возвращает False:

def is_lucky_ticket(ticket_number: str) -> bool:
    """Проверяет, является ли билет счастливым.
    Счастливым считается билет, где сумма первых трех цифр
    равна сумме последних трех цифр.
    
    Параметры:
        ticket_number: Номер билета.
        
    Возвращает:
        True (билет счастливый) или False (билет несчастливый).
    """
    # Проверим, что номер билета состоит ровно из 6 цифр
    if len(ticket_number) != 6 or not ticket_number.isdigit():
        return False  # Номер билета некорректен
    
    # Разделим номер билета на две части
    first_half = ticket_number[:3]  # Первые три цифры
    second_half = ticket_number[3:] # Последние три цифры
    
    # Считаем сумму цифр в первой половине
    sum_first = sum(int(digit) for digit in first_half)
    # Считаем сумму цифр во второй половине
    sum_second = sum(int(digit) for digit in second_half)
    
    # Возвращаем True, если суммы равны, иначе False
    return sum_first == sum_second


print(is_lucky_ticket("123456"))  # (1 + 2 + 3 ≠ 4 + 5 + 6)
print(is_lucky_ticket("123321"))  # (1 + 2 + 3 = 3 + 2 + 1)

Вывод:

False
True

Пример 3. Шифр Цезаря

Шифр Цезаря – это шифр подстановки, названный в честь Юлия Цезаря, который по легенде использовал его для секретной переписки. Суть шифра заключается в том, что каждая буква в исходном тексте заменяется на другую букву, находящуюся в алфавите на фиксированном расстоянии от неё, которое называется ключом шифра. Например, если ключ шифра равен 3, то каждая буква будет сдвинута на 3 позиции, поэтому буква «А» превратится в «Г», «Б» в «Д», и так далее. В конце алфавита счёт зацикливается, поэтому «Я» при том же сдвиге станет «В».

Функция caesar_cipher() шифрует текст с помощью шифра Цезаря, сдвигая каждую букву строки text на заданное количество позиций shift. При этом сохраняется регистр букв, а небуквенные символы остаются без изменений:

def caesar_cipher(text: str, shift: int) -> str:   
    """Шифрует русский текст шифром Цезаря.

    Параметры:
        text: Исходный текст для шифрования.
        shift: Величина сдвига (ключ шифрования).

    Возвращает:
        Зашифрованный текст.
    """
    lowercase = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
    uppercase = lowercase.upper()
    alphabet_length = len(lowercase)  # 33 буквы

    # Приводим сдвиг к диапазону [0, 32]
    shift = shift % alphabet_length

    encrypted_text = []
    for char in text:
        if char in lowercase + uppercase:
            letters = lowercase if char in lowercase else uppercase
            # Находим индекс символа
            old_index = letters.index(char)
            # Вычисляем новый индекс
            new_index = (old_index + shift) % alphabet_length
            # Добавляем зашифрованный символ
            encrypted_text.append(letters[new_index])
        else:
            # Не буквенный символ (например, пробел) оставляем без измене-ний
            encrypted_text.append(char)

    return "".join(encrypted_text)


secret = caesar_cipher("У тебя всё получится! Ты молодец!", 5)
print(f"Зашифрованный текст: {secret}")

Здесь оператор остатка от деления (%) используется для того, чтобы реализовать циклический переход от буквы «Я» к «А».

Вывод:

Зашифрованный текст: Ш чйёд жцк фуршьнчцд! Ча суруийы!

Итоги

  • Для вызова функции используются круглые скобки, без них функцию можно передать как объект другой функции или присвоить переменной.
  • Параметры функции – это переменные, которые функция ожидает получить.
  • Аргументыфункции – это конкретные значения, которые переданы функции.
  • Функция – это именованный блок кода, который выполняет определенную задачу и может быть вызван по имени из других частей программы.
  • Правила именования функций соответствуют правилам именования переменных, однако рекомендуется начинать её имя с глагола, указывающего на то, что функция делает.
  • Тело функции – блок кода, который выполняет функция.
  • Оператор pass обозначает блок кода, который ничего не делает, то есть применяется в качестве заглушки. Он может применяться в функциях, а также условиях и циклах.
  • Оператор return завершает выполнение функции и возвращает одно значение. Если значение не указано, то он возвращает None.
  • Аннотации типов могут использоваться для указания типов параметров и возвращаемого значения.
  • Тип параметра указывает конструкция имя: тип, а тип возвращаемого значения – конструкция -> тип, написанная перед двоеточием, обозначающим начало блока с телом функции.
  • Если функция принимает или возвращает значения разных типов, то они разделяются с помощью оператора |.
  • Тип элементов коллекций указывается в квадратных скобках после названия типа коллекции: коллекция[тип1, тип2,..]. Однако тип элементов словаря указывается как dict[тип_ключа, тип_значения].

Задания для самопроверки

1. Как связаны параметры и аргументы и функции?

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

2. Напишите функцию greet(), которая ничего не возвращает, а выводит на экран строку "Добро пожаловать в мир функций!". Затем вызовите эту функцию.

def greet() -> None:
    print("Добро пожаловать в мир функций!")

greet()
# Вывод: Добро пожаловать в мир функций!

3. Напишите функцию filter_positive(numbers), которая принимает список чисел numbers и возвращает новый список, содержащий только положительные числа (строго больше 0). Вызовите эту функцию для списка [1, -12, 31, -4, -5, 0, 24] и выведите результат на экран.

def filter_positive(numbers: list[int]) -> list[int]:
    positive_numbers = [num for num in numbers if num > 0]
    return positive_numbers


data = [1, -12, 31, -4, -5, 0, 24]
print(filter_positive(data))
# Вывод: [1, 31, 24]

4. Напишите функцию get_unique(numbers), которая принимает список чисел numbers и возвращает список без повторяющихся значений. Вызовите эту функцию для списка [100, 100, 200, 1, 100, -2, -2] и выведите результат на экран.

def get_unique(numbers: list[int]) -> list[int]:
    # Преобразуем в множество, а потом снова в список
    result = list(set(numbers))
    return result


numbers = [100, 100, 200, 1, 100, -2, -2]
print(get_unique(numbers))
# Вывод: [200, 1, 100, -2]

5. Напишите функцию get_initials(full_name), которая принимает строку full_name с полным именем и возвращает инициалы в виде строки. Например, для строки "Репин Илья Ефимович" она должна вернуть строку "РЕК". Вызовите эту функцию для строки "Айвазовский Иван Константинович" и выведите результат на экран.

def get_initials(full_name: str) -> str:
    # Извлекаем первую букву каждого слова
    initials_list = [name[0].upper() for name in full_name.split()]
    # Объединяем список в строку
    result = "".join(initials_list)
    return result


name_string = "Айвазовский Иван Константинович"
print(get_initials(name_string))
# Вывод: АИК