Здравствуйте! Допустим, есть такой код: x = 10 нужно получить байткод, который ему соответствует. Попробуем его скомпилировать: from dis import dis dis('x=10') Получим: 1 0 LOAD_CONST 0 (10) 2 STORE_NAME 0 (x) 4 LOAD_CONST 1 (None) 6 RETURN_VALUE Или, так (что почти то же самое): from dis import Bytecode bc = Bytecode('x=10') list(bc.codeobj.co_code) [100, 0, 90, 0, 100, 1, 83, 0] В последнем байткоде потеряна информация о константах, при том, что есть LOAD_CONST (100). Попробуем скомпилировать файл с помощью compileall: >> python -m compileall ./ Listing './'... Compiling '.et.py'... Получим: 00 00 00 00 00 00 00 00 00 01 00 00 00 40 00 00 00 73 08 00 00 00 64 00 5A 00 64 01 53 00 29 02 E9 0A 00 00 00 4E 29 01 DA 01 78 A9 00 72 03 00 00 00 72 03 00 00 00 FA 08 2E 2F 73 65 74 2E 70 79 DA 08 3C 6D 6F 64 75 6C 65 3E 01 00 00 00 73 00 00 00 00 Найти в этом коде предыдущий мне не удалось. Как это можно объяснить? Где вообще можно почитать про то, как устроен .pyc? Хочу написать свою маленькую ВМ для Python.
Ответ pyc-файл состоит из Четырёхбайтового магического номера Четырёхбайтовой метки времени Четырёх байт хранящих размер исходного файла Сериализованного объекта кода Магический номер - это два байта уникальных для каждой версии интерпретатора и два байта 0d0a. Байты 0d0a - это символ возврата каретки и перевода строки, защищающие файл от повреждения в случае редактирования в текстовом режиме. Метка времени хранит время последнего изменения исходника, по которому сгенерирован pyc-файл. С размером, уверен, всё очевидно и каких-либо уточнений не требует. Остальная часть файла - это результат выполнения marshal.dump на объекте кода, который в свою очередь является результатом компиляции исходника. "Дизассемблировать" pyc-файл можно этим скриптом: import dis, marshal, struct, sys, time, types from codecs import encode def show_file(fname): with open(fname, 'rb') as fh: magic = fh.read(4) moddate = fh.read(4) filesz = fh.read(4) modtime = time.asctime(time.localtime(struct.unpack('=L', moddate)[0])) filesz = struct.unpack('=L', filesz) print('magic {!s}'.format(encode(magic, 'hex').decode())) print('moddate {!s} ({!s})'.format(encode(moddate, 'hex').decode(), modtime)) print('file size {:d}'.format(*filesz)) code = marshal.load(fh) show_code(code) def show_code(code, indent=''): print('{!s}code'.format(indent)) indent += ' ' print('{!s}argcount {:d}'.format(indent, code.co_argcount)) print('{!s}nlocals {:d}'.format(indent, code.co_nlocals)) print('{!s}stacksize {:d}'.format(indent, code.co_stacksize)) print('{!s}flags {:0>4x}'.format(indent, code.co_flags)) show_hex('code', code.co_code, indent=indent) dis.disassemble(code) print('{!s}consts'.format(indent)) for const in code.co_consts: if type(const) == types.CodeType: show_code(const, indent + ' ') else: print(' {!s}{!r}'.format(indent, const)) print('{!s}names {!r}'.format(indent, code.co_names)) print('{!s}varnames {!r}'.format(indent, code.co_varnames)) print('{!s}freevars {!r}'.format(indent, code.co_freevars)) print('{!s}cellvars {!r}'.format(indent, code.co_cellvars)) print('{!s}filename {!r}'.format(indent, code.co_filename)) print('{!s}name {!r}'.format(indent, code.co_name)) print('{!s}firstlineno {:d}'.format(indent, code.co_firstlineno)) show_hex('lnotab', code.co_lnotab, indent=indent) def show_hex(label, h, indent): h = encode(h, 'hex').decode() if len(h) < 60: print('{!s}{!s} {!s}'.format(indent, label, h)) else: print ('{!s}{!s}'.format(indent, label)) for i in range(0, len(h), 60): print('{!s} {!s}'.format(indent, h[i:i+60])) show_file(sys.argv[1]) Запуск его на pyc-файле этого скрипта a, b = 1, 0 if a or b: print('Hello', a) даст такой вывод magic 330d0d0a moddate 1094a85a (Wed Mar 14 11:16:32 2018) files sz 48 code argcount 0 nlocals 0 stacksize 3 flags 0040 code 64045c025a005a01650073106501721a6502640265008302010064035300 1 0 LOAD_CONST 4 ((1, 0)) 2 UNPACK_SEQUENCE 2 4 STORE_NAME 0 (a) 6 STORE_NAME 1 (b) 2 8 LOAD_NAME 0 (a) 10 POP_JUMP_IF_TRUE 16 12 LOAD_NAME 1 (b) 14 POP_JUMP_IF_FALSE 26 3 >> 16 LOAD_NAME 2 (print) 18 LOAD_CONST 2 ('Hello') 20 LOAD_NAME 0 (a) 22 CALL_FUNCTION 2 24 POP_TOP >> 26 LOAD_CONST 3 (None) 28 RETURN_VALUE consts 1 0 'Hello' None (1, 0) names ('a', 'b', 'print') varnames () freevars () cellvars () filename 'D:\\playground\\python\\test.py' name '' firstlineno 1 lnotab 08010801 Ответ написан на основе этой статьи.