Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Интерпретатор Python: о чём думает змея? (часть I-III)

Anna | 15.06.2014 | нет комментариев
От переводчика

Крайне свободный перевод серии из трёх статей об устройстве питоновского интерпретатора. Автор занимается разработкой собственного велосипеда по этой теме и решил поделиться умениями, появившимися в процессе. Посмотрим, что у него из этого получилось.

Данная серия статей рассчитана на тех, кто может писать на python в целом, но нехорошо представляет как данный язык устроен изнутри. Собственно, как и я три месяца назад.

Маленький дисклеймер: свой рассказ я буду вести на примере интерпретатора python 2.7. Всё, о чем пойдёт речь дальше, дозволено повторить и на python 3.x с поправкой на некоторые отличия в синтаксисе и именование некоторых функций.

Выходит, начнём.

Часть I. Слушай Питон, а что у тебя внутри?

Начнём с немножко (на самом деле, с крепко) высокоуровневого взора на то, что же из себя представляет наша любимая змея. Что происходит, когда вы набираете строку сходственную этой в интерактивном интерпретаторе?

>>> a = "hello"

Ваш палец падает на enter и питон инициирует 4 следующих процесса: лексический обзорпарсинг,компиляцию и непринужденно интерпретацию. Лексический обзор – это процесс разбора набранной вами строки кода в определенную последовательность символов, называемых токенами. Дальше парсер на основе этих токенов генерирует конструкцию, которая отображает взаимоотношения между входящими в неё элементами (в данном случае, конструкция это абстрактное синтаксическое древо либо АСД). Дальше, применяя АСД, компилятор создаёт один либо несколько объектных модулей и передаёт их в интерпретатор для непосредственного выполнения.

Я не буду углубляться в темы лексического обзора, парсинга и компиляции, в основном потому, что сам не имею о них ни минимального представления. Взамен этого, давайте отменнее предположим, что мудрые люди сделали всё как нужно и данные этапы в питоновском интерпретаторе отрабатывают без ошибок. Представили? Двигаем дальше.

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

Выходит,

Объекты функций либо функции, как объекты

Если это не первая ваша статья о программировании на питоне, вы обязаны быть наслышаны о неких «объектах функций». Это именно о них люди с разумным видом рассуждают в контексте разговоров о «функциях, как объектах первого класса» и «наличии функций первого класса в питоне». Разглядим дальнейший пример:

>>> def foo(a):
...     x = 3
...     return x   a
...
>>> foo
<function foo at 0x107ef7aa0>

Выражение «функции – это объекты первого класса» обозначает, что функции – это объекты первого класса, в том смысле, в коем и списки – это объекты, и экземпляр класса MyObject – объект. И так как foo это объект, он имеет важность сам по себе, безотносительно вызова его, как функции (то есть, foo и foo() — это различные вещи). Мы можем передать foo в иную функцию в качестве довода, можем переназначить её на новое имя (other_function = foo). С функциями первого класса дозволено делать, что желательно и они всё стерпят.

Часть 2. Объектные модули

На данном этапе we need to go deeper, Дабы узнать, что объект функции в свою очередь содержит объект кода:

>>> def foo(a):
...     x = 3
...     return x   a
...
>>> foo
<function foo at 0x107ef7aa0>
>>> foo.func_code
<code object foo at 0x107eeccb0, file "<stdin>", line 1>

Как видно из приведённого листинга, объектный модуль является признаком объекта функции (у которого есть и уйма других признаков, но в данном случае специального интереса они не представляют в силу простотыfoo).

Объектный модуль генерируется питоновским компилятором и дальше передаётсся интерпретатору. Модуль содержит всю нужную для выполнения информацию. Давайте посмотрим на его признаки:

>>> dir(foo.func_code)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename',
'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals',
'co_stacksize', 'co_varnames']

Их, как видите, много, следственно все рассматривать не будем, для примера остановимся на трёх особенно внятных:

>>> foo.func_code.co_varnames
('a', 'x')
>>> foo.func_code.co_consts
(None, 3)
>>> foo.func_code.co_argcount
1

Признаки выглядят достаточно подсознательно:
co_varnames – имена переменных
co_consts – значения, о которых знает функция
co_argcount – число доводов, которые функция принимает

Всё это крайне познавательно, но выглядит несколько черезчур высокоуровнево для нашей темы, не правда ли? Где же инструкции интерпретатору для непосредственного выполнения нашего модуля? А такие инструкции есть и представлены они байткодом. Конечный также является атрибутом объектного модуля:

>>> foo.func_code.co_code 'd\x01\x00}\x01\x00|\x01\x00|\x00\x00\x17S'

Что за неизвестная байтовая фигня, спросите вы?

Часть III. Байткод

