Наши партнеры

UnixForum





Библиотека сайта 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. Вы быстро столкнетесь с инструкциями, которые еще не реализованы в этом сокращенном варианте интерпретатора. Полную реализацию можно найти по ссылке https://github.com/nedbat/byterun, либо, а если внимательно ознакомитесь с исходным кодом ceval.c реального интерпретатора CPython, то вы сможете реализовать недостающие функции самостоятельно!

Благодарности

Спасибо Неду Батчелдеру (Ned Batchelder) за запуск этого проекта и приглашение меня в проект в качестве исполнителя, Майклу Арнтзениусу (Michael Arntzenius) за его помощь в отладке кода и в редактировании текста статьи, Лете Монтополи (Leta Montopoli) за ее редакторские правки и всему центру Recurse Center за его поддержку и интерес к данному проекту. Все обнаруженные ошибки относите только на мой счет.

  1. В этой главе используется байт-код, созданный для версии Python 3.5 или более ранних версий; в спецификацию байт-кода версии Python 3.6 были внесены некоторые изменения.
  2. Спасибо Майклу Арнтзениусу (Michael Arntzenius) за объяснение этой ошибки.

Перейти к началу статьи.