Python глазами Gopher'а: типизация, управление памятью и интерпретируемая среда
Python глазами Gopher'а: типизация, управление памятью и интерпретируемая среда
Парадокс: вы — Senior Go-разработчик. Вы привыкли к бинарникам, компилируемым за миллисекунды, легковесным горутинам и строгой статической типизации. И теперь, переходя в AI-инженерию, вы видите, что вся индустрия машинного обучения построена на Python — языке, который интерпретируется на лету, не имеет строгих типов и блокирует параллельное выполнение потоков. Почему самая вычислительно сложная отрасль в мире выбрала один из самых медленных языков?
Ответ кроется в смене парадигмы. В Go язык — это инструмент для написания бизнес-логики и высоконагруженных систем. В AI Python — это не инструмент вычислений. Это высокоуровневый клей (API), который управляет тяжеловесными C/C++ и CUDA-библиотеками.
Чтобы эффективно применять Vibecoding и управлять AI-агентами при написании Python-кода, вам нужно откалибровать свою ментальную модель и понять, как Python работает «под капотом».
От бинарника к виртуальной машине (PVM)
В Go процесс сборки прямолинеен: go build берет исходный код и компилирует его в машинный код под конкретную архитектуру. Вы получаете один исполняемый файл, который ОС запускает напрямую.
Python работает иначе. При запуске скрипта исходный код сначала транслируется в байт-код (вы могли видеть папки __pycache__ и файлы .pyc). Затем этот байт-код исполняется Python Virtual Machine (PVM) — обычно это стандартная реализация CPython.
PVM — это огромный цикл на языке C, который читает инструкции байт-кода и выполняет соответствующие им C-функции.
В мире AI Python-код — это просто пульт управления. Когда вы пишете код для обучения нейросети, 99% времени процессора или видеокарты тратится не в PVM, а внутри предкомпилированных C-библиотек, к которым PVM просто обращается через интерфейс (FFI).
Concurrency и проклятие GIL
В Go многопоточность встроена в ДНК языка. Планировщик Go распределяет десятки тысяч горутин по системным потокам (модель M:N), позволяя им выполняться параллельно на всех ядрах процессора.
В CPython существует механизм, который вызывает наибольший шок у разработчиков из других экосистем — Global Interpreter Lock (GIL).
GIL — это глобальный мьютекс. Он гарантирует, что в любой момент времени только один системный поток может выполнять байт-код Python. Даже если у вас 32-ядерный сервер и вы запустили 32 потока (через модуль threading), они будут выстраиваться в очередь, передавая друг другу GIL.
Кажется, что для машинного обучения, где требуются колоссальные параллельные вычисления, это приговор. Но здесь срабатывает архитектурный трюк экосистемы Python: GIL защищает только внутренние структуры данных самого интерпретатора.
Когда Python вызывает тяжелую функцию из библиотеки, написанной на C (например, перемножение матриц в NumPy или расчет градиентов в PyTorch), эта библиотека отпускает GIL перед началом вычислений. Вся тяжелая математика выполняется на C или CUDA параллельно на всех доступных ядрах или тысячах ядер GPU. В это время PVM может спокойно передать GIL другому Python-потоку для выполнения легковесных задач (например, чтения следующего батча данных с диска).
Типизация: от интерфейсов к Duck Typing
В Go типизация статическая. Интерфейсы реализуются неявно, но компилятор жестко проверяет соответствие типов на этапе сборки. Если функция ждет io.Reader, вы не скомпилируете код, передав туда int.
Python использует динамическую строгую типизацию. Тип привязан к объекту в памяти, а не к переменной. Переменная — это просто ссылка (указатель). Философия полиморфизма в Python строится на Duck Typing (утиной типизации):
«Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка».
Если функция вызывает метод .read() у переданного объекта, Python не волнует, какой класс у этого объекта. Если метод существует — код выполнится. Если нет — упадет с AttributeError в рантайме (во время выполнения).
Для Senior-разработчика падение в рантайме из-за неверного типа — это неприемлемый риск. Именно поэтому в современном Python (и особенно при Vibecoding, как мы разбирали в первом курсе) мы используем Type Hints (аннотации типов) и библиотеки вроде Pydantic.
# Классический Python (опасно для сложных систем)
def process_data(data):
return data.sum()
# Современный Python (контракты для AI-агентов и статических анализаторов)
from typing import List
def process_data(data: List[float]) -> float:
return sum(data)
Аннотации типов не меняют поведение PVM — интерпретатор их игнорирует. Но они служат жестким контрактом для статических анализаторов (например, mypy) и, что критически важно, для LLM. Когда вы просите Claude Code написать интеграцию, наличие Type Hints задает границы, не позволяя ИИ галлюцинировать с типами возвращаемых значений.
Управление памятью: Reference Counting вместо GC-пауз
В Go работает конкурентный сборщик мусора (Garbage Collector) алгоритма Mark-and-Sweep. Он периодически сканирует память, строит граф достижимых объектов, а затем удаляет всё, до чего не смог добраться. Это вызывает небольшие задержки (STW - Stop The World), которые команда Go годами оптимизировала до долей миллисекунды.
В Python основной механизм управления памятью совершенно другой — Reference Counting (подсчет ссылок).
Каждый объект в Python имеет скрытое поле — счетчик ссылок.
- Создали список
a = [1, 2]→ счетчик равен 1. - Передали его в функцию
process(a)→ внутри функции счетчик стал 2. - Функция завершилась → счетчик вернулся к 1.
- Вызвали
del aили переменная вышла из области видимости → счетчик стал 0.
Как только счетчик достигает нуля, память освобождается моментально, детерминированно и без ожидания прохода сборщика мусора. Для AI это невероятно полезно: когда вы удаляете огромную матрицу на 10 ГБ из оперативной памяти или памяти GPU, ресурсы освобождаются в ту же микросекунду.
Но у подсчета ссылок есть фатальный недостаток — циклические зависимости.
Если объект A ссылается на объект B, а объект B ссылается на объект A, их счетчики ссылок никогда не опустятся ниже 1, даже если основная программа потеряла к ним доступ. Чтобы решать эту проблему, в Python есть дополнительный сборщик мусора (Generational GC), который периодически просыпается, ищет такие изолированные «острова» объектов и удаляет их.
Понимая эти отличия, вы перестаете бороться с Python и начинаете использовать его по назначению: как гибкий и выразительный язык управления контрактами, делегирующий тяжелую работу специализированным C-движкам. Именно с таким движком мы познакомимся на следующем шаге.