Вы вероятно и сами понимаете, но я, на каждый случай, озвучу – «байткод» и «объект кода» это различные вещи: 1-й является признаком второго, среди многих других (см. часть 2). Признак именуется co_code и содержит все нужные инструкции для выполнения интерпретатором.

Что же из себя представляет данный байткод? Как следует из наименования, это легко последовательность байтов. При итоге в консоль выглядит она довольно абсурдно, следственно давайте приведём её к числовой последовательности, пропустив через ord:

>>> [ord(b) for b in foo.func_code.co_code] [100, 1, 0, 125, 1, 0, 124, 1, 0, 124, 0, 0, 23, 83]

Таким образом мы получили числовое представление питоновского байткода. Интерпретатор пройдётся по всякому байту в последовательности и исполнит связанные с ним инструкции. Обратите внимание, что байткод сам по себе не содержит питоновских объектов, ссылок на объекты и т.п.

Байткод дозволено попытаться осознать открыв файл интерпретатора CPython (ceval.c), но мы этого делать не будем. Вернее будем, но позднее. Теперь же пойдём простым путём и воспользуемся модулем dis из стандартной библиотеки.

Дизассемблируй это

Дизассемблирование – это перевод байтовой последовательности в кое-что больше внятное человеческому интеллекту. Для этой цели в питоне существует модуль dis, тот, что детально покажет вам всё, что спрятано. У модуля нет специального использования в продакшн-коде, итоги его работы необходимы только вам, не интерпретатору.

Выходит, давайте применим dis и снимем паранжу с нашего объектного модуля. Для этого воспользуемся функцией dis.dis:

>>> def foo(a):
...     x = 3
...     return x   a
...
>>> import dis
>>> dis.dis(foo.func_code)
  2           0 LOAD_CONST               1 (3)
              3 STORE_FAST               1 (x)

  3           6 LOAD_FAST                1 (x)
              9 LOAD_FAST                0 (a)
             12 BINARY_ADD
             13 RETURN_VALUE
Спрятанный текст

Нередко дозволено видеть записи вида dis.dis(foo), т.е. объект функции передаётся в дизассемблер напрямую. Это сделано для комфорта, под капотом dis всё равно находит и анализирует func_code. В нашем примере мы передаём объект кода очевидно для лучшего понимания процесса.

Числа в первой колонке – это номера строк анализируемых исходников. Вторая колонка отражает смещение команд в байткоде: LOAD_CONST находится в позиции «0», STORE_FAST в позиции «3» и т.д. Третья колонка даёт байтовым инструкциям человекопонятные наименования. Наименования эти необходимы тольконичтожным людишкам нам, в интерпретаторе они не применяются.

Две последние колонки содержат подробности об доводах для данной команды, если таковые имеются. Четвёртая колонка отражает позицию довода в признаке объектного модуля. В нашем примере довод инструкции LOAD_CONST находится на первой позиции признака-списка co_consts, довод STORE_FAST – на первой позиции co_varnames. Наконец, в пятой колонке dis отражает значение либо наименование соответствующей переменной. Удостоверимся в сказанном на практике:

>>> foo.func_code.co_consts[1]
3
>>> foo.func_code.co_varnames[1]
'x'

Это также поясняет, отчего инструкция STORE_FAST находится на третьей позиции в байткоде: если где-то в байткоде есть довод, следующие два байта будут представлять данный довод. Правильная обработка таких обстановок также ложится на плечи интерпретатора.

Хинт

Есливас внезапно поразило неимение доводов у BINARY_ADD – возьмите печеньку за наблюдательность, но не волнуйтесь прежде времени. Мы вернёмся к этому моменту чуть позднее, когда разговор пойдёт о самом интерпретаторе.

Как dis переводит байты (скажем, 100) в осмысленные имена (скажем, LOAD_CONST) и напротив? Подумайте, как бы вы сами организовали сходственную систему? Если у вас возникли мысли, как бы «ну, может там есть какой-то список с последовательным определением байтов» либо «по-любому словарь с наименованиями инструкций в качестве ключей и байтами как значениями», поздравляю – вы безусловно правы. Именно так всё и устроено. Сами определения происходят в файле opcode.py (дозволено также посмотреть заголовочный файл opcode.h), где вы сумеете увидеть ~полторы сотни сходственных строк:

def_op('LOAD_CONST', 100)       # Index in const list
def_op('BUILD_TUPLE', 102)      # Number of tuple items
def_op('BUILD_LIST', 103)       # Number of list items
def_op('BUILD_SET', 104)        # Number of set items

(Какой-то любитель комментариев бережно оставил нам пояснения к инструкциям.)

Сейчас мы имеем некоторое представление о том, чем является (и чем не является) байткод и как применятьdis для его обзора. В следующих частях мы разглядим, как питон может компилироваться в байткод, оставаясь при этом динамическим ЯП.

