Шаг 182.
Python: тонкости программирования. Питоновские методы повышения производительности. По ту сторону байткода

    На этом шаге мы рассмотрим внутреннее представление данных и программы.

    Когда интерпретатор СPython исполняет вашу программу, он сначала ее транслирует в последовательность байткодовых инструкций. Байткод - это промежуточный язык для виртуальной машины Python, который используется в качестве оптимизации производительности.

    Вместо того чтобы непосредственно исполнить человекочитаемый исходный код, в Python используются компактные цифровые коды, константы и ссылки, которые представляют результат лексического и семантического анализа, выполняемого компилятором.

    Это экономит время и оперативную память для повторных исполнений программ или частей программ. Например, байткод, который получается в результате этого шага компиляции, кэшируется на диске в файлах .pyc и .pyo, чтобы во второй раз исполнение того же самого файла Python проходило быстрее.

    Все это абсолютно прозрачно для программиста. Вам не нужно знать о том, что происходит в этот промежуточный шаг трансляции или как виртуальная машина Python работает с байткодом. На самом деле формат байткода считается деталью реализации, и не гарантируется, что он останется стабильным или совместимым между различными версиями Python. И все же, мы убеждены, что возможность заглянуть по ту сторону абстракций, обеспечиваемых интерпретатором Python, весьма информативна. Понимание, по крайней мере, некоторых внутренних механизмов поможет вам писать более производительный код (когда производительность имеет значение).

    В качестве подопытного образца давайте возьмем простую функцию greet(), с которой можно поэкспериментировать и которую можно использовать, чтобы разобраться в байткоде Python:

>>> def greet(name):
	return 'Привет, ' + name + '!'

>>> greet('Гвидо')
'Привет, Гвидо!'

    Если помните, было отмечено, что Python сначала транслирует наш исходный код в промежуточный язык, прежде чем он его "выполнит". Так вот, если это правда, то мы должны увидеть результаты этого шага компиляции. И мы можем.

    Каждая функция имеет атрибут __code__Python 3), который мы можем использовать, чтобы получить инструкции виртуальной машины, константы и переменные, используемые нашей функцией greet():

>>> greet('Гвидо')
'Привет, Гвидо!'
>>> greet.__code__.co_code
b'd\x01|\x00\x17\x00d\x02\x17\x00S\x00'
>>> greet.__code__.co_consts
(None, 'Привет, ', '!')
>>> greet.__code__.co_varnames
('name',)

    Вы видите, что co_consts содержит части строки приветствия, которую собирает наша функция. Константы и код хранятся отдельно, чтобы сэкономить пространство памяти. Константы... как бы сказать... константны, то есть они не подлежат изменению и используются попеременно в разных местах.

    Поэтому вместо того, чтобы повторять фактические постоянные величины в потоке команд co_code, Python хранит константы отдельно в поисковой таблице. Поток команд затем может ссылаться на константу по индексу в поисковой таблице. То же самое верно и для переменных, хранящихся в поле co_varnames.

    Надеюсь, что этот общий принцип начинает проясняться. Но рассмотрение потока команд co_code по-прежнему заставляет нас чувствовать себя нехорошо. Этот промежуточный язык явно предназначен для того, чтобы с ним было легко работать виртуальной машине Python, а не людям. В конце концов, для это существует текстоориенированный исходный код.

    На следующем шаге мы закончим изучение этого вопроса.




Предыдущий шаг Содержание Следующий шаг