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

Функции: def и return

Пишем код один раз — используем сколько угодно раз. Разбираемся с def, return и областью видимости

Теория~30 минутНовичокDRYdef / returnВозврат NoneНесколько returnScope / LEGB

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

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

До сих пор, если один и тот же расчёт был нужен в разных местах программы, приходилось копировать строки кода. Это неудобно: если в расчёте найдётся ошибка, придётся искать и исправлять её во всех копиях сразу. Программисты называют это нарушением принципа DRY — сокращение от английского Don't Repeat Yourself, «не повторяйся». Если один и тот же код встречается в программе два раза и больше — это сигнал, что здесь нужна функция.

Без функции — одна и та же логика повторяется трижды:

width1, height1 = 5, 3
area1 = width1 * height1
print(f"Площадь: {area1}")

width2, height2 = 10, 4
area2 = width2 * height2
print(f"Площадь: {area2}")

width3, height3 = 7, 2
area3 = width3 * height3
print(f"Площадь: {area3}")

С функцией — логика написана один раз:

def print_area(width, height):
    area = width * height
    print(f"Площадь: {area}")

print_area(5, 3)
print_area(10, 4)
print_area(7, 2)

Как объявить функцию: слово def

Функция, которую вы «записываете в тетрадь» — это объявление с ключевым словом def (сокращение от английского define, «определить»). Вот самая простая функция, какая только бывает:

def greet():
    print("Привет!")

Разберём эту запись по кусочкам:

def Ключевое слово: говорит Python «дальше идёт определение новой функции»
greet Имя функции. Правила именования те же, что у переменных: буквы, цифры, подчёркивание, не начинать с цифры
() Круглые скобки нужны всегда, даже пустые. Внутри них позже появятся параметры
: Двоеточие закрывает заголовок функции — так же, как в if и for
print("Привет!") Тело функции, с отступом в 4 пробела. Всё, что имеет отступ, принадлежит функции

Важно: строка def greet(): сама по себе ничего не печатает. Python на этом шаге только запоминает: «есть функция greet, вот что она делает». Тело выполнится позже — когда функцию вызовут.

Определение и вызов — разные вещи

Начинающие часто путают эти два шага, поэтому зафиксируем разницу чётко. Определение (def имя(): с телом) — это запись рецепта. Вызов (имя()) — это команда «выполни рецепт прямо сейчас». Функцию можно определить один раз, а вызвать сколько угодно раз в любом месте программы после определения.

def greet():           # определение — Python запомнил функцию
    print("Привет!")

greet()                 # вызов — печатает "Привет!"
greet()                 # вызов — печатает "Привет!" ещё раз
greet()                 # и ещё раз

Вызов — это всегда имя со скобками: greet(). Если написать greet без скобок, Python не запустит функцию, а просто покажет ссылку на неё, что-то вроде <function greet at 0x...>. Скобки — это и есть команда «выполнить».

Оператор return: отдаём результат наружу

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

Это принципиально отличается от print(), который просто показывает текст на экране и на этом всё заканчивается — результат нигде не сохраняется.

def square(n):
    return n * n

result = square(5)            # result = 25
print(result)                 # 25
print(square(3) + square(4))  # 9 + 16 = 25

Теперь сравним с версией, где вместо return стоит print:

def square_bad(n):
    print(n * n)     # только печатает, ничего не отдаёт наружу

result = square_bad(5)   # на экране появится: 25
print(result)             # None — результата, с которым можно работать, нет

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

Функция без return возвращает None

Если в теле функции нет ни одного return, Python всё равно что-то возвращает — специальное значение None, которое означает «ничего». Это официальный термин: None — единственный объект своего типа NoneType, и он не равен ни нулю, ни пустой строке, ни False.

def say_hello():
    print("Привет!")
    # return здесь нет

result = say_hello()   # на экране: Привет!
print(result)           # None
print(type(result))     # <class 'NoneType'>

Отсюда частая ошибка новичков: написать x = my_func(), забыв поставить return внутри my_func, а потом удивляться, почему x оказался None. Если результат неожиданно None — первым делом проверьте, есть ли в функции return.

Несколько return: ранний выход

В одной функции может быть несколько операторов return. Как только выполнение доходит до первого же return — функция немедленно завершается, а всё, что написано ниже, просто не выполняется. Такой приём называется ранним выходом (по-английски early return).

def classify_age(age):
    if age < 0:
        return "Некорректный возраст"
    if age < 18:
        return "Ребёнок"
    if age < 60:
        return "Взрослый"
    return "Пожилой"

print(classify_age(-5))   # Некорректный возраст
print(classify_age(10))   # Ребёнок
print(classify_age(35))   # Взрослый
print(classify_age(70))   # Пожилой

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

Возврат нескольких значений сразу

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

def min_max(numbers):
    return min(numbers), max(numbers)   # это кортеж из двух значений

result = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print(result)          # (1, 9)
print(type(result))    # <class 'tuple'>