От переводчика

Крайне свободный перевод серии из трёх статей об устройстве питоновского интерпретатора. Автор занимается разработкой собственного велосипеда по этой теме и решил поделиться умениями, появившимися в процессе. Посмотрим, что у него из этого получилось.

Данная серия статей рассчитана на тех, кто может писать на python в целом, но нехорошо представляет как данный язык устроен изнутри. Собственно, как и я три месяца назад.

Маленький дисклеймер: свой рассказ я буду вести на примере интерпретатора python 2.7. Всё, о чем пойдёт речь дальше, дозволено повторить и на python 3.x с поправкой на некоторые отличия в синтаксисе и именование некоторых функций.

Выходит, начнём.

Часть I. Слушай Питон, а что у тебя внутри?

Начнём с немножко (на самом деле, с крепко) высокоуровневого взора на то, что же из себя представляет наша любимая змея. Что происходит, когда вы набираете строку сходственную этой в интерактивном интерпретаторе?

>>> a = "hello"

Ваш палец падает на enter и питон инициирует 4 следующих процесса: лексический обзорпарсинг,компиляцию и непринужденно интерпретацию. Лексический обзор – это процесс разбора набранной вами строки кода в определенную последовательность символов, называемых токенами. Дальше парсер на основе этих токенов генерирует конструкцию, которая отображает взаимоотношения между входящими в неё элементами (в данном случае, конструкция это абстрактное синтаксическое древо либо АСД). Дальше, применяя АСД, компилятор создаёт один либо несколько объектных модулей и передаёт их в интерпретатор для непосредственного выполнения.

Я не буду углубляться в темы лексического обзора, парсинга и компиляции, в основном потому, что сам не имею о них ни минимального представления. Взамен этого, давайте отменнее предположим, что мудрые люди сделали всё как нужно и данные этапы в питоновском интерпретаторе отрабатывают без ошибок. Представили? Двигаем дальше.

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

Выходит,

Объекты функций либо функции, как объекты

Если это не первая ваша статья о программировании на питоне, вы обязаны быть наслышаны о неких «объектах функций». Это именно о них люди с разумным видом рассуждают в контексте разговоров о «функциях, как объектах первого класса» и «наличии функций первого класса в питоне». Разглядим дальнейший пример:

>>> def foo(a):
...     x = 3
...     return x   a
...
>>> foo
<function foo at 0x107ef7aa0>

Выражение «функции – это объекты первого класса» обозначает, что функции – это объекты первого класса, в том смысле, в коем и списки – это объекты, и экземпляр класса MyObject – объект. И так как foo это объект, он имеет важность сам по себе, безотносительно вызова его, как функции (то есть, foo и foo() — это различные вещи). Мы можем передать foo в иную функцию в качестве довода, можем переназначить её на новое имя (other_function = foo). С функциями первого класса дозволено делать, что желательно и они всё стерпят.

Часть 2. Объектные модули

На данном этапе we need to go deeper, Дабы узнать, что объект функции в свою очередь содержит объект кода:

>>> def foo(a):
...     x = 3
...     return x   a
...
>>> foo
<function foo at 0x107ef7aa0>
>>> foo.func_code
<code object foo at 0x107eeccb0, file "<stdin>", line 1>

Как видно из приведённого листинга, объектный модуль является признаком объекта функции (у которого есть и уйма других признаков, но в данном случае специального интереса они не представляют в силу простотыfoo).

Объектный модуль генерируется питоновским компилятором и дальше передаётсся интерпретатору. Модуль содержит всю нужную для выполнения информацию. Давайте посмотрим на его признаки:

>>> dir(foo.func_code)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename',
'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals',
'co_stacksize', 'co_varnames']

Их, как видите, много, следственно все рассматривать не будем, для примера остановимся на трёх особенно внятных:

>>> foo.func_code.co_varnames
('a', 'x')
>>> foo.func_code.co_consts
(None, 3)
>>> foo.func_code.co_argcount
1

Признаки выглядят достаточно подсознательно:
co_varnames – имена переменных
co_consts – значения, о которых знает функция
co_argcount – число доводов, которые функция принимает

Всё это крайне познавательно, но выглядит несколько черезчур высокоуровнево для нашей темы, не правда ли? Где же инструкции интерпретатору для непосредственного выполнения нашего модуля? А такие инструкции есть и представлены они байткодом. Конечный также является атрибутом объектного модуля:

>>> foo.func_code.co_code 'd\x01\x00}\x01\x00|\x01\x00|\x00\x00\x17S'

Что за неизвестная байтовая фигня, спросите вы?

Часть III. Байткод

