Шаг 181.
Python: сборник рецептов.
Метапрограммирование. Дизассемблирование байт-кода Python

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

Задача

    Вы хотите узнать в подробностях, как работает ваш код "под капотом", путем дизассемблирования его в низкоуровневый байт-код с помощью интерпретатора.

Решение

    Модуль dis можно использовать для вывода результата дизассемблирования любой функции Python. Например:

>>> def countdown(n):
	while n > 0:
		print('T-minus', n)
		n -= 1
	print('Blastoff!')

	
>>> import dis
>>> dis.dis(countdown)
  2     >>    0 LOAD_FAST                0 (n)
              2 LOAD_CONST               1 (0)
              4 COMPARE_OP               4 (>)
              6 POP_JUMP_IF_FALSE       28

  3           8 LOAD_GLOBAL              0 (print)
             10 LOAD_CONST               2 ('T-minus')
             12 LOAD_FAST                0 (n)
             14 CALL_FUNCTION            2
             16 POP_TOP

  4          18 LOAD_FAST                0 (n)
             20 LOAD_CONST               3 (1)
             22 INPLACE_SUBTRACT
             24 STORE_FAST               0 (n)
             26 JUMP_ABSOLUTE            0

  5     >>   28 LOAD_GLOBAL              0 (print)
             30 LOAD_CONST               4 ('Blastoff!')
             32 CALL_FUNCTION            1
             34 POP_TOP
             36 LOAD_CONST               0 (None)
             38 RETURN_VALUE
>>> 


Обсуждение

    Модуль dis может быть полезен, если вам когда-либо потребуется изучить, что происходит в ваших программах на очень низком уровне (например, если вы пытаетесь разобраться в характеристиках производительности).

    Сырой байт-код, интепретируемый функцией dis(), доступен в функциях:

>>> countdown.__code__.co_code
b'|\x00d\x01k\x04r\x1ct\x00d\x02|\x00\x83\x02\x01\x00|\x00d\x038\x00}
\x00q\x00t\x00d\x04\x83\x01\x01\x00d\x00S\x00'

    Если вы захотите интерпретировать этот код самостоятельно, вам понадобятся некоторые из констант, определенных в модуле opcode. Например:

>>> c = countdown.__code__.co_code
>>> import opcode
>>> opcode.opname[c[0]]
'LOAD_FAST'
>>> opcode.opname[c[3]]
'POP_TOP'
>>> opcode.opname[c[2]]
'LOAD_CONST'
>>> 

    Иронично, но в модуле dis нет функции, которая облегчила бы вам программную обработку байт-кода. Однако эта функция-генератор примет сырую последовательность байт-кода и превратит ее в опкоды (коды операций) и аргументы:

>>> import opcode
>>> def generate_opcodes(codebytes):
	extended_arg = 0
	i = 0
	n = len(codebytes)
	while i < n:
		op = codebytes[i]
		i += 1
		if op >= opcode.HAVE_ARGUMENT:
			oparg = codebytes[i] + codebytes[i+1]*256 + extended_arg
			extended_arg = 0
			i += 2
			if op == opcode.EXTENDED_ARG:
				extended_arg = oparg * 65536
				continue
		else:
			oparg = None
	yield (op, oparg)

>>> 

    Чтобы использовать эту функцию, напишите такой код:

>>> for op, oparg in generate_opcodes(countdown.__code__.co_code):
	print(op, opcode.opname[op], oparg)

	
124 LOAD_FAST 25600
1 POP_TOP None
107 COMPARE_OP 29188
28 INPLACE_FLOOR_DIVIDE None
116 LOAD_GLOBAL 25600
2 ROT_TWO None
124 LOAD_FAST 33536
2 ROT_TWO None
1 POP_TOP None
0 <0> None
124 LOAD_FAST 25600
3 ROT_THREE None
56 INPLACE_SUBTRACT None
0 <0> None
125 STORE_FAST 28928
0 <0> None
116 LOAD_GLOBAL 25600
4 DUP_TOP None
131 CALL_FUNCTION 257
0 <0> None
100 LOAD_CONST 21248
0 <0> None
>>> 

    Это малоизвестный факт, но вы можете заменить сырой байт-код любой функции, какой пожелаете. Это требует некоторых усилий, но вот пример того, как это работает:

>>> def add(x, y):
	return x + y

>>> c = add.__code__
>>> c
<code object add at 0x0000026EFA8759D0, file "<pyshell#30>", line 1>
>>> c.co_code
b'|\x00|\x01\x17\x00S\x00'
>>>
>>> # Создаем полностью новый объект кода с фейковым байт-кодом
>>> import types
>>> newbytecode = b'xxxxxxx'
>>> nc = types.CodeType(c.co_argcount, c.co_kwonlyargcount,
    c.co_nlocals, c.co_stacksize, c.co_flags, newbytecode, c.co_consts,
    c.co_names, c.co_varnames, c.co_filename, c.co_name,
    c.co_firstlineno, c.co_lnotab)
>>> nc
<code object add at 0x10069fe40, file "<stdin>", line 1>
>>> add.__code__ = nc
>>> add(2, 3)
Segmentation fault

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

    Со следующего раза мы будем рассматривать модули и пакеты.




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