Модуль 5 · Урок 5.1
Функции: def, return и область видимости
Своё действие с именем: параметры и аргументы, return против print, локальные имена и изменение переданных объектов.
Ты уже вызывал готовые действия: print, len, max. Когда одно и то же вычисление нужно в нескольких местах, его хочется записать один раз и вызывать по имени. Функция — именованное действие: ей передают входные данные, она возвращает результат туда, где её вызвали.
Начнём с площади прямоугольника 3 × 5, затем разберём, чем return отличается от print, почему имена внутри функции не мешают внешним и что бывает, когда функция получает список.
Функция
Функция даёт имя действию с входными данными и результатом. Посчитаем площадь прямоугольника функцией:
def rectangle_area(width, height): return width * height area = rectangle_area(3, 5)print(area)print(rectangle_area(2, 10))def создаёт функцию. rectangle_area — её имя. width и height — параметры, то есть локальные имена для входных данных. В вызове числа 3 и 5 — аргументы, конкретные переданные значения.
При выполнении def тело не запускается: оно выполняется при каждом вызове. Поэтому одну функцию можно вызвать много раз с разными аргументами: второй вызов дал 20. Вот что происходит при вызове rectangle_area(3, 5):
width и height, тело вычисляет 15, а return отдаёт его в строку вызова — она становится area = 15.Тонкости:где писать определение
Определение должно выполниться раньше вызова. Функция может вызывать другую функцию, определённую ниже в файле, — если к моменту реального вызова обе def уже выполнены. Проще всего писать все функции в начале файла.
return и print
return передаёт значение месту вызова, print только показывает текст. return завершает текущий вызов и передаёт значение тому месту, где он произошёл. print показывает текст, но не делает этот текст возвращаемым результатом. Нажми «Запуск» и пройди программу по шагам:
def square(number): return number * number result = square(4) + square(3)print(result)При вызове у функции появляются свои переменные — рамка «вызов square(4)» внутри программы; после return она исчезает, а значение возвращается в строку, где был вызов.
Если вместо return поставить print, квадраты будут видны на экране, но сложение результатов вызовов не сработает: оба вызова вернут None.
def square(number): print(number * number) result = square(4)print(result)Функция без выполненного return со значением возвращает None. Запись return без выражения тоже возвращает None. Инструкции после выполненного return в этом вызове уже не исполняются.
def describe_sign(number): if number > 0: return "положительное" if number < 0: return "отрицательное" return "ноль" print(describe_sign(-4), describe_sign(0))Ранний возврат позволяет последовательно исключить случаи без лишней вложенности: третий return выполняется, только если первые два не сработали.
Локальные имена
Имена внутри функции локальны: они не заменяют внешние. Область видимости — часть программы, в которой имя разрешается определённым способом. Параметры и присваивания внутри функции обычно создают локальные имена, существующие для данного вызова. Пройди программу по шагам:
x = 10 def calculate(): x = 3 return x + 1 print(calculate())print(x)x = 3 появляется в рамке вызова calculate, а внешнее x в программе остаётся 10: внутреннее имя не заменяет внешнее.
Если внутри функции есть присваивание имени, Python обычно считает его локальным во всём теле. Поэтому попытка сначала прочитать его, а потом присвоить приводит к ошибке:
x = 10 def show(): print(x) x = 3 show()Строка x = 3 сделала x локальным во всей функции, и print(x) обратился к локальному имени, которое ещё не получило значения. Нужно внешнее значение — передай его параметром и верни новое.
Тонкости:порядок поиска имён, global и nonlocal
Имя Python ищет по уровням: сначала в текущем вызове, затем в окружающих функциях, затем в файле, затем среди встроенных имён — сокращённо LEGB. global и nonlocal позволяют функции менять имя внешнего уровня, но программу с ними труднее читать.
У if и for нет собственной отдельной области имён, как у функции. Переменная, присвоенная внутри выполненной ветки, остаётся доступной после неё в окружающей области.
Параметр и объект
Присвоить параметру и изменить переданный объект — разные действия. Параметр — ещё одно имя того объекта, который передали. Нажми «Запуск» и пройди программу по шагам:
def rebind(items): items = [99] def mutate(items): items.append(99) a = [1, 2]rebind(a)print(a)mutate(a)print(a)В rebind заменяется локальная связь имени. В mutate меняется тот же список, на который ссылается внешнее a. Это та же модель «имя — ссылка на объект», что и в уроке о списках: параметр — ещё одно имя того же списка. Изменение данных вне возвращаемого результата называют побочным эффектом. Вывод и запись файла — тоже побочные эффекты.
Пока выполняется rebind, стрелки от a и items сначала ведут к одному списку, а после items = [99] — к разным. В mutate обе стрелки ведут к одному списку, и append меняет именно его.
В описании функции полезно явно указывать, меняет ли она переданную коллекцию. Если изменение не нужно, создавай отдельный результат.
Итог
Теперь ты умеешь давать вычислению имя и вызывать его сколько угодно раз. Вот что из урока понадобится дальше.
def имя(параметры):Создаёт функцию; тело выполняется только при вызове.- Аргумент и параметрВ вызове
rectangle_area(3, 5)3 и 5 — аргументы, они становятся значениями параметров. returnОтдаёт значение в место вызова и завершает вызов; без него функция возвращаетNone.printТолько показывает текст, результатом функции не становится.- Локальные именаПрисваивание внутри функции создаёт своё имя; чтение до присваивания —
UnboundLocalError. - Параметр-список
appendменяет переданный список, присваивание параметру — нет.
Проверьте себя
Ответь сначала сам, потом открой ответ.
В вызове rectangle_area(4, 7) что здесь аргументы, а что параметры? Что вернёт вызов?
ОтветСкрыть ответ
4 и 7 — аргументы, width и height — параметры. Вызов вернёт 28.
Что напечатает программа?
def double(n): print(n * 2) result = double(5)print(result)ОтветСкрыть ответ
10 и None: функция печатает 10, но ничего не возвращает.
Что вернёт describe_sign(5) из урока и какие строки функции при этом выполнятся?
ОтветСкрыть ответ
«положительное»: первая проверка истинна, первый return завершает вызов, и остальные строки не выполняются.
Вне функции задано count = 0, а функция bump() выполняет count = count + 1. Что произойдёт при вызове bump() и почему?
ОтветСкрыть ответ
UnboundLocalError: присваивание делает count локальным во всём теле, а правая часть читает его раньше, чем оно получило значение.
Функция add_zero(items) выполняет items.append(0). Чему равен a после a = [1] и add_zero(a)?
ОтветСкрыть ответ
[1, 0]: параметр items — ещё одно имя того же списка, и append меняет его.
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Напиши функцию: периметр прямоугольника
Напиши функцию perimeter(width, height), которая возвращает периметр прямоугольника. Вызови её для 3 и 5, напечатав результат и запиши, что напечатала программа.
Введите число.
Подсказка
Периметр — удвоенная сумма сторон; верни его через return.
Разбор
Например:
def perimeter(width, height): return 2 * (width + height) print(perimeter(3, 5))2 · (3 + 5) = 16.
Ответ: 16
Что напечатает программа?
def f(n): print(n * 2) r = f(3)print(r)Запишите напечатанные строки через пробел в порядке вывода.
Подсказка
print внутри функции печатает, но функция без return возвращает None.
Разбор
Вызов печатает 6, а r получает None.
Ответ: 6 None
Что напечатает программа?
def describe_sign(number): if number > 0: return "положительное" if number < 0: return "отрицательное" return "ноль" print(describe_sign(7))print(describe_sign(-0.5))Запишите напечатанные строки через пробел в порядке вывода.
Подсказка
Первый выполненный return завершает вызов.
Разбор
7 больше нуля — первый return. −0.5 меньше нуля — второй.
Ответ: положительное отрицательное
Что напечатает программа?
x = 1 def g(): x = 2 return x print(g(), x)Запишите напечатанные строки через пробел в порядке вывода.
Подсказка
Присваивание внутри функции создаёт своё локальное x.
Разбор
g() возвращает своё локальное 2, внешнее x осталось 1.
Ответ: 2 1
Что напечатает программа?
def h(a): a.append(0) a = [5] a.append(1) lst = [9]h(lst)print(lst)Запишите список так, как его печатает Python.
Подсказка
Пока a и lst — имена одного списка, append меняет его. После a = [5] имя a ведёт к новому списку.
Разбор
Первый append добавил 0 в общий список. Потом a связали с новым списком, и второй append менял уже его.
Ответ: [9, 0]
Напиши функцию: последняя цифра
Напиши функцию last_digit(n), которая возвращает последнюю цифру модуля числа. Вызови её так: print(last_digit(47) + last_digit(-123) + last_digit(50)) и запиши, что напечатала программа.
Введите число.
Подсказка
Последняя цифра — abs(n) % 10.
Разбор
Например:
def last_digit(n): return abs(n) % 10 print(last_digit(47) + last_digit(-123) + last_digit(50))7 + 3 + 0 = 10.
Ответ: 10
Напиши функцию: чётные числа
Напиши функцию is_even(n), которая возвращает True или False, и с её помощью посчитай чётные числа в списке [3, 8, 0, -6, 7]. Вызови её в цикле по списку, напечатав количество и запиши, что напечатала программа.
Введите число.
Подсказка
is_even возвращает n % 2 == 0; в цикле прибавляй 1, когда она вернула True.
Разбор
Например:
def is_even(n): return n % 2 == 0 count = 0for x in [3, 8, 0, -6, 7]: if is_even(x): count += 1print(count)Чётные — 8, 0 и −6.
Ответ: 3
Напиши функцию: наибольшее из трёх
Напиши функцию max_of_three(a, b, c) без max. Вызови её для −5, −2 и −9, напечатав результат и запиши, что напечатала программа.
Введите число.
Подсказка
Начни с best = a и сравни с ним b и c отдельными if.
Разбор
Например:
def max_of_three(a, b, c): best = a if b > best: best = b if c > best: best = c return best print(max_of_three(-5, -2, -9))Наибольшее из −5, −2, −9 — −2.
Ответ: -2