Вы вероятно и сами понимаете, но я, на каждый случай, озвучу – «байткод» и «объект кода» это различные вещи: 1-й является признаком второго, среди многих других (см. часть 2). Признак именуется co_code и содержит все нужные инструкции для выполнения интерпретатором.

Что же из себя представляет данный байткод? Как следует из наименования, это легко последовательность байтов. При итоге в консоль выглядит она довольно абсурдно, следственно давайте приведём её к числовой последовательности, пропустив через ord:

>>> [ord(b) for b in foo.func_code.co_code] [100, 1, 0, 125, 1, 0, 124, 1, 0, 124, 0, 0, 23, 83]

Таким образом мы получили числовое представление питоновского байткода. Интерпретатор пройдётся по всякому байту в последовательности и исполнит связанные с ним инструкции. Обратите внимание, что байткод сам по себе не содержит питоновских объектов, ссылок на объекты и т.п.

Байткод дозволено попытаться осознать открыв файл интерпретатора CPython (ceval.c), но мы этого делать не будем. Вернее будем, но позднее. Теперь же пойдём простым путём и воспользуемся модулем dis из стандартной библиотеки.

Дизассемблируй это

Дизассемблирование – это перевод байтовой последовательности в кое-что больше внятное человеческому интеллекту. Для этой цели в питоне существует модуль dis, тот, что детально покажет вам всё, что спрятано. У модуля нет специального использования в продакшн-коде, итоги его работы необходимы только вам, не интерпретатору.

Выходит, давайте применим dis и снимем паранжу с нашего объектного модуля. Для этого воспользуемся функцией dis.dis:

>>> def foo(a):
...     x = 3
...     return x   a
...
>>> import dis
>>> dis.dis(foo.func_code)
  2           0 LOAD_CONST               1 (3)
              3 STORE_FAST               1 (x)

  3           6 LOAD_FAST                1 (x)
              9 LOAD_FAST                0 (a)
             12 BINARY_ADD
             13 RETURN_VALUE
Спрятанный текст

Нередко дозволено видеть записи вида dis.dis(foo), т.е. объект функции передаётся в дизассемблер напрямую. Это сделано для комфорта, под капотом dis всё равно находит и анализирует func_code. В нашем примере мы передаём объект кода очевидно для лучшего понимания процесса.

Числа в первой колонке – это номера строк анализируемых исходников. Вторая колонка отражает смещение команд в байткоде: LOAD_CONST находится в позиции «0», STORE_FAST в позиции «3» и т.д. Третья колонка даёт байтовым инструкциям человекопонятные наименования. Наименования эти необходимы тольконичтожным людишкам нам, в интерпретаторе они не применяются.

Две последние колонки содержат подробности об доводах для данной команды, если таковые имеются. Четвёртая колонка отражает позицию довода в признаке объектного модуля. В нашем примере довод инструкции LOAD_CONST находится на первой позиции признака-списка co_consts, довод STORE_FAST – на первой позиции co_varnames. Наконец, в пятой колонке dis отражает значение либо наименование соответствующей переменной. Удостоверимся в сказанном на практике:

>>> foo.func_code.co_consts[1]
3
>>> foo.func_code.co_varnames[1]
'x'

Это также поясняет, отчего инструкция STORE_FAST находится на третьей позиции в байткоде: если где-то в байткоде есть довод, следующие два байта будут представлять данный довод. Правильная обработка таких обстановок также ложится на плечи интерпретатора.

Хинт

Есливас внезапно поразило неимение доводов у BINARY_ADD – возьмите печеньку за наблюдательность, но не волнуйтесь прежде времени. Мы вернёмся к этому моменту чуть позднее, когда разговор пойдёт о самом интерпретаторе.

Как dis переводит байты (скажем, 100) в осмысленные имена (скажем, LOAD_CONST) и напротив? Подумайте, как бы вы сами организовали сходственную систему? Если у вас возникли мысли, как бы «ну, может там есть какой-то список с последовательным определением байтов» либо «по-любому словарь с наименованиями инструкций в качестве ключей и байтами как значениями», поздравляю – вы безусловно правы. Именно так всё и устроено. Сами определения происходят в файле opcode.py (дозволено также посмотреть заголовочный файл opcode.h), где вы сумеете увидеть ~полторы сотни сходственных строк:

def_op('LOAD_CONST', 100)       # Index in const list
def_op('BUILD_TUPLE', 102)      # Number of tuple items
def_op('BUILD_LIST', 103)       # Number of list items
def_op('BUILD_SET', 104)        # Number of set items

(Какой-то любитель комментариев бережно оставил нам пояснения к инструкциям.)

Сейчас мы имеем некоторое представление о том, чем является (и чем не является) байткод и как применятьdis для его обзора. В следующих частях мы разглядим, как питон может компилироваться в байткод, оставаясь при этом динамическим ЯП.

 Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB