Вызов функций и стек вызовов в Python
Обновлено:
Функции в программе могут работать не только самостоятельно.
Одна функция может:
- вызвать другую функцию;
- передать ей аргументы;
- получить от неё результат;
- продолжить свою работу;
- вернуть собственный результат дальше.
Так можно строить программу из небольших функций, каждая из которых отвечает за свою часть логики.
1. Простой пример
Создадим три функции:
def calculate_discount(price):
return price * 0.1
def calculate_delivery(price):
if price >= 5000:
return 0
return 500
def calculate_order_total(price):
discount = calculate_discount(price)
delivery = calculate_delivery(price)
total_price = price - discount + delivery
return total_price
Вызовем главную функцию:
result = calculate_order_total(4000)
print(result)
Результат:
4100.0
Здесь функции связаны между собой:
calculate_order_total()
↓
calculate_discount()
calculate_order_total()
↓
calculate_delivery()
Главная функция использует результаты двух других функций для собственного вычисления.
Последовательность выполнения
2. Что происходит при вызове
Начинаем с глобальной области:
result = calculate_order_total(4000)
Python переходит внутрь:
calculate_order_total()
и начинает выполнять её сверху вниз.
Первая операция:
discount = calculate_discount(price)
Для её выполнения необходимо вызвать другую функцию.
Поэтому работа calculate_order_total() временно приостанавливается.
3. Переход во вторую функцию
Python вызывает:
calculate_discount(4000)
Внутри выполняется:
return price * 0.1
Получаем:
400.0
После return функция calculate_discount() завершается.
Python возвращается именно туда, откуда она была вызвана:
discount = calculate_discount(price)
Поэтому фактически получается:
discount = 400.0
После этого calculate_order_total() продолжает работу со следующей строки.
4. Следующий вызов
Далее выполняется:
delivery = calculate_delivery(price)
Первая функция снова временно приостанавливается, и Python переходит в:
calculate_delivery(4000)
Для значения 4000 функция возвращает:
500
Python снова возвращается в:
calculate_order_total()
и получается:
delivery = 500
После этого выполняется:
total_price = price - discount + delivery
То есть:
4000 - 400 + 500
Получаем:
4100.0
В конце:
return total_price
возвращает результат уже из главной функции в глобальную область.
Цепочка из нескольких функций
5. Одна функция вызывает вторую, а вторая — третью
Рассмотрим более последовательный пример:
def calculate_tax(price):
return price * 0.18
def calculate_price_with_tax(price):
tax = calculate_tax(price)
return price + tax
def create_price_report(price):
final_price = calculate_price_with_tax(price)
return f'Итоговая стоимость: {final_price}'
report = create_price_report(1000)
print(report)
Результат:
Итоговая стоимость: 1180.0
Здесь появляется цепочка:
глобальная область
↓
create_price_report()
↓
calculate_price_with_tax()
↓
calculate_tax()
6. Как выполняется такая цепочка
Начинаем здесь:
report = create_price_report(1000)
Шаг 1
Вызывается:
create_price_report(1000)
Внутри неё Python доходит до:
final_price = calculate_price_with_tax(price)
Теперь первая функция ждёт результат второй.
Шаг 2
Вызывается:
calculate_price_with_tax(1000)
Она доходит до:
tax = calculate_tax(price)
Теперь уже вторая функция ждёт результат третьей.
Шаг 3
Вызывается:
calculate_tax(1000)
Она выполняет:
return price * 0.18
и возвращает:
180.0
7. Возвращение происходит в обратном порядке
После завершения:
calculate_tax()
Python возвращается не сразу в глобальную область, а в функцию, которая её вызвала:
calculate_price_with_tax()
Получается:
tax = 180.0
Функция продолжает работу:
return price + tax
и возвращает:
1180.0
Теперь Python возвращается в:
create_price_report()
Получается:
final_price = 1180.0
Функция продолжает работу:
return f'Итоговая стоимость: {final_price}'
И только после этого результат возвращается в глобальную область:
report = 'Итоговая стоимость: 1180.0'
Схема выполнения
8. Вызовы идут в одну сторону, возвраты — в обратную
Вызовы:
Global
↓
create_price_report()
↓
calculate_price_with_tax()
↓
calculate_tax()
Возвраты:
calculate_tax()
↑
calculate_price_with_tax()
↑
create_price_report()
↑
Global
Можно представить это так:
Global
↓
Function 1
↓
Function 2
↓
Function 3
Function 3
↑
Function 2
↑
Function 1
↑
Global
Каждая функция после вызова другой функции ждёт её завершения.
После получения результата она продолжает выполнение с того места, где остановилась.
Стек вызовов
9. Call Stack
Такой механизм связан с понятием:
Call Stack
или:
стек вызовов
Стек вызовов хранит информацию о текущей цепочке вызванных функций.
Например:
create_price_report()
calculate_price_with_tax()
calculate_tax()
Когда вызывается новая функция, информация о её вызове добавляется в стек.
Когда функция завершается, её вызов убирается из стека, и Python возвращается к предыдущей функции.
Упрощённо:
1. create_price_report()
2. calculate_price_with_tax()
3. calculate_tax()
После завершения третьей:
1. create_price_report()
2. calculate_price_with_tax()
После завершения второй:
1. create_price_report()
После завершения первой стек текущих пользовательских вызовов освобождается, и выполнение продолжается в глобальной области.
Что происходит с локальными областями
10. У каждого вызова функции своя локальная область
Рассмотрим:
def calculate_tax(price):
tax = price * 0.18
return tax
def calculate_total(price):
tax = calculate_tax(price)
total = price + tax
return total
Во время выполнения:
calculate_total(1000)
у calculate_total() есть собственная локальная область:
price
tax
total
Во время работы:
calculate_tax()
создаётся другая локальная область:
price
tax
Одинаковые имена:
price
tax
не означают, что это одни и те же локальные переменные.
Каждый вызов функции имеет собственную локальную область.
Связь между функциями происходит через:
- аргументы;
- возвращаемые объекты;
- доступные внешние имена.
Функция может обработать результат другой функции
11. Возвращённое значение не обязательно сразу возвращать дальше
Например:
def calculate_discount(price):
return price * 0.1
def calculate_final_price(price):
discount = calculate_discount(price)
discounted_price = price - discount
service_fee = 100
return discounted_price + service_fee
calculate_discount() возвращает результат:
discount
Но calculate_final_price() после этого продолжает собственную бизнес-логику:
discounted_price = price - discount
service_fee = 100
и только затем возвращает окончательный результат.
То есть результат одной функции может стать промежуточным значением для другой.
Зачем разделять программу на несколько функций
12. Одна функция — одна понятная задача
Вместо одной большой функции:
def process_order(price):
...
можно разделить обязанности:
def calculate_discount(price):
...
def calculate_delivery(price):
...
def calculate_total(price):
...
def create_order_report(price):
...
Каждая функция решает отдельную небольшую задачу.
После этого функции можно связывать между собой.
Такой код обычно:
- проще читать;
- проще проверять;
- проще изменять;
- проще повторно использовать;
- проще тестировать;
- проще искать ошибку в конкретной части логики.
Главное по теме
Функция может вызвать другую функцию:
def first_function():
result = second_function()
Вторая функция может вызвать третью:
def second_function():
result = third_function()
Когда происходит новый вызов, текущая функция временно ждёт его завершения.
После return Python возвращается именно в то место, откуда функция была вызвана:
Global
↓
Function 1
↓
Function 2
↓
Function 3
После этого возвращение идёт в обратном порядке:
Function 3
↑
Function 2
↑
Function 1
↑
Global
Этот механизм связан со стеком вызовов — Call Stack.
У каждого вызова функции существует собственная локальная область видимости.
Функции обычно передают данные друг другу через аргументы:
calculate_tax(price)
а результаты — через:
return
Благодаря этому большую бизнес-логику можно разбивать на небольшие связанные функции, каждая из которых отвечает за свою часть программы.