Особенности интерпретатора Python
У Python есть одна интересная особенность, которая его выделяет среди других языков программирования. Мы знаем, что Python — интерпретируемый язык, но его способ интерпретации несколько отличается от других похожих. В свою очередь это порождает достаточно неочевидные вещи в поведении самого Питона. Знание таких особенностей, позволяет лучше понять поведение Python.
Компилятор и интерпретатор
Для начала рассмотрим отличие компилятора от интерпретатора. Ключевое различие в том, что компилятор на выходе даёт исполняемый код. То есть исходный код программы используется для генерации другого кода — исполняемого. Именно исполняемый файл и работает. Если мы изменим исходный код, то потребуется новая компиляция, чтобы получить рабочую программу.
Интерпретатор работает непосредственно с исходным кодом. Сам по себе интерпретатор как бы сопоставляет команды из исходного файла с тем, что уж есть в интерпретаторе и построчно их выполняет. Если изменить исходный код, то интерпретатор на лету подхватит изменения.
Анализ исходного кода
Перед тем, как компилятор или интерпретатор начнут выполнение программы обычно проверяется синтаксис исходного кода. Например проверка парности скобок, кавычек, отступов и т.д. После этого нужно проверить ключевые слова, потом доступность функций, модулей и т.п. Когда этот этап пройдён, проверяются типы данных, всякие логические ошибки — то есть перед запуском программы следует убедиться, что она корректно написана и может быть выполнена.
Компилятор, очевидно, должен пройти все этапы проверки и лишь в конце происходит сборка программы. Если на любом этапе возникают ошибки, то компилятор просто откажется собирать итоговый файл.
Интерпретатор же, работает чуть по другому. Поскольку исполняется каждый исходный файл, то проверка на корректность происходит уже во время его выполнения. При этом интерпретатор может как обрабатывать файл целиком, так и построчно.
Интерпретация целого файла
Для примера рассмотрим PHP, поскольку именно в нём реализован подобный подход. Интерпретатор PHP анализирует файл целиком. Если в любой строчке файла есть ошибки, то PHP не позволит запустить данный файл. Например, такой простой файл, где мы специально допустим ошибку:
Из за «blabla» PHP вывалится с ошибкой «PHP Fatal error: Uncaught Error: Undefined constant «blabla»». При этом первая строчка с «hello» не будет выполнена.
Теперь рассмотрим другой код:
Он выполняется как положено, но обратите внимание, что функцию мы объявили уже после того, как её использовали. То есть интерпретатор загрузил весь файл, построил по нему карту функций и сделал их доступными как минимум в пределах данного файла.
Теперь для исследования давайте создадим ещё одну функцию с тем же именем и попробуем выполнить файл. PHP вываливается с ошибкой «PHP Fatal error: Cannot redeclare a()». То есть мы не можем создать две функции с одинаковым именем.
Запомним эти особенности и теперь посмотрим как ведёт себя Python
Построчная интерпретация
Выполним тот же код, чтобы понять отличия:
print('hello') blabla
Python вываливается с ошибкой «NameError: name ‘blabla’ is not defined». Но при этом первая строчка с «hello» была выполнена. То есть Python выполняет код построчно.
Теперь второй пример:
print('hello') a(10) def a(x): print(x)
Опять ошибка: «NameError: name ‘a’ is not defined». То есть Python ничего не знает о том, что функция объявлена ниже.
Попробуем объявить две одноимённые функции:
print('hello') def a(x): print(x) def a(x): print(x*2) a(10) # 20
Когда дело дойдёт до выполнения функции, то Python использовал последнюю объявленную.
Из этого можно сделать важный вывод: Python построчный интерпретатор, где команды выполняются сверху вниз, и это отличает его от других интерпретаторов. Наиболее близким по поведению к Python можно назвать Basic, который когда-то был крайне популярным.
Байт-код
Байт-код — это специальный файл, который является промежуточным перед непосредственным исполнением файла. Например в Java компилятор отдаёт не исполняемый файл, а байт-файл, который в свою очередь выполняется виртуально машиной (она под конкретную систему). В Python что-то подобное — исходный код трансформируется в байт-код, только в памяти и потом интерпретируется виртуальной машиной.
Но есть одна особенность — если в других языках, байт-код формируется уже после проверки на ошибки, то в Python — он формируется безусловно как есть. Для примера можно любой пример с ошибками скомпилировать в pyc-файл (это и есть байт-код) с помощью python -m compileall . и убедиться, что файлы создаются без каких-либо проблем.
Таким образом байт-код в Python — это просто бинарная форма исходного кода без особых проверок.
Особенности Python
Они проистекают именно из-за этой технической особенности интерпретатора.
Мы можем выполнять код до строки с ошибкой
Это довольно сильно затрудняет отладку. Например если в файле какой-то ошибочный код выполняется после многочисленных циклов или условий, то мы можем никогда об этом не узнать, пока не сработают все эти условия или циклы.
Можно затереть любые другие функции
На Python можно спокойно переписать любые, даже стандартные функции. Например такой вот «дикий» код:
print1 = print def print(x): print1('hello') print(10) # hello print(20) # hello print(30) # hello
Теперь на любой print() будет выводиться «hello».
Другой пример: можно «убить» любую функцию:
print = 10 list = 20 str = 30 dict = 40 range = 50
Вспомните как часто для имени строки используется «str»? А в Python это стандартная функция. Можно случайно затереть любую функцию и Python даже не выдаст предупреждения.
При программировании на Python нужно как-то следить за именованием функций и переменных. Можно случайно использовать одно имя для функции и переменной, а потом не понимать почему код работает неверно. Проблему можно было бы решить, если в Python можно было бы использовать какой-то символ для обозначения переменных: как в PHP символ «$». Но в Python доступны только цифры, буквы и символ подчеркивания.
Стоит ли удивляться обилию идентификаторов с подчеркиванием в Python-программах? Просто ничего другого нет.
Я бы посоветовал для переменных использовать верблюжью нотацию Pascal (т.н. CamelCase), где первый символ указывается с большой буквы:
My = 'google' MyName = 'Маша' MyAge = 26
Она наиболее близка к общепринятой (mixedCase) и избавит от использования символа подчеркивания.
Неожиданное выполнение кода
Построчное выполнение кода в Python возможно в достаточно неожиданных местах, например при описании класса:
class MyClass: print('Як справи?') k = 42 def out(self): print(10) print('Hello!') print(k)
Простой запуск кода, выведет «Як справи?», «Hello!» и переменную k , хотя мы даже не объявляли создание объекта.
Кстати этот пример показывает особенности ООП в Python. Если в других языках инструкция class это начало шаблона типа данных, единый блок, который не должен выполняться вне объекта, то Python всё это воспринимает «в лоб» — для него class — максимум отдельная область видимости (scope) для методов и полей, а всё что внутри выполняется как обычный код.
Это как раз «чудеса» работы интерпретатора, а не какая-то «особенная объектная модель» Python.
Как работает Python?
Всем еще раз привет, сейчас расскажу о том, как работает Python, что такое интерпретатор, как работает компилятор и что такое байт-код, далее расскажу о виртуальной машине (PVM) и о производительности Python. Также о альтернативных реализациях интерпретатора.
После того, как вы установили себе Python, перейдем к теоретически-практической части и начнем с того что из себя представляет интерпретатор.
Интерпретатор
Интерпретатор — это такая программа, которая выполняет другие программы. Когда вы пишете программу на языке Python, интерпретатор читает вашу программу и выполняет содержащиеся в ней инструкции. В действительности, интерпретатор — это слой программной логики между вашим программным кодом и аппаратурой вашего компьютера.
В зависимости от используемой версии Python сам интерпретатор может быть реализован как программа на языке C, как набор классов Java и в каком-либо другом виде, но об этом позже.
Запуск сценария в консоли
Давайте запустите в консоле интерпретатор:
# pythonТеперь он ожидает ввода комманд, введите туда следующую инструкцию:
print 'hello world!'ура, наша первая программа! 😀
Запуск сценария из файла
Создайте файл "test.py", с содержимым:
# вывести "hello world" print "hello world" # вывести 2 в 10 степени print 2 ** 10и выполните этот файл:
# python /path/to/test.pyВы увидите в консоли результат, поехали дальше!
Динамическая компиляция и байт-код
После того, как запустите сценарий, Python сначала компилирует исходный текст сценария в байт-код для виртуальной машины. Компиляция - это просто этап перевода, а байт-код это низкоуровневое платформонезависимое представление исходного текста программы. Python транслирует каждую инструкцию в исходном коде сценария в группы инструкций байт-кода для повышения скорости выполнения программы, так как байт-код выполняется намного быстрее. После компиляции в байт-код, создается файл с расширением ".pyc" по соседству с исходным текстом сценария.
В следующий раз, когда вы запустите свою программу интерпретатор минует этап компиляции и отдаст на выполнение откомпилированный файл с расширением ".pyc". Однако, если вы изменили исходные тексты вашей программы, то снова произойдет этап компиляции в байт-код, так как Python автоматически следит за датой изменения файла с исходным кодом.
Если Python окажется не в состоянии записать файл с байт-кодом, например из-за отсутствия прав на запись на диск, то программа не пострадает, просто байт-код будет собран в памяти и при завершении программы оттуда удален.
Виртуальная машина Python (PVM)
После того как пройдет процесс компиляции, байт-код передается механизму под названием виртуальная машина, которая и выполнит инструкции из байт-кода. Виртуальная машина - это механизм времени выполнения, она всегда присутствует в составе системы Python и это крайняя составляющая системы под названием "Интерпретатор Python".
Для закрепления пройденного еще раз проясним ситуацию, компиляция в байт-код производится автоматически, а PVM - это всего лишь часть системы Python, которую вы установили вместе с интерпретатором и компилятором. Все происходит прозрачно для программиста, и вам не надо выполнять эти операции вручную.
Производительность
Программисты имеющие опыт работы с такими языками как C и C++, могут заметить некоторые отличия в модели выполнения Python. Первое - это отсутствие этапа сборки или вызова утилиты "make", программы на Python могут быть сразу же запущены после написания исходного кода. Второе отличие - байт-код не является двоичным машинным кодом (например инструкции для микропроцессора Intel), он является внутренним представлением программы на языке Python.
По этим причинам программы на Python не могут выполняться также быстро как на C/C++. Обход инструкций выполняет виртуальная система, а не микропроцессор, и чтобы выполнить байт-код, необходима дополнительная интерпретация, инструкции которой требуют большего времени, чем машинные инструкции микропроцессора.
Однако, с другой стороны, в отличии от традиционных интерпретаторов, например как в PHP, здесь присутствует дополнительный этап компиляции - интерпретатору не требуется каждый раз анализировать исходный текст программы.
В итоге, Python по производительности находится между традиционными компилирующими и традиционными интерпретирующими языками программирования.
Альтернативные реализации Python
То что было сказано выше о компиляторе и виртуальной машине, характерно для стандартной реализации Python, так называемой CPython (реализации на ANSI C). Однако также существует альтернативные реализации, такие как Jython и IronPython, о которых пойдет сейчас речь.
CPython
Это стандартная и оригинальная реализация Python, названа так, потому что написана на ANSI C. Именно ее мы установили, когда выбрали пакет ActivePython или установили из FreeBSD портов. Поскольку это эталонная реализация, она как правило работает быстрее, устойчивее и лучше, чем альтернативные реализации.
Jython
Первоначальное название JPython, основная цель - тесная интеграция с языком программирования Java. Реализация Jython состоит из Java-классов, которые выполняют компиляцию программного кода на языке Python в байт-код Java и затем передают полученный байт-код виртуальной машине Java (JVM).
Цель Jython состоит в том, чтобы позволить программам на языке Python управлять Java-приложениями, точно также как CPython может управлять компонентами на языках C/C++. Эта реализация имеет беcшовную интеграцию с Java. Поскольку программный код на Python транслируется в байт-код Java, во время выполнения он ведет себя точно также, как настоящая программа на языке Java. Программы на Jython могут выступать в качестве апплетов и сервлетов, создавать графический интерфейс с использованием механизмов Java и т.д. Более того, Jython обеспечивает поддержку возможности импортировать и использовать Java-классы в программном коде Python.
Тем не менее, поскольку реализация Jython обеспечивает более низкую скорость выполнения и менее устойчива по сравнению с CPython, она представляет интерес скорее для разработчиков программ на языке Java, которым необходим язык сценариев в качестве интерфейса к Java-коду.
IronPython
Реализация предназначена для обеспечения интеграции программ Python с приложениями, созданными для работы в среде Microsoft .NET Framework операционной системы Windows, а также в Mono - открытом эквиваленте для Linux. Платформа .NET и среда выполнения языка C# предназначены для обеспечения взаимодействия между программными объектами - независимо от используемого языка программирования, в духе более ранней модели COM компании Microsoft.
IronPython позволяет программам на языке Python играть роль как клиентских, так и серверных компонентов, доступных из других языков программирования .NET. Поскольку разработка ведется компанией Microsoft, от IronPython, помимо прочего, можно было бы ожидать существенной оптимизации производительности.
Средства оптимизации скорости выполнения
Существуют и другие реализации, включая динамический компилятор Psyco и транслятор Shedskin C++, которые пытаются оптимизировать основную модель выполнения.
Динамический компилятор Psyco
Система Psyco - это компонент, расширяющий модель выполнения байт-кода, что позволяет программам выполняться быстрее. Psyco является расширением PVM, которое собирает и использует информацию о типах, чтобы транслировать части байт-кода программы в истинный двоичный машинный код, который выполняется гораздо быстрее. Для такой трансляции не требуется вносить изменения в исходный код или производить дополнительную компиляцию в ходе разработки.
Во время выполнения программы, Psyco собирает информацию о типах объектов, и затем эта информация используется для генерации высокоэффективного машинного кода, оптимизированного для объектов этого типа. После этого произведенный машинный код заменяет соответствующие участки байт-кода, тем самым увеличивается скорость выполнения.
В идеале некоторые участки программного кода под управление Psyco могут выполняться также быстро, как скомпилированный код на языке Си.
Psyco обеспечивает увеличение скорости от 2 до 100 раз, но обычно в 4 раза, при использовании немодифицированного интерпретатора Python. Единственный минус у Psyco, это то обстоятельство, что в настоящее время он способен генерировать машинный код только для архитектуры Intel x86.
Psyco не идет в стандартной поставке, его надо скачать и установить отдельно. Еще есть проект PyPy, который представляет собой попытку переписать PVM с целью оптимизации кода как в Psyco, проект PyPy собирается поглотить в большей мере проект Psyco.
Транслятор Shedskin C++
Shedskin - это система, которая преобразует исходный код на языке Python в исходный код на языке C++, который затем может быть скомпилирован в машинный код. Кроме того, система реализует платформонезависемый подход к выполнению программного кода Python.
Фиксированные двоичные файлы (frozen binaries)
Иногда необходимо из своих программ на Python создавать самостоятельные исполняемые файлы. Это необходимо скорее для упаковки и распространения программ.
Фиксированные двоичные файлы объединяют в единый файл пакета байт-код программ, PVM и файлы поддержки, необходимые программам. В результате получается единственный исполняемый файл, например файл с расширение ".exe" для Windows.
На сегодняшний день существует три основных инструмента создания "frozen binaries":
- py2exe - он может создавать автономные программы для Windows, использующие библиотеки Tkinter, PMW, wxPython и PyGTK для создания графического интерфейса, программы использующие программные средства создания игр PyGame, клиентские программы win32com и многие другие;
- PyInstaller - напоминает py2exe, но также работает в Linux и UNIX и способен производить самоустанавливающиеся исполняемые файлы;
- freeze - оригинальная версия.
Вам надо загружать эти инструменты отдельно от Python, они распространяются бесплатно.
Фиксированные двоичные файлы имеют немалый размер, ибо они содержат в себе PVM, но по современным меркам из все же нельзя назвать необычно большими. Так как интерпретатор Python встроен непосредственно в фиксированные двоичные файлы, его установка не является обязательным требованием для запуска программ на принимающей стороне.
Резюме
На сегодня всё, в следующей статье расскажу о стандартных типах данные в Python, ну и в последующих статьях рассмотрим каждый тип в отдельности, а также функции и операторы для работы с этими типами.
Комментарии
создал файл, запустил его через пайтон, но пишет, что ошибка кодировки (файл сохранен в UTF-8) 🙁
SyntaxError: Non-ASCII character '\xd0'
решение - указать в самом начале файла
# -*- coding: utf-8 -*-
Каков процесс компиляции и загрузки в python?
Сначала Python компилирует исходный код (.py-файл) в формат, известный как байтовый код. Компиляция - это просто шаг перевода, а байт-код - это низкоуровневое и независимое от платформы представление вашего исходного кода. Скомпилированный код обычно хранится в .pyc-файлах и регенерируется при обновлении источника или при необходимости. Чтобы распространять программу для людей, у которых уже установлен Python, вы можете отправить файлы .py или файлы .pyc.
Байт-код (.pyc-файл) загружается в среду исполнения Python и интерпретируется с помощью виртуальной машины Python, которая представляет собой фрагмент кода, который считывает каждую инструкцию в байт-коде и выполняет любую операцию. Компиляция байтового кода является автоматической, и PVM является частью системы Python, установленной на вашем компьютере. PVM всегда присутствует как часть системы Python и является компонентом, который действительно запускает ваши скрипты. Технически это всего лишь последний шаг так называемого интерпретатора Python. И так происходит процесс (очень общий). Конечно, для повышения производительности есть оптимизация и кеширование.
Каждый раз, когда интерпретируемая программа запускается, интерпретатор должен преобразовывать исходный код в машинный код, а также извлекать библиотеки времени выполнения. Этот процесс преобразования заставляет программу работать медленнее, чем сопоставимая программа, написанная на компилированном языке. Python делает что-то умное, чтобы улучшить свою производительность. Он компилируется в байт-код (.pyc-файлы) при первом запуске файла. Это существенно улучшает выполнение кода при следующем вводе или выполнении модуля.
Как работает Python: интерпретатор, байт-код, PVM
Python — интерпретируемый язык программирования. Он не конвертирует свой код в машинный, который понимает железо (в отличие от С и С++). Вместо этого, Python-интерпретатор переводит код программы в байт-код, который запускается на виртуальной машине Python (PVM). Давайте рассмотрим подробнее, как это работает на примере самой популярной реализации интерпретатора — CPython.
Интерпретатор — это программа, которая конвертирует ваши инструкции, написанные на Python, в байт-код и выполняет их. По сути интерпретатор — это программный слой между вашим исходным кодом и железом.
Существует 2 типа интерпретаторов:
- Простой интерпретатор . Он берет одну инструкцию, транслирует и сразу выполняет ее, а затем берет следующую инструкцию.
- Интерпретатор компилирующего типа . Это система из компилятора и интерпретатора. Компилятор переводит исходный код программы в промежуточное представление (байт-код), а интерпретатор (виртуальная машина) выполняет этот байт-код.
- Интерпретатор компилирующего типа (благодаря этому достигается большее быстродействие выполнения программ).
- Считается эталонной реализацией языка Python.
- Написан на C.
- Исходный код CPython находится в открытом доступе.
- Его разработка ведётся группой разработчиков под руководством Гвидо ван Россума — создателя Python.
Кроме этого, у интерпретатора CPython есть особенность — он может работать в режиме диалога (REPL — read-eval-print loop). Интерпретатор считывает законченную конструкцию языка, выполняет её, печатает результаты и переходит к ожиданию ввода пользователем следующей конструкции.
Как CPython выполняет программы
Интерпретатор "Питона" выполняет любую программу поэтапно.
Этап #1. Инициализация
После запуска вашей программы, Python-интерпретатор читает код, проверяет форматирование и синтаксис. При обнаружении ошибки он незамедлительно останавливается и показывает сообщение об ошибке.
Помимо этого, происходит ряд подготовительных процессов:
- анализ аргументов командной строки;
- установка флагов программы;
- чтение переменных среды и т.д.
Этап #2. Компиляция
Интерпретатор транслирует (переводит) исходные инструкции вашей программы в байт-код (низкоуровневое, платформонезависимое представление исходного текста). Такая трансляция необходима в первую очередь для повышения скорости — байт-код выполняется в разы быстрее, чем исходные инструкции.
Если Python-интерпретатор обладает правом записи, он будет сохранять байт-код в виде файла с расширением .pyc . Если исходный текст программы не изменился с момента последней компиляции, при следующем запуске вашей программы, Python сразу загрузит файл .pyc , минуя этап компиляции (тем самым ускорит процесс запуска программы).
Этап #3. Выполнения
Как только байт-код скомпилирован, он отправляется на виртуальную машину Python (PVM). Здесь выполняется байт-код на PVM. Если во время этого выполнения возникает ошибка, то выполнение останавливается с сообщением об ошибке.
PVM является частью Python-интерпретатора. По сути это просто большой цикл, который выполняет перебор инструкций в байт-коде и выполняет соответствующие им операции.
Альтернативы CPython
CPython является стандартной реализацией, но существуют и другие реализации, созданные для специфических целей и задач.
Jython
Основная цель данный реализации — тесная интеграция с языком Java. Работает следующим образом:
- Java-классы выполняют компиляцию программного кода на языке Python в байт-код Java.
- Полученный байт-код запускается на виртуальной машине Java (JVM).
Jython позволить Python-программам управлять Java-приложениями. Во время выполнения такая программа ведет себя точно так же, как настоящая программа на языке Java.
IronPython
Предназначена для обеспечения интеграции Python-программ с C# приложениями на Microsoft .NET Framework или Mono. Принцип работы такой же, как и у Jython.
PyPy
PyPy — это интерпретатор Python, написанный на Python (если быть точнее, то на RPython).
Особенностью PyPy является использование трассирующего JIT-компилятора (just-in-time), который на лету транслирует некоторые элементы в машинный код. Благодаря этому, при выполнении некоторых операций PyPy обгоняет CPython в несколько раз. Но плата за такую производительность — более высокое потребление памяти.

