Что означают нули в байт-коде функции функции python?

Я пытаюсь научиться тому, как работает байт-код python, поэтому я могу кое-что сделать с помощью кода функций (просто для удовольствия, а не для реального использования), поэтому я начал с некоторых простых примеров, таких как:

def f(x): return x + 3/x 

Байт-код:

 (124, 0, 0, 100, 1, 0, 124, 0, 0, 20, 23, 83) 

Поэтому для меня имеет смысл, что 124 является байт-кодом LOAD_FAST , а имя загружаемого объекта: f.__code__.co_varnames[0] где 0 – число после 124 . И 100 указывает LOAD_CONST для загрузки f.__code__.co_consts[1] где 1 – это число после 100 . Но тогда есть куча вспомогательных нулей, таких как второй и третий и пятый нули, которые, кажется, не имеют никакой цели, по крайней мере для меня. Что они указывают?

Текстовый байт-код:

 >>> dis.dis(f) 2 0 LOAD_FAST 0 (x) 3 LOAD_CONST 1 (3) 6 LOAD_FAST 0 (x) 9 BINARY_DIVIDE 10 BINARY_ADD 11 RETURN_VALUE 

Большое количество байт-кодов принимает аргументы (любой байт-код с dis.HAVE_ARGUMENT в или над dis.HAVE_ARGUMENT . Те, у которых есть 2-байтовый аргумент, в порядке little-endian.

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

С 2 байтами вы можете дать любому байткоду значение аргумента между 0 и 65535, для байт-кодов, чем нужно больше, вы можете префикс байт-кода с байт-кодом EXTENDED_ARG , добавив еще 2 байта для значения от 0 до 4294967295. Теоретически вы можете использовать EXTENDED_ARG несколько раз, но интерпретатор CPython использует int для переменной oparg и, таким образом, для практических целей ограничивается 4-байтовыми значениями.

Начиная с Python 3.4, модуль dis предоставляет вам экземпляры Instruction которые упрощают интроспекцию каждого байт-кода и их аргументов. Используя это, мы можем пройти через байт-коды, которые вы нашли для своей функции f :

 >>> def f(x): ... return x + 3/x ... >>> f.__code__.co_varnames ('x',) >>> f.__code__.co_consts (None, 3) >>> import dis >>> instructions = dis.get_instructions(f) >>> instructions <generator object _get_instructions_bytes at 0x10be77048> >>> instruction = next(instructions) >>> instruction Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='x', argrepr='x', offset=0, starts_line=2, is_jump_target=False) 

Таким образом, первый код операции 124 или LOAD_FAST помещает значение для первого локального имени в стек; это аргумент 0 0 , little-endian интерпретируется как целое число 0 , индекс в массив локалей кода. dis заполнил атрибут argval , показывая нам, что первое локальное имя – x . В предыдущем сеансе я показываю, как вы можете подвергнуть объект кода просмотру списка имен.

 >>> instruction = next(instructions) >>> instruction Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=3, argrepr='3', offset=3, starts_line=None, is_jump_target=False) 

Следующая инструкция толкает константу в стек; аргумент теперь равен 1 0 или мало-endian для целого числа 1 ; вторая константа, связанная с объектом кода. Код f.__code__.co_consts tuple показывает, что он равен 3 , но объект Instruction дает его как атрибут argval .

 >>> next(instructions) Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='x', argrepr='x', offset=6, starts_line=None, is_jump_target=False) 

Затем у нас есть еще один LOAD_FAST , нажав другую ссылку на локальное имя x в стек.

 >>> next(instructions) Instruction(opname='BINARY_TRUE_DIVIDE', opcode=27, arg=None, argval=None, argrepr='', offset=9, starts_line=None, is_jump_target=False) 

Это байт-код без аргумента , код операции 27 находится ниже dis.HAVE_ARGUMENT . Аргумент не требуется, поскольку этот код операции принимает два верхних значения в стеке, делит их, возвращая результат с плавающей запятой обратно в стек. Таким образом, последние x и 3 константы берутся, делятся и результат возвращается.

 >>> next(instructions) Instruction(opname='BINARY_ADD', opcode=23, arg=None, argval=None, argrepr='', offset=10, starts_line=None, is_jump_target=False) 

Другой байт-код без аргументов; это добавляет два верхних значения стека, заменяя их результатом. Исход BINARY_TRUE_DIVIDE взят и значение x которое было нажато первым, и результат возвращается в стек.

 >>> next(instructions) Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=11, starts_line=None, is_jump_target=False) 

Последняя инструкция, а другая – аргументы. RETURN_VALUE завершает текущий кадр, возвращая верхнее значение из стека в качестве результата для вызывающего.

Байт-коды Python принимают 2-байтовые аргументы. Дополнительные нули – это высокие байты аргументов.