Библиотека сайта rus-linux.net
Интерпретатор языка Python, написанный на языке Python
Оригинал: A Python Interpreter Written in Python
Автор: Allison Kaptur
Дата публикации: July 12, 2016
Перевод: Н.Ромоданов
Дата перевода: февраль 2017 г.
Это седьмая часть статьи "Интерпретатор языка Python, написанный на языке Python".
Перейти к
началу.
Динамическая типизация: Чего не знает компилятор
Вы, наверное, слышали, что язык Python является "динамическим" языком, а именно - "динамически типизированным" языком. Все то, что мы делали к этому моменту, проливает некоторый свет на это определение.
Одна из сторон «динамики» в этом смысле означает, что много действий выполняется на этапе выполнения программы. Ранее мы видели, что у компилятора языка Python не слишком много информации о том, что, на самом деле, делает код. Например, рассмотрим небольшую функцию mod
, приведенную ниже. Функция mod получает два аргумента и возвращает остаток от деления первого аргумента на второй аргумент. В байт-коде мы видим, что загружаются переменные a
и b
, а затем байт-код BINARY_MODULO
самостоятельно выполняет операцию.
>>> def mod(a, b): ... return a % b >>> dis.dis(mod) 2 0 LOAD_FAST 0 (a) 3 LOAD_FAST 1 (b) 6 BINARY_MODULO 7 RETURN_VALUE >>> mod(19, 5) 4
Вычисление 19 % 5 дает нам результат, равный 4, и тут нет никаких сюрпризов. А что произойдет, если мы вызовем эту функцию с другими аргументами?
>>> mod("by%sde", "teco") 'bytecode'
Что сейчас произошло? Вы, наверное, видели этот синтаксис раньше, но в другом контексте:
>>> print("by%sde" % "teco") bytecode
Использование символа % при форматировании строки во время подготовки ее на печать означает вызов инструкции BINARY_MODULO
. Эта инструкция, когда она исполняется, выполняет свое действие над двумя значениями , находящимися в стеке, причем независимо от того, являются ли они строками, числами или экземплярами класса, который вы сами определили. Байт-код был сгенерирован на этапе компиляции функции (или, что эффективнее, в момент ее определения) и с различными типами аргументов будет использоваться один и тот же фрагмент байт-кода.
Компилятор языка Python мало знает о такой особенности использования байт-кода. На интерпретатор возлагается задача определения типа объекта, с которым работает операция BINARY_MODULO
, и выполнение для этого типа объекта правильных действий. Вот почему язык Python описывается как динамически типизированный: вы не узнаете типы аргументов функции до тех пор, пока вы, на самом деле, не начнете ее выполнять. В противоположность этому, в языке, который является статически типизированным, программист, прежде всего, сообщит компилятору о том, какого типа будут аргументы (или компилятор это сразу выяснит самостоятельно).
Отсутствие у компилятора подобной информации является одной из причин изучения приемов оптимизации языка Python или статического его анализа — если просто смотреть на байт-код, без его фактического выполнения, то вы не узнаете, что будет делать каждая инструкция! На самом деле, вы можете определить класс, в котором реализуется метод __mod__
, а язык Python будет обращаться в этому методу в случае, если вы для своих объектов будете пользоваться операцией %. Так что инструкция BINARY_MODULO
может, вообще, запускаться на любом коде!
Просто давайте взглянем на следующий код, выполнение первого действия a % b
в котором кажется расточительным.
def mod(a,b): a % b return a %b
К сожалению, статический анализ этого кода, т. е. Тот анализ, который можно сделать, не запуская код, не может выяснить, что первое действие a % b
, на самом деле, ничего не делает. Вызов функции __mod__
с операцией % может выполнять запись в файл, или взаимодействовать с другой частью вашей программы, или же выполнять какие-то действия с литералами, что возможно в языке Python. Трудно оптимизировать функцию, когда вы не знаете, что она делает! В большой статье Рассела Пауэра и Алекса Рубинштейна "Насколько может быть быстрой интерпретация языка Python?" (Russell Power and Alex Rubinsteyn, "How fast can we make interpreted Python?") отмечается, что "при общем отсутствии информации о типе, каждая команда должна рассматриваться как вызов произвольного метода INVOKE_ARBITRARY_METHOD
"./p>
Заключение
Интерпретатор Byterun представляет собой компактный интерпретатор языка Python, в котором легче разобраться, чем в интерпретаторе CPython. В интерпретаторе Byterun скопированы детали реализации первичных структур интерпретатора CPythonв: интерпретатор, использующий стек, работает с наборами инструкций на байт-коде. Интерпретатор последовательно шаг за шагом выполняет эти инструкции или выполняет переходы на другие инструкции кода и одновременно помещает данные в стек данных или удаляет их оттуда. По мере того, как происходят вызовы функций, выход из функций или используются генераторы, интерпретатор создает и уничтожает фреймы, либо выполняет переходы между фреймами. Интерпретатор Byterun также подвержен тем же самым ограничениям, что другие интерпретаторы: поскольку в языке Python используется динамическая типизация, интерпретатор для того, чтобы реализовать правильное поведение программы, должен на этапе выполнения программы выполнять дополнительные действия.
Я предполагаю, что вы будете дизассемблировать свои программы и запускать их с помощью интерпретатора Byterun. Вы быстро столкнетесь с инструкциями, которые еще не реализованы в этом сокращенном варианте интерпретатора. Полную реализацию можно найти по ссылке ceval.c
реального интерпретатора CPython, то вы сможете реализовать недостающие функции самостоятельно!
Благодарности
Спасибо Неду Батчелдеру (Ned Batchelder) за запуск этого проекта и приглашение меня в проект в качестве исполнителя, Майклу Арнтзениусу (Michael Arntzenius) за его помощь в отладке кода и в редактировании текста статьи, Лете Монтополи (Leta Montopoli) за ее редакторские правки и всему центру Recurse Center за его поддержку и интерес к данному проекту. Все обнаруженные ошибки относите только на мой счет.
- В этой главе используется байт-код, созданный для версии Python 3.5 или более ранних версий; в спецификацию байт-кода версии Python 3.6 были внесены некоторые изменения.
- Спасибо Майклу Арнтзениусу (Michael Arntzenius) за объяснение этой ошибки.
Перейти к началу статьи.