Модель интерпретации Python по сравнению с прямой и виртуальной компиляцией
Я составлял диаграммы (каламбур) в надежде понять различные реализации распространенных языков программирования. Я понимаю, скомпилирован или интерпретирован код, зависит от реализации кода и не является аспектом самого языка программирования.
Я заинтересован в сравнении интерпретации Python с прямой компиляцией (например, C++)
http://s14.postimage.org/bqga02mip/Direct_Compilation.png
и модель виртуальной машины (например, Java или C#)
http://s14.postimage.org/6vml1so75/Virtual_Machine.png
В свете этих двух диаграмм, приведенных выше, не могли бы вы помочь мне разработать аналогичную блок-схему того, как файл.py преобразуется в.pyc, использует стандартные библиотеки (насколько я понимаю, они называются модулями), а затем фактически запускается. Многие программисты на SO указывают, что python как язык сценариев выполняется не процессором, а интерпретатором, но это звучит совершенно невозможно, потому что в конечном счете вычисления должны выполняться аппаратными средствами.
3 ответа
Во-первых, это деталь реализации. Я ограничиваю свой ответ CPython и PyPy, потому что я знаком с ними. Ответы для Jython, IronPython и других реализаций будут отличаться - вероятно, радикально.
Python ближе к "модели виртуальной машины". Код Python, вопреки утверждениям некоторых слишком громких для их уровня знаний людей и, несмотря на то, что все (включая меня) смешивают его в непринужденной дискуссии, никогда не интерпретируется. Он всегда компилируется в байт-код (опять же, на CPython и PyPy) при загрузке. Если он был загружен, потому что модуль был импортирован и был загружен из файла.py, может быть создан файл.pyc для кэширования выходных данных компиляции. Этот шаг не является обязательным; Вы можете отключить его различными способами, и на выполнение программы не повлияет ни малейший бит (за исключением того, что следующий процесс для загрузки модуля должен сделать это снова). Тем не менее, компиляция в байт-код не избежать, байт-код генерируется в памяти, если он не загружен с диска.
Этот байт-код (точные детали которого являются подробностями реализации и различаются в разных версиях) затем выполняется на уровне модуля, что влечет за собой создание объектов функций, объектов классов и тому подобного. Эти объекты просто повторно используют (удерживают указатель) байт-код, который уже находится в памяти. Это не похоже на C++ и Java, где код и классы устанавливаются в камне во время / после компиляции. Во время исполнения import
С заявлениями можно столкнуться. Мне не хватает места, времени и понимания, чтобы описать импортную технику, но короткая история такова:
- Если он уже был импортирован один раз, вы получаете этот объект модуля (другая конструкция времени выполнения для вещи, которую статические языки имеют только во время компиляции). Несколько встроенных модулей (ну, все они в PyPy по причинам, выходящим за рамки этого вопроса) уже импортированы до запуска любого кода Python, просто потому, что они так тесно интегрированы с ядром интерпретатора и настолько фундаментальны.
sys
такой модуль. Некоторый код Python может также выполняться заранее, особенно когда вы запускаете интерактивный интерпретатор (посмотритеsite.py
). - В противном случае модуль находится. Правила для этого не наша забота. В конце эти правила получают либо файл Python, либо динамически связанный фрагмент машинного кода (.DLL в Windows, хотя модули Python специально используют расширение.pyd, но это только имя; в unix используется эквивалентный.so).
- Модуль сначала загружается в память (загружается динамически или анализируется и компилируется в байт-код).
- Затем модуль инициализируется. Модули расширения имеют специальную функцию для того, что называется. Модули Python просто запускаются сверху вниз. В хорошо работающих модулях это просто устанавливает глобальные данные, определяет функции и классы и импортирует зависимости. Конечно, может случиться и все остальное. Полученный объект модуля кэшируется (помните первый шаг) и возвращается.
Все это относится как к стандартным библиотечным модулям, так и к сторонним модулям. Вот почему вы можете получить странное сообщение об ошибке, если вы называете свой скрипт, как стандартный библиотечный модуль, который вы импортируете в этот скрипт (он импортирует сам, хотя и не падает из-за кеширования - одна из многих вещей, которые я пропустил).
Как выполняется байт-код (последняя часть вашего вопроса) отличается. CPython просто интерпретирует его, но, как вы правильно заметили, это не значит, что он волшебным образом не использует процессор. Вместо этого существует большой уродливый цикл, который определяет, какая инструкция байт-кода должна быть выполнена далее, а затем переходит к некоторому нативному коду, который выполняет семантику этой инструкции. PyPy более интересный; он начинает с интерпретации, но записывает некоторую статистику по пути. Когда он решает, что это стоит сделать, он начинает подробно записывать то, что делает интерпретатор, и генерирует некоторый высоко оптимизированный нативный код. Интерпретатор все еще используется для других частей кода Python. Обратите внимание, что то же самое происходит со многими JVM и, возможно, с.NET, но на приведенной вами диаграмме это скрыто.
Для справочной реализации python:
(.py) -> python (проверяет наличие.pyc) -> (.pyc) -> python (выполнение динамически загружает модули)
Есть и другие реализации. Наиболее заметными являются:
С технической точки зрения Python является языком сценариев, но он также компилируется, исходный код Python берется из исходного файла и подается в интерпретатор, который часто компилирует исходный код в байт-код либо изнутри, а затем выбрасывает его или извлекает и сохраняет его как.pyc.
Да, python - это отдельная виртуальная машина, которая затем располагается поверх реального оборудования, но весь байт-код python представляет собой серию инструкций для pvm (виртуальной машины python), очень похожую на ассемблер для реального процессора.