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

Как Python исполняет код?

1.0 Junior🔥 71 комментариев
#Python Core

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

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

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

Как Python исполняет код

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

Этап 1: Парсинг (Parsing)

Сначала Python парсит исходный код и проверяет синтаксис:

import ast
import inspect

# Исходный код
code = """
def hello():
    print("Hello")
"""

# Парсим в AST (Abstract Syntax Tree)
tree = ast.parse(code)
print(ast.dump(tree))

# Проверка синтаксиса
try:
    ast.parse("def foo(")
except SyntaxError as e:
    print(f"Синтаксическая ошибка: {e}")

Этап 2: Компиляция в байт-код (Bytecode Compilation)

После парсинга код компилируется в байт-код — промежуточный формат, который исполняет виртуальная машина Python (CPython):

import dis

def add(a, b):
    return a + b

# Посмотреть байт-код функции
dis.dis(add)

# Вывод:
# 2           0 LOAD_FAST                0 (a)
#             2 LOAD_FAST                1 (b)
#             4 BINARY_ADD
#             6 RETURN_VALUE

# Компилируем строку в код
code_obj = compile("x = 2 + 3", "<string>", "exec")
print(code_obj)
print(code_obj.co_code)  # Байт-код в бинарном виде

# Инспектируем скомпилированный объект
print(code_obj.co_names)       # Переменные
print(code_obj.co_consts)      # Константы
print(code_obj.co_varnames)    # Локальные переменные

Этаг 3: Исполнение на виртуальной машине (Python VM)

Виртуальная машина Python исполняет байт-код пошагово. Она использует стек для временного хранения значений:

# Пример: x = 2 + 3
# Байт-код:
#   1. LOAD_CONST 1 (2)      # Загрузить 2 на стек
#   2. LOAD_CONST 2 (3)      # Загрузить 3 на стек
#   3. BINARY_ADD            # Сложить два верхних значения
#   4. STORE_NAME 'x'        # Сохранить результат в переменную x

import sys

def show_execution():
    x = 2 + 3
    return x

# Показать весь байт-код функции
dis.dis(show_execution)

# Получить информацию о коде
code = show_execution.__code__
print(f"Количество аргументов: {code.co_argcount}")
print(f"Локальные переменные: {code.co_varnames}")
print(f"Константы: {code.co_consts}")

Полный процесс: парсинг → компиляция → исполнение

import marshal
import types

# Исходный Python код
source_code = """
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

result = fibonacci(5)
"""

# Шаг 1: Парсинг
ast_tree = ast.parse(source_code)
print(f"AST построено успешно")

# Шаг 2: Компиляция в байт-код
code_obj = compile(source_code, filename="<string>", mode="exec")
print(f"Байт-код скомпилирован")
print(f"Размер байт-кода: {len(code_obj.co_code)} байт")

# Шаг 3: Исполнение
namespace = {}
exec(code_obj, namespace)
print(f"Результат: {namespace['result']}")

# Можно сохранить байт-код в файл (.pyc)
with open("script.pyc", "wb") as f:
    f.write(b'\\x3\\r\\r\\n')  # Магический номер Python
    f.write(marshal.dumps(code_obj))

Оптимизация: кэширование байт-кода

Пython автоматически кэширует скомпилированный байт-код в директорию __pycache__/:

my_project/
├── main.py
└── __pycache__/
    └── main.cpython-310.pyc  # Кэшированный байт-код

В следующий раз при импорте модуля Python загружает .pyc файл вместо переcompilation:

import sys
import importlib

# Посмотреть, откуда загружен модуль
import os
print(os.__file__)  # Путь к модулю

# Проверить кэш импортов
print(sys.modules.keys())  # Все загруженные модули

# Принудительная перезагрузка модуля
importlib.reload(os)

Производительность байт-кода

import timeit
import dis

# Сравнение разных подходов
def using_list():
    return [x*2 for x in range(1000)]

def using_loop():
    result = []
    for x in range(1000):
        result.append(x*2)
    return result

def using_map():
    return list(map(lambda x: x*2, range(1000)))

print("List comprehension:")
dis.dis(using_list)
print(f"Время: {timeit.timeit(using_list, number=100000)}")

print("\\nИспользование loop:")
dis.dis(using_loop)
print(f"Время: {timeit.timeit(using_loop, number=100000)}")

print("\\nИспользование map:")
dis.dis(using_map)
print(f"Время: {timeit.timeit(using_map, number=100000)}")

Отличие от других языков

# Python (интерпретируемый язык)
# Исходный код → Парсинг → Компиляция (байт-код) → Исполнение
# Происходит каждый раз при запуске (кроме кэша)

# Java (полукомпилируемый язык)
# Исходный код → Компиляция (в .class) → JVM исполнение
# Компиляция происходит один раз

# C (компилируемый язык)
# Исходный код → Компиляция (в машинный код) → Прямое исполнение
# Самый быстрый, но нет гибкости интерпретатора

# Python преимущества:
# - Динамическая типизация
# - Быстрая разработка
# - Кроссплатформенность
# - Интерактивное выполнение (REPL)

# Python недостатки:
# - Медленнее скомпилированных языков
# - Требует Python при запуске

Практический пример: профилирование выполнения

import cProfile
import pstats
from io import StringIO

def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Профилируем функцию
profiler = cProfile.Profile()
profiler.enable()

result = fibonacci(30)

profiler.disable()

# Вывод статистики
s = StringIO()
ps = pstats.Stats(profiler, stream=s).sort_stats('cumulative')
ps.print_stats(10)  # Top 10 функций
print(s.getvalue())

Резюме

  • Парсинг: Проверка синтаксиса, построение AST
  • Компиляция: Преобразование в байт-код (промежуточный формат)
  • Исполнение: Виртуальная машина исполняет байт-код на стеке
  • Кэширование: Python сохраняет .pyc файлы для быстрой загрузки
  • Динамичность: Код может быть скомпилирован и выполнен во время runtime
  • Гибкость: Можно использовать eval(), exec(), compile() для динамического выполнения кода