Развитые и гибкие интеграционные возможности Python являются его основным преимуществом в качестве языка для интеграции приложений. Из лекции нетрудно заключить, что Python легко взаимодействует с другими системами.
Ссылки
Библиотека Boost Python для C++ http://www.boost.org
14. Лекция: Устройство интерпретатора языка Python.
В этой лекции сделана попытка пролить свет на внутреннее устройство интерпретатора Python. Для иллюстрации работы интерпретатора рассматриваются отладчик, профайлер и «дизассемблер».
Лексический анализ
Лексический анализатор языка программирования разбивает исходный текст программы (состоящий из одиночных символов) на лексемы — неделимые «слова» языка.
Основные категории лексем Python: идентификаторы и ключевые слова (NAME), литералы (STRING, NUMBER и т.п.), операции (OP), разделители, специальные лексемы для обозначения (изменения) отступов (INDENT, DEDENT) и концов строк (NEWLINE), а также комментарии (COMMENT). Лексический анализатор доступен через модуль tokenize, а определения кодов лексем содержатся в модуле token стандартной библиотеки Python. Следующий пример показывает лексический анализатор в действии:
Листинг
import StringIO, token, tokenize
prog_example = """
for i in range(100): # comment
if i % 1 == 0: \
print ":", t**2
«"".strip()
rl = StringIO.StringIO(prog_example).readline
for t_type, t_str, (br,bc), (er,ec), logl in tokenize.generate_tokens(rl):
print "%3i %10s : %20r» % (t_type, token.tok_name[t_type], t_str)
А вот что выведет эта программа, разбив на лексемы исходный код примера:
Листинг
Фактически получен поток лексем, который может использоваться для различных целей. Например, для синтаксического «окрашивания» кода на языке Python. Словарь token.tok_name позволяет получить мнемонические имена для типа лексемы по номеру.
Синтаксический анализ
Вторая стадия преобразования исходного текста программы в байт–код интерпретатора состоит в синтаксическом анализе исходного текста. Модуль parser содержит функции suite() и expr() для построения деревьев синтаксического разбора соответственно для кода программ и выражений Python. Модуль symbol содержит номера символов грамматики Python, словарь для получения названия символа из грамматики Python.
Следующая программа анализирует достаточно простой код Python (prg) и порождает дерево синтаксического разбора (AST–объект), который тут же можно превращать в кортеж и красиво выводить функцией pprint.pprint(). Далее определяется функция для превращения номеров символов в их мнемонические обозначения (имена) в грамматике:
Листинг
import pprint, token, parser, symbol
prg = ""«print 2*2»""
pprint.pprint(parser.suite(prg).totuple())
def pprint_ast(ast, level=0):
if type(ast) == type(()):
for a in ast:
pprint_ast(a, level+1)
elif type(ast) == type(""):
print repr(ast)
else:
print " "*level,
try:
print symbol.sym_name[ast]
except:
print «token.»+token.tok_name[ast],
print
pprint_ast(parser.suite(prg).totuple())
Эта программа выведет следующее (структура дерева отражена отступами):
Листинг
(257,
(264,
(265,
(266,
(269,
(1, 'print'),
(292,
(293,
(294,
(295,
(297,
(298,
(299,
(300,
(301,
(302,
(303, (304, (305, (2, '2')))),
(16, '*'),
(303, (304, (305, (2, '2')))))))))))))))),
(4, ''))),
(0, ''))
file_input
stmt
simple_stmt
small_stmt
print_stmt
token.NAME 'print'
test
and_test
not_test
comparison
expr
xor_expr
and_expr
shift_expr
arith_expr
term
factor
power
atom
token.NUMBER '2'
token.STAR '*'
factor
power
atom
token.NUMBER '2'
token.NEWLINE ''
token.ENDMARKER ''
Получение байт–кода
После того как получено дерево синтаксического разбора, компилятор должен превратить его в байт–код, подходящий для исполнения интерпретатором. В следующей программе проводятся отдельно синтаксический анализ, компиляция и выполнение (вычисление) кода (и выражения) в языке Python:
Листинг
import parser
prg = ""«print 2*2»""
ast = parser.suite(prg)
code = ast.compile('filename.py')
exec code
prg = ""«2*2»""
ast = parser.expr(prg)
code = ast.compile('filename1.py')
print eval(code)
Функция parser.suite() (или parser.expr()) возвращает AST–объект (дерево синтаксического анализа), которое методом compile() компилируется в Python байт–код и сохраняется в кодовом объекте code. Теперь этот код можно выполнить (или, в случае выражения — вычислить) с помощью оператора exec (или функции eval()).
Здесь необходимо заметить, что недавно в Python появился пакет compiler, который объединяет модули для работы анализа исходного кода на Python и генерации кода. В данной лекции он не рассматривается, но те, кто хочет глубже изучить эти процессы, может обратиться к документации по Python.
Изучение байт–кода
Для изучения байт–кода Python–программы можно использовать модуль dis (сокращение от «дизассемблер»), который содержит функции, позволяющие увидеть байт–код в мнемоническом виде. Следующий пример иллюстрирует эту возможность:
Листинг
>>> def f():
… print 2*2
…
>>> dis.dis(f)
2 0 LOAD_CONST 1 (2)
3 LOAD_CONST 1 (2)
6 BINARY_MULTIPLY
7 PRINT_ITEM
8 PRINT_NEWLINE
9 LOAD_CONST 0 (None)
12 RETURN_VALUE
Определяется функция f(), которая должна вычислить и напечатать значение выражения 2*2. Функция dis() модуля dis выводит код функции f() в виде некого «ассемблера», в котором байт–код Python представлен мнемоническими именами. Следует заметить, что при интерпретации используется стек, поэтому LOAD_CONST кладет значение на вершину стека, а BINARY_MULTIPLY берет со стека два значения и помещает на стек результат их перемножения. Функция без оператора return возвращает значение None. Как и в случае с кодами для микропроцессора, некоторые байт–коды принимают параметры.
Мнемонические имена можно увидеть в списке dis.opname (ниже печатаются только задействованные имена):
Листинг
>>> import dis
>>> [n for n in dis.opname if n[0] != "<"]
['STOP_CODE', 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'DUP_TOP', 'ROT_FOUR',
'NOP', 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT', 'UNARY_CONVERT',
'UNARY_INVERT', 'LIST_APPEND', 'BINARY_POWER', 'BINARY_MULTIPLY',
'BINARY_DIVIDE', 'BINARY_MODULO', 'BINARY_ADD', 'BINARY_SUBTRACT',