← Назад к вопросам

Какая стуктура данных управляет цепочкой вызовов?

2.0 Middle🔥 161 комментариев
#Python Core#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Структура данных для управления цепочкой вызовов

Цепочка вызовов (call stack, или просто стек вызовов) в программировании управляется структурой данных STACK (стек). Это одна из фундаментальных структур данных в компьютерной науке.

Что такое Stack (стек)?

Stack — это структура данных с принципом LIFO (Last In First Out — последний вошёл, первый вышел). Это означает, что последний добавленный элемент извлекается первым.

# Иллюстрация LIFO принципа
stack = []
stack.append(1)      # [1]
stack.append(2)      # [1, 2]
stack.append(3)      # [1, 2, 3]
print(stack.pop())   # 3 - последний вошёл, первый вышел
print(stack)         # [1, 2]

Call Stack в Python

Когда функция вызывает другую функцию, в стек вызовов добавляется новый фрейм (frame). Когда функция завершает работу, её фрейм удаляется из стека. Это происходит автоматически и управляется интерпретатором Python.

def function_a():
    print("A start")
    function_b()  # Добавляет фрейм B в стек
    print("A end")

def function_b():
    print("B start")
    function_c()  # Добавляет фрейм C в стек
    print("B end")

def function_c():
    print("C start")
    print("C end")  # Удаляет фрейм C из стека

function_a()

# Порядок вывода:
# A start
# B start
# C start
# C end      <- function_c завершена, её фрейм удалён
# B end      <- function_b завершена, её фрейм удалён
# A end      <- function_a завершена, её фрейм удалён

Структура стека вызовов

Визуально, стек вызовов выглядит так:

Шаг 1:             Шаг 2:             Шаг 3:
+----------+       +----------+       +----------+
| main     |       | main     |       | main     |
+----------+       +----------+       +----------+
                   | function_a|      | function_a|
                   +----------+       +----------+
                                     | function_b|
                                     +----------+
                                     | function_c|
                                     +----------+

Практический пример с traceback

Когда происходит ошибка, Python показывает весь call stack:

def level_1():
    level_2()

def level_2():
    level_3()

def level_3():
    x = 1 / 0  # ZeroDivisionError

level_1()

# Traceback (most recent call last):
#   File "script.py", line 12, in <module>
#     level_1()
#   File "script.py", line 2, in level_1
#     level_2()
#   File "script.py", line 5, in level_2
#     level_3()
#   File "script.py", line 8, in level_3
#     x = 1 / 0
# ZeroDivisionError: division by zero

# Это показывает весь стек: main -> level_1 -> level_2 -> level_3

Операции над стеком

Stack имеет две основные операции:

1. PUSH — добавить элемент на вершину стека

Когда функция вызывается, её фрейм добавляется (PUSH) на вершину стека:

def caller():
    callee()  # PUSH: фрейм callee добавляется на стек

def callee():
    pass

2. POP — удалить элемент с вершины стека

Когда функция возвращает управление, её фрейм удаляется (POP) из стека:

def function():
    return 42  # POP: фрейм function удаляется из стека

Реализация стека на Python

class Stack:
    def __init__(self):
        self.items = []
    
    def push(self, item):
        """Добавить элемент на вершину стека"""
        self.items.append(item)
    
    def pop(self):
        """Удалить и вернуть элемент с вершины"""
        if not self.is_empty():
            return self.items.pop()
        return None
    
    def peek(self):
        """Получить элемент с вершины без удаления"""
        if not self.is_empty():
            return self.items[-1]
        return None
    
    def is_empty(self):
        """Проверить, пуст ли стек"""
        return len(self.items) == 0
    
    def size(self):
        """Получить размер стека"""
        return len(self.items)

# Использование
stack = Stack()
stack.push('function_a')
stack.push('function_b')
stack.push('function_c')

print(stack.peek())  # function_c
print(stack.pop())   # function_c
print(stack.pop())   # function_b
print(stack.pop())   # function_a

Stack Overflow (переполнение стека)

Если стек заполнится (что случается с бесконечной рекурсией), произойдёт ошибка RecursionError:

def infinite_recursion():
    infinite_recursion()  # Каждый вызов добавляет фрейм в стек

infinite_recursion()
# RecursionError: maximum recursion depth exceeded

# Стек переполнился, так как функция никогда не завершается

Контроль стека в Python

Python позволяет контролировать глубину стека:

import sys

# Получить текущий лимит рекурсии
print(sys.getrecursionlimit())  # Обычно 1000

# Установить новый лимит (осторожно!)
sys.setrecursionlimit(2000)

# Получить информацию о стеке вызовов
import inspect
frame = inspect.currentframe()
print(inspect.getframeinfo(frame))

Практическое применение: обход графа с помощью стека

def depth_first_search(graph, start):
    """DFS использует стек для управления порядком посещения узлов"""
    stack = [start]
    visited = set()
    
    while stack:
        node = stack.pop()  # Берём с вершины стека (LIFO)
        if node not in visited:
            visited.add(node)
            print(node)
            # Добавляем соседей на стек
            for neighbor in graph[node]:
                if neighbor not in visited:
                    stack.append(neighbor)

# Пример графа
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

depth_first_search(graph, 'A')

Выводы

Call Stack управляется структурой данных STACK с принципом LIFO:

  • При вызове функции её фрейм добавляется (PUSH) на вершину стека
  • Когда функция завершается, её фрейм удаляется (POP) из стека
  • Это основной механизм управления потоком выполнения в программировании
  • Понимание call stack критично для отладки и оптимизации кода
  • Stack используется не только для управления вызовами, но и в алгоритмах (DFS, обратная польская нотация и т.д.)