Роман Сузи - Язык программирования 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)
А вот что выведет эта программа, разбив на лексемы исходный код примера:
prog_example:
1 NAME : 'for'
1 NAME : 'i'
1 NAME : 'in'
1 NAME : 'range'
50 OP : '('
2 NUMBER : '100'
50 OP : ')'
50 OP : ':'
52 COMMENT : '# comment'
4 NEWLINE : 'n'
5 INDENT : ' '
1 NAME : 'if'
1 NAME : 'i'
50 OP : '%'
2 NUMBER : '1'
50 OP : '=='
2 NUMBER : '0'
50 OP : ':'
1 NAME : 'print'
3 STRING : '":"'
50 OP : ','
1 NAME : 't'
50 OP : '**'
2 NUMBER : '2'
6 DEDENT : ''
0 ENDMARKER : ''
Фактически получен поток лексем, который может использоваться для различных целей. Например, для синтаксического «окрашивания» кода на языке 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],
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',
'BINARY_SUBSCR', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE',
'INPLACE_FLOOR_DIVIDE', 'INPLACE_TRUE_DIVIDE', 'SLICE+0', 'SLICE+1',
'SLICE+2', 'SLICE+3', 'STORE_SLICE+0', 'STORE_SLICE+1', 'STORE_SLICE+2',
'STORE_SLICE+3', 'DELETE_SLICE+0', 'DELETE_SLICE+1', 'DELETE_SLICE+2',
'DELETE_SLICE+3', 'INPLACE_ADD', 'INPLACE_SUBTRACT', 'INPLACE_MULTIPLY',
'INPLACE_DIVIDE', 'INPLACE_MODULO', 'STORE_SUBSCR', 'DELETE_SUBSCR',
'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR', 'BINARY_OR',
'INPLACE_POWER', 'GET_ITER', 'PRINT_EXPR', 'PRINT_ITEM', 'PRINT_NEWLINE',
'PRINT_ITEM_TO', 'PRINT_NEWLINE_TO', 'INPLACE_LSHIFT', 'INPLACE_RSHIFT',
'INPLACE_AND', 'INPLACE_XOR', 'INPLACE_OR', 'BREAK_LOOP', 'LOAD_LOCALS',
'RETURN_VALUE', 'IMPORT_STAR', 'EXEC_STMT', 'YIELD_VALUE', 'POP_BLOCK',
'END_FINALLY', 'BUILD_CLASS', 'STORE_NAME', 'DELETE_NAME',
'UNPACK_SEQUENCE', 'FOR_ITER', 'STORE_ATTR', 'DELETE_ATTR', 'STORE_GLOBAL',
'DELETE_GLOBAL', 'DUP_TOPX', 'LOAD_CONST', 'LOAD_NAME', 'BUILD_TUPLE',
'BUILD_LIST', 'BUILD_MAP', 'LOAD_ATTR', 'COMPARE_OP', 'IMPORT_NAME',
'IMPORT_FROM', 'JUMP_FORWARD', 'JUMP_IF_FALSE', 'JUMP_IF_TRUE',
'JUMP_ABSOLUTE', 'LOAD_GLOBAL', 'CONTINUE_LOOP', 'SETUP_LOOP',
'SETUP_EXCEPT', 'SETUP_FINALLY', 'LOAD_FAST', 'STORE_FAST', 'DELETE_FAST',
'RAISE_VARARGS', 'CALL_FUNCTION', 'MAKE_FUNCTION', 'BUILD_SLICE',
'MAKE_CLOSURE', 'LOAD_CLOSURE', 'LOAD_DEREF', 'STORE_DEREF',
'CALL_FUNCTION_VAR', 'CALL_FUNCTION_KW', 'CALL_FUNCTION_VAR_KW',