байт код python это
Как работает Python
Если нет, то сегодня ваш счастливый день. Я расскажу вам, что такое байт-код Python, как его использует Python для выполнения вашего кода и как знание о нем может вам помочь.
Python часто описывается как интерпретируемый язык – язык, на котором ваш исходный код переводится в нативные инструкции процессора во время работы программы, но это только частично верно. Python, как и многие интерпретируемые языки, на самом деле компилирует исходный код в набор инструкций для виртуальной машины, и интерпретатор Python является реализацией этой виртуальной машины. Этот промежуточный формат называется «байт-код».
Давайте посмотрим на пример. Вот классический «Hello, World!», написанный на Python:
И вот байт-код, в который он превращается (переведенный в удобочитаемую форму):
Если вы наберете функцию hello() и используете для ее запуска интерпретатора CPython, приведенный выше листинг будет выполняться Python. Хотя это может показаться немного странным, поэтому давайте посмотрим детальнее на происходящее.
Внутри виртуальной машины Python
CPython использует виртуальную машину на основе стека. То есть он полностью ориентирован на структуры данных стека (где вы можете «протолкнуть» элемент на «вершину» структуры или «вытолкнуть» элемент с «верха»).
CPython использует три типа стеков:
Большинство инструкций байт-кода Python манипулируют оценочным стеком текущего фрейма стека вызовов, хотя есть некоторые инструкции, которые делают другие вещи (например, переход к конкретным инструкциям или манипулирование стеком блоков).
Чтобы почувствовать это, предположим, что у нас есть некоторый код, который вызывает функцию, например: my_function (my_variable, 2). Python преобразует это в последовательность из четырех инструкций байт-кода:
Инструкция CALL_FUNCTION будет иметь аргумент 2, что указывает на то, что Python должен извлечь два позиционных аргумента из верхней части стека; тогда вызываемая функция будет сверху, и она также может быть выдвинута (для функций, включающих аргументы ключевых слов, используется другая инструкция – CALL_FUNCTION_KW, но с аналогичным принципом работы. Также используется третья инструкция, CALL_FUNCTION_EX, для вызовов функций, которые включают распаковку аргументов с помощью операторов * или **). Как только Python получит все это, он выделит новый фрейм в стеке вызовов, заполнит локальные переменные для вызова функции и выполнит байт-код my_function внутри этого кадра. Как только это будет сделано, кадр будет извлечен из стека вызовов, а в исходном кадре возвращаемое значение my_function будет помещено поверх стека оценки.
Доступ и понимание байт-кода Python
Если вы хотите поэкспериментировать с этим, модуль dis в стандартной библиотеке Python очень полезен; модуль dis предоставляет «дизассемблер» для байт-кода Python, что позволяет легко получить удобочитаемую версию и просмотреть различные инструкции байт-кода. Документация для модуля dis просматривает его содержимое и предоставляет полный список инструкций байт-кода, а также информацию о том, что они делают и какие аргументы они принимают.
Например, чтобы получить список байт-кодов для функции hello(), я набрал его в интерпретаторе Python и запустил:
Функция dis.dis() будет дизассемблировать функцию, метод, класс, модуль, скомпилированный объект кода Python или строковый литерал, содержащий исходный код, и напечатает понятную для человека версию. Другая удобная функция в модуле dis – это distb(). Вы можете передать ему объект трассировки Python или вызвать его после возникновения исключения, и он разберет самую верхнюю функцию в стеке вызовов во время исключения, напечатает его байт-код и вставит указатель на инструкцию, которая вызвала исключение.
Также полезно посмотреть на объекты скомпилированного кода, которые Python собирает для каждой функции, поскольку при выполнении функции используются атрибуты этих объектов кода. Например:
Объект кода доступен как атрибут __code__ в функции и содержит несколько важных атрибутов:
Многие инструкции байт-кода, особенно те, которые загружают значения для помещения в стек или сохраняют значения в переменных и атрибутах, – используют индексы в этих кортежах в качестве аргументов.
Итак, теперь мы можем понять список байт-кода функции hello():
«Необработанный» байт-код (как нечитаемые человеком байты) также доступен в объекте кода как атрибут co_code. Вы можете использовать список dis.opname для поиска имен инструкций байт-кода по их десятичным значениям байтов, если вы хотите попытаться вручную разобрать функцию.
Использование байт-кода
Сейчас вы наверное задаетесь вопросом: «Какова практическая ценность всей этой информации?».
Во-первых, понимание модели исполнения Python поможет вам понять ваш код. Людям нравится шутить о том, что C является своего рода «переносимым ассемблером», где вы можете догадаться, в какие машинные инструкции превратится тот или иной фрагмент исходного кода C. Понимание байт-кода даст вам те же возможности с Python. Если вы можете предвидеть, во что превращается байт-код вашего исходного кода Python, вы можете принять более правильное решение о том, как его написать и оптимизировать.
Во-вторых, понимание байт-кода – полезный способ ответить на вопросы о Python. Например, я часто вижу новых программистов на Python, которые задаются вопросом, почему одни конструкции быстрее других (например, почему <> быстрее, чем dict()). Знание того, как получить доступ и прочитать байт-код Python, позволяет вам выработать ответы (попробуйте: dis.dis(«<>«) или dis.dis(«dict ()»)).
Наконец, понимание байт-кода и того, как Python выполняет его, дает полезные знания о конкретном виде программирования, которым программисты Python нечасто занимаются, – стек-ориентированное программирование.
Интерпретатор – это программа, которая конвертирует ваши инструкции, написанные на Python, в байт-код и выполняет их. По сути интерпретатор – это программный слой между вашим исходным кодом и железом.
Существует 2 типа интерпретаторов:
Как CPython выполняет программы
Интерпретатор «Питона» выполняет любую программу поэтапно.
Этап #1. Инициализация
После запуска вашей программы, Python-интерпретатор читает код, проверяет форматирование и синтаксис. При обнаружении ошибки он незамедлительно останавливается и показывает сообщение об ошибке.
Помимо этого, происходит ряд подготовительных процессов:
Этап #2. Компиляция
Интерпретатор транслирует (переводит) исходные инструкции вашей программы в байт-код (низкоуровневое, платформонезависимое представление исходного текста). Такая трансляция необходима в первую очередь для повышения скорости – байт-код выполняется в разы быстрее, чем исходные инструкции.
Этап #3. Выполнения
Как только байт-код скомпилирован, он отправляется на виртуальную машину Python (PVM). Здесь выполняется байт-код на PVM. Если во время этого выполнения возникает ошибка, то выполнение останавливается с сообщением об ошибке.
PVM является частью Python-интерпретатора. По сути это просто большой цикл, который выполняет перебор инструкций в байт-коде и выполняет соответствующие им операции.
Альтернативы СPython
CPython является стандартной реализацией, но существуют и другие реализации, созданные для специфических целей и задач.
Jython
Основная цель данный реализации – тесная интеграция с языком Java. Работает следующим образом:
Jython позволить Python-программам управлять Java-приложениями. Во время выполнения такая программа ведет себя точно так же, как настоящая программа на языке Java.
IronPython
Особенностью PyPy является использование трассирующего JIT-компилятора (just-in-time), который на лету транслирует некоторые элементы в машинный код. Благодаря этому, при выполнении некоторых операций PyPy обгоняет CPython в несколько раз. Но плата за такую производительность – более высокое потребление памяти.
Погружение в пучину интерпретатора Python. Ч1
От переводчика: Наверно всем интересно, что внутри у инструмента, который используешь, этот интерес овладел и мной, но главное не утопать в нём и не закопаться так что не вылезти. Найдя для себя интересный материал, я решил заботливо перевести его и представить хабросообществу (моя первая публикация, прошу ногами сильно не пинать). Тем, кому интересен как Python работает на самом деле, прошу проследовать под кат.
Последние три месяца я потратил много времени на byterun, интерпретатор питоновского байткода, написанного на питоне. Работа над этим проектом была для меня захватывающе весёлой и познавательной. Я был бы рад, если бы вы тоже его потыкали. Но прежде нам надо немного остепенится, понять как работает python, так, чтобы мы знали, что такое интерпретатор на самом деле и с чем его едят.
Я подразумеваю, что вы сейчас в том же положении, что и я три месяца назад. Вы понимаете python, но понятия не имеете как он работает.
Небольшая заметка: Я работаю с версией 2.7 в этом посте. Третья версия почти схожа со второй, есть небольшие различия в синтаксисе и наименованиях, но в целом всё тоже самое.
Как работает python?
Мы начнём с очень (очень очень) высокого уровня внутренней работы. Что происходит когда вы выполняете код в вашем интерпретаторе?
Годы идут, ледянки тают, Линус Торвальдс пилит очередное ядро, а 64 битый процессор без устали трудится, тем временем происходит четыре шага: лексической анализ, парсинг, компиляция и наконец таки интерпретация. Парсер забирает скормленные ему инструкции и генерирует структуру которая объясняет их связь формируя AST( Абстрактное Синтаксическое Дерево). Компилятор затем преобразует AST в одни (или несколько) объектов кода (байткод + обвязка). Потом интерпретатор выполняет каждый объект.
Я не собираюсь говорить об лексическом анализе, парсинге или компиляции сегодня, наверно потому что я сам про эти вещи ни чего не знаю, но не унывайте: вы всегда сможете изучить это, потратив часов этак пятьдесят. Мы предположим, что эти шаги прошли хорошо и успешно, и у нас есть на руках объекты python кода.
Перед тем как приступить к делу, я хочу сделать небольшую ремарку: в данном топике мы будем говорить об объектах функциях, объектах кода, и байткоде. Это всё разные вещи. Давайте начнём с функций. Нам не обязательно вникать глубоко в них, чтобы добраться до интерпретатора, но я просто хочу прояснить, что объекты функции и объекты кода — это две большие разницы, а объекты функции — самые интересные.
Объекты функции
Вы наверно могли слышать про «объекты функции». Это вещи которые люди подразумевают когда говорят: «Функции — это объекты первого класса». Давайте изучим их подробнее:
«Функции это объекты первого класса» означает что функции — это объекты также как список это объект или экземпляры MyObject это объекты. Раз foo это объект, мы можем исследователь его не выполняя его (в этом и есть разница между foo() и foo). Мы можем предать foo как параметр в другую функцию или можем присвоить его переменной.
Давайте немного посмотрим на foo подробней:
Как вы можете видеть в выше приведённом коде, объект кода это атрибут объекта функции. Объект кода генерируется питоновским компилятором и интерпретатором, он содержит информацию необходимую для работы интерпретатора. Давайте посмотрим на атрибуты объекта кода:
Здесь целая куча ништяков, большинство из которых нм сейчас не нужно. Давайте подробнее рассмотрим три атрибута объекта foo.
Вот что здесь есть: имена переменных и констант которые используются в нашей функции и количество принимаемых аргументов. Но мы всё ещё не видим ни чего что было бы похоже на инструкции. Инструкции называют байткодом ссылка, кстати это атрибут объекта кода:
Напоминаю что байткод и объекты кода это не одно и тоже. Байткод это атрибут объекта кода помимо многих других атрибутов. Так что же такое байткод? Ну это просто набор байт. Они выглядят странно когда мы их печатаем потому что некоторым байтом сопоставимы символы а другим нет, давайте выведем их как числа.
Вот байты которые творят всю магию. Интерпретатор будет последовательно и безустанно выбирать байты, смотреть какие они операции выполняют и с какими аргументами и исполнять команды. Для того чтобы пойти ещё дальше можно просмотреть исходный код Cpython а конкретно ceval.c что мы сделаем позднее.
Дизассемблирование байткода
Дизассемблирование означает взять все эти байты и преобразовать их во что-нибудь, что мы человеки способны понять. Это не выполняется в стандартном цикле питона. Сегодня для этой задачи есть отличный инструмент — модуль dis. Мы воспользуемся функцией dis.dis чтобы проанализировать что делает наша foo.
Первый номер это строка исходного python кода, второй номер это смещение внутри байткода: LOAD_CONST находится на позиции 0, а STORE_FAST на позиции 3 и так далее. Средняя колонка это название самой инструкции, последние две колонки дают понятие об аргументах инструкции (ели они есть), четвертая колонка показывает сам аргумент, который представляет собой индекс в других атрибутов объекта кода. В этом примере аргумент для LOAD_CONST это индекс в списке co_consts, а аргумент для STORE_FAST это индекс в co_varnames, в пятой колонке выводятся имена переменных или значение констант. Мы можем с легкостью это проверить:
Это также объясняет вторую инструкцию STORE_FAST которая находится по позиции 3 в байткоде. Если инструкция имеет аргумент следующие два байта и есть этот аргумент. Работа интерпретатора как раз таки в том чтобы не запутается и продолжать сеять разумное, доброе, вечное. (вы могли заметить что BINARY_ADD не имеет аргументов, не волнуйтесь мы ещё вернемся к этому)
Была одна вешь которая удивляла меня когда я начел разбираться в том как работает python, как python может быть динамическим, если он ещё и «компилируется»? Обычно эти два слова «антонимы», есть динамические языки такие как Python, Ruby, и Javascript, а есть компилируемые таки как C, Java, и Haskell.
Когда люди говорят об компилируемых языках они имеют ввиду компиляцию в нативные x86/ARM/etc инструкции. Интерпретируемый язык не имеет компиляции вообще, разве что только «компилируется» на лету в байткод. Интерпретатор питона разбирает байткод и выполняет его внутри виртуальной машины, что кстати достаточно много работы, но мы поговорим об этом позднее.
Для того чтобы быть динамическим надо быть абстрактным, давайте посмотрим что это значит:
Эта дизассемблированая функция в байткоде. К тому времени как мы получаем приглашение функция modus была скомпилирована и объект корда был сгенерирован. Достаточно внезапно, но операция остатка от деления % (операция modulus) преобразуется в BINARY_MODULO. Похоже этой функцией можно воспользоваться для чисел:
Неплохо, а что если мы передадим что то другое, например строку.
Опана, что это тут? Вы наверно уже видели это раньше:
Когда операция BINARY_MODULO выполняется для двух строк она выполняет подстановку строк вместо остатка от деления. Эта ситуация отличный пример динамической типизации. Когда компилятор генерирует объект кода для modulus он не имеет понятия что такое x и y, строки ли они или числа или что-то ещё. Он просто выполняет инструкции: загрузить одну перемененную, загрузить другую, выполнять препарацию бинарного модуля, вернуть результат. Работа интерпретатора в том чтобы понимать что BINARY_MODULO значит в текущем контексте. Наша функция modulus может считать остаток, подставлять строки… может что-то ещё? Если мы определим класс с методом __mod__ то мы сможем сделать что угодно.
Одна и та же функция с одним и тем же байткодом может выполнять разные операции в зависимости от типа контекста. Также функция modulus может возбудить исключение для примера TypeError если мы вызовем его для объектов, которые не реализованы.
Это является одной из причин того, почему трудно оптимизировать python. Вы не знаете, когда вы генерируете код объекта и байт-код, что за объекты будут в конечном итоге. Russell Power и Alex Rubinsteyn написали статью «как быстр может быть python», это статья достаточного содержательная.
На сегодня пока все. Оригинал статьи тут. Прошу прошения за возможные ошибки т.к. от природы обладаю врождённой безграмотностью и вынужден пользоваться машинным способом проверки текста.
Как работает Python?
Всем еще раз привет, сейчас расскажу о том, как работает Python, что такое интерпретатор, как работает компилятор и что такое байт-код, далее расскажу о виртуальной машине (PVM) и о производительности Python. Также о альтернативных реализациях интерпретатора.
После того, как вы установили себе Python, перейдем к теоретически-практической части и начнем с того что из себя представляет интерпретатор.
Интерпретатор
В зависимости от используемой версии Python сам интерпретатор может быть реализован как программа на языке C, как набор классов Java и в каком-либо другом виде, но об этом позже.
Запуск сценария в консоли
Давайте запустите в консоле интерпретатор:
Теперь он ожидает ввода комманд, введите туда следующую инструкцию:
ура, наша первая программа! 😀
Запуск сценария из файла
Создайте файл «test.py», с содержимым:
и выполните этот файл:
Вы увидите в консоли результат, поехали дальше!
Динамическая компиляция и байт-код
В следующий раз, когда вы запустите свою программу интерпретатор минует этап компиляции и отдаст на выполнение откомпилированный файл с расширением «.pyc». Однако, если вы изменили исходные тексты вашей программы, то снова произойдет этап компиляции в байт-код, так как Python автоматически следит за датой изменения файла с исходным кодом.
Если Python окажется не в состоянии записать файл с байт-кодом, например из-за отсутствия прав на запись на диск, то программа не пострадает, просто байт-код будет собран в памяти и при завершении программы оттуда удален.
Виртуальная машина Python (PVM)
Производительность
По этим причинам программы на Python не могут выполняться также быстро как на C/C++. Обход инструкций выполняет виртуальная система, а не микропроцессор, и чтобы выполнить байт-код, необходима дополнительная интерпретация, инструкции которой требуют большего времени, чем машинные инструкции микропроцессора.
В итоге, Python по производительности находится между традиционными компилирующими и традиционными интерпретирующими языками программирования.
Альтернативные реализации Python
То что было сказано выше о компиляторе и виртуальной машине, характерно для стандартной реализации Python, так называемой CPython (реализации на ANSI C). Однако также существует альтернативные реализации, такие как Jython и IronPython, о которых пойдет сейчас речь.
CPython
Это стандартная и оригинальная реализация Python, названа так, потому что написана на ANSI C. Именно ее мы установили, когда выбрали пакет ActivePython или установили из FreeBSD портов. Поскольку это эталонная реализация, она как правило работает быстрее, устойчивее и лучше, чем альтернативные реализации.
Jython
Цель Jython состоит в том, чтобы позволить программам на языке Python управлять Java-приложениями, точно также как CPython может управлять компонентами на языках C/C++. Эта реализация имеет беcшовную интеграцию с Java. Поскольку программный код на Python транслируется в байт-код Java, во время выполнения он ведет себя точно также, как настоящая программа на языке Java. Программы на Jython могут выступать в качестве апплетов и сервлетов, создавать графический интерфейс с использованием механизмов Java и т.д. Более того, Jython обеспечивает поддержку возможности импортировать и использовать Java-классы в программном коде Python.
Тем не менее, поскольку реализация Jython обеспечивает более низкую скорость выполнения и менее устойчива по сравнению с CPython, она представляет интерес скорее для разработчиков программ на языке Java, которым необходим язык сценариев в качестве интерфейса к Java-коду.
IronPython
Средства оптимизации скорости выполнения
Существуют и другие реализации, включая динамический компилятор Psyco и транслятор Shedskin C++, которые пытаются оптимизировать основную модель выполнения.
Динамический компилятор Psyco
Во время выполнения программы, Psyco собирает информацию о типах объектов, и затем эта информация используется для генерации высокоэффективного машинного кода, оптимизированного для объектов этого типа. После этого произведенный машинный код заменяет соответствующие участки байт-кода, тем самым увеличивается скорость выполнения.
В идеале некоторые участки программного кода под управление Psyco могут выполняться также быстро, как скомпилированный код на языке Си.
Psyco обеспечивает увеличение скорости от 2 до 100 раз, но обычно в 4 раза, при использовании немодифицированного интерпретатора Python. Единственный минус у Psyco, это то обстоятельство, что в настоящее время он способен генерировать машинный код только для архитектуры Intel x86.
Psyco не идет в стандартной поставке, его надо скачать и установить отдельно. Еще есть проект PyPy, который представляет собой попытку переписать PVM с целью оптимизации кода как в Psyco, проект PyPy собирается поглотить в большей мере проект Psyco.
Транслятор Shedskin C++
Фиксированные двоичные файлы (frozen binaries)
Иногда необходимо из своих программ на Python создавать самостоятельные исполняемые файлы. Это необходимо скорее для упаковки и распространения программ.
Фиксированные двоичные файлы объединяют в единый файл пакета байт-код программ, PVM и файлы поддержки, необходимые программам. В результате получается единственный исполняемый файл, например файл с расширение «.exe» для Windows.
На сегодняшний день существует три основных инструмента создания «frozen binaries»:
Вам надо загружать эти инструменты отдельно от Python, они распространяются бесплатно.
Фиксированные двоичные файлы имеют немалый размер, ибо они содержат в себе PVM, но по современным меркам из все же нельзя назвать необычно большими. Так как интерпретатор Python встроен непосредственно в фиксированные двоичные файлы, его установка не является обязательным требованием для запуска программ на принимающей стороне.
Резюме
На сегодня всё, в следующей статье расскажу о стандартных типах данные в Python, ну и в последующих статьях рассмотрим каждый тип в отдельности, а также функции и операторы для работы с этими типами.
создал файл, запустил его через пайтон, но пишет, что ошибка кодировки (файл сохранен в UTF-8) 🙁
SyntaxError: Non-ASCII character ‘\xd0’
Тип данных bytes, байтовые строки
Синтаксис для байтовых литералов в основном такой же, как и для строковых литералов, за исключением того, что добавляется префикс ‘b’ :
Как и в случае строковых литералов, байтовые литералы могут также использовать префикс ‘r’ для отключения обработки escape-последовательностей.
Помимо литеральных форм, объекты bytes могут быть созданы с помощью встроенного класса bytes() :
Для байтовых строк доступны следующие операции:
Две шестнадцатеричные цифры точно соответствуют одному байту и по этому шестнадцатеричные числа являются широко используемым форматом для описания двоичных данных. Соответственно, тип bytes имеет дополнительный метод класса для чтения данных в этом формате:
bytes.fromhex(string) ::
bytes.hex() ::
Метод bytes.hex() преобразовывает объект bytes в его шестнадцатеричное представление. Возвращает строковый объект, содержащий две шестнадцатеричные цифры для каждого байта.
В Python-3.8 метод bytes.hex() поддерживает необязательные параметры sep и bytes_per_sep для вставки разделителей между байтами в шестнадцатеричный вывод.
Если нужно облегчить чтение шестнадцатеричной строки, можно указать параметр sep разделителя из одного символа для включения в вывод. По умолчанию между каждым байтом. Второй необязательный параметр bytes_per_sep управляет интервалом. Положительные значения вычисляют положение разделителя справа, отрицательные значения слева.