minimum, maximum = min_max([3, 1, 4, 1, 5, 9, 2, 6])   # сразу распаковали
print(minimum)   # 1
print(maximum)   # 9

return a, b и return (a, b) — одно и то же: кортеж создаёт именно запятая, а не скобки. Такой приём удобен, когда нужно вернуть сразу и результат, и, например, признак успеха операции.

Локальные переменные и область видимости

Представьте, что каждая функция — это отдельная комната со своими вещами. То, что лежит внутри комнаты, не видно снаружи, и наоборот — вещи из коридора сами не оказываются в комнате. Переменная, созданная внутри функции, существует только внутри неё: снаружи её просто нет. Это называется официальным термином область видимости (scope).

def calculate():
    secret = 42
    result = secret * 2
    return result

print(calculate())   # 84
print(secret)         # NameError: name 'secret' is not defined

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

x = 100   # переменная уровня модуля

def show():
    x = 999   # другая переменная, живёт только внутри show
    print(x)   # 999

show()
print(x)   # 100 — внешняя x не изменилась

Совет: такая изоляция — это удобство, а не помеха. Не пытайтесь её обойти без необходимости: правильный способ передать данные в функцию — через параметры, а получить результат обратно — через return.

Ключевое слово global

Функция без проблем может читать переменную снаружи себя. А вот изменить такую переменную изнутри функции просто присваиванием не получится — Python решит, что вы создаёте новую локальную переменную с тем же именем. Чтобы явно сказать «я хочу изменить именно внешнюю переменную», используют слово global:

counter = 0   # переменная уровня модуля

def increment():
    global counter    # без этой строки Python создал бы локальную counter
    counter += 1

increment()
increment()
increment()
print(counter)   # 3

Без строки global counter код упал бы с ошибкой: Python решил бы, что counter внутри функции локальная, и попытался бы прочитать её раньше, чем она создана.

Когда стоит пользоваться global? Почти никогда. Если код тянется к global на каждом шагу — это повод пересмотреть структуру: передавайте данные через параметры и забирайте результат через return. Так проще понять, откуда пришло значение и кто его меняет.

Правило LEGB: как Python ищет имена

Когда в коде встречается имя переменной, Python должен понять, о какой именно переменной идёт речь, если таких имён вложено несколько. Он ищет по уровням строго по порядку, и это правило называется LEGB — по первым буквам английских названий уровней.

L Local Внутри той функции, где мы сейчас находимся — ищем здесь первым делом
E Enclosing Внутри функции, в которую текущая функция вложена (для функции внутри функции)
G Global Переменные, объявленные на верхнем уровне файла, вне всех функций
B Built-in Встроенные имена самого Python: print, len, range, True, None
x = "уровень модуля"

def outer():
    x = "объемлющий уровень"

    def inner():
        x = "локальный уровень"
        print(x)          # найдёт "локальный уровень" — он ближе всего

    inner()
    print(x)               # найдёт "объемлющий уровень"

outer()
print(x)                   # найдёт "уровень модуля"

Уровень Built-in — это то, что Python знает по умолчанию: print, len, int и так далее. Именно поэтому не стоит называть свои переменные list или print: вы перекроете встроенное имя своей переменной, и вызвать оригинальную функцию станет нельзя.

Частые ошибки

Забыть скобки при вызове. Без скобок функция не выполняется — вы просто получаете ссылку на неё, а не результат:

def square(n):
    return n * n

result = square      # забыли скобки
print(result)          # <function square at 0x...>, а не число

Спутать print и return. Функция, которая только печатает результат, не отдаёт его наружу — работать с таким «результатом» дальше нельзя:

def double(n):
    print(n * 2)    # только печатает

total = double(5) + double(3)   # TypeError: печать возвращает None

Неправильный отступ у тела функции. Тело обязано иметь отступ, иначе Python не поймёт, что относится к функции:

def greet():
print("Привет!")   # нет отступа

# IndentationError: expected an indented block

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

  • DRY: функция — это код с именем, который можно вызывать сколько угодно раз, не копируя его
  • def имя(): определяет функцию, вызов — это всегда имя() со скобками
  • return отдаёт значение обратно туда, откуда вызвали функцию; без него функция возвращает None
  • Несколько return — функция завершается на первом же достигнутом return, это называют ранним выходом
  • return a, b возвращает кортеж; его можно сразу распаковать: x, y = func()
  • Локальные переменные существуют только внутри своей функции и пропадают после её завершения
  • global позволяет изменить внешнюю переменную изнутри функции, но злоупотреблять им не стоит
  • LEGB — порядок поиска имён: Local, затем Enclosing, затем Global, затем Built-in

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

6 вопросов

Площадь прямоугольника

Напишите функцию rectangle_area(width, height), которая принимает ширину и высоту прямоугольника и возвращает его площадь.

Например, rectangle_area(5, 3) должна вернуть 15.

Максимум из трёх чисел без max

Premium

Простое число

Premium