¿Cómo debo entender la salida de dis.dis?

Me gustaría entender cómo usar dis (el desemejante del código de bytes de Python) . Específicamente, ¿cómo se debe interpretar la salida de dis.dis (o dis.disassemble )?

.

Aquí hay un ejemplo muy específico (en Python 2.7.3):

 dis.dis("heapq.nsmallest(d,3)") 0 BUILD_SET 24933 3 JUMP_IF_TRUE_OR_POP 11889 6 JUMP_FORWARD 28019 (to 28028) 9 STORE_GLOBAL 27756 (27756) 12 LOAD_NAME 29811 (29811) 15 STORE_SLICE+0 16 LOAD_CONST 13100 (13100) 19 STORE_SLICE+1 

Veo que JUMP_IF_TRUE_OR_POP etc. son instrucciones de JUMP_IF_TRUE_OR_POP (aunque, de manera interesante, BUILD_SET no aparece en esta lista, aunque espero que funcione como BUILD_TUPLE ) . Creo que los números en el lado derecho son asignaciones de memoria, y los números en la izquierda son números goto … Me doy cuenta de que casi aumentan en 3 cada vez (pero no del todo).

Si dis.dis("heapq.nsmallest(d,3)") dentro de una función:

 def f_heapq_nsmallest(d,n): return heapq.nsmallest(d,n) dis.dis("f_heapq(d,3)") 0 BUILD_TUPLE 26719 3 LOAD_NAME 28769 (28769) 6 JUMP_ABSOLUTE 25640 9  # what is  ? 10 DELETE_SLICE+1 11 STORE_SLICE+1 

Está intentando desmontar una cadena que contiene código fuente, pero eso no es compatible con dis.dis en Python 2. Con un argumento de cadena, trata la cadena como si contuviera un código de bytes (vea la función dis.py en dis.py ). Por lo tanto, está viendo una salida sin sentido basada en una mala interpretación del código fuente como un código de bytes.

Las cosas son diferentes en Python 3, donde dis.dis comstack un argumento de cadena antes de desensamblarlo:

 Python 3.2.3 (default, Aug 13 2012, 22:28:10) >>> import dis >>> dis.dis('heapq.nlargest(d,3)') 1 0 LOAD_NAME 0 (heapq) 3 LOAD_ATTR 1 (nlargest) 6 LOAD_NAME 2 (d) 9 LOAD_CONST 0 (3) 12 CALL_FUNCTION 2 15 RETURN_VALUE 

En Python 2, debe comstackr el código usted mismo antes de pasarlo a dis.dis :

 Python 2.7.3 (default, Aug 13 2012, 18:25:43) >>> import dis >>> dis.dis(compile('heapq.nlargest(d,3)', '', 'eval')) 1 0 LOAD_NAME 0 (heapq) 3 LOAD_ATTR 1 (nlargest) 6 LOAD_NAME 2 (d) 9 LOAD_CONST 0 (3) 12 CALL_FUNCTION 2 15 RETURN_VALUE 

que significan los números? El número 1 en el extremo izquierdo es el número de línea en el código fuente a partir del cual se compiló este código de byte. Los números en la columna de la izquierda son el desplazamiento de la instrucción dentro del bytecode, y los números de la derecha son los opargs . Veamos el código de byte real:

 >>> co = compile('heapq.nlargest(d,3)', '', 'eval') >>> co.co_code.encode('hex') '6500006a010065020064000083020053' 

En el offset 0 en el código de bytes encontramos 65 , el código de operación para LOAD_NAME , con el oparg 0000 ; luego (en el desplazamiento 3) 6a es el código de operación LOAD_ATTR , con 0100 el oparg, y así sucesivamente. Tenga en cuenta que los opargs están en orden little-endian, por lo que 0100 es el número 1. El módulo de opcode no documentado contiene tablas opname que le dan el nombre de cada opcode y opmap que le da el opcode de cada nombre:

 >>> opcode.opname[0x65] 'LOAD_NAME' 

El significado del oparg depende del código de operación, y para la historia completa necesita leer la implementación de la máquina virtual CPython en ceval.c . Para LOAD_NAME y LOAD_ATTR el oparg es un índice en la propiedad co_names del objeto de código:

 >>> co.co_names ('heapq', 'nlargest', 'd') 

Para LOAD_CONST es un índice en la propiedad co_consts del objeto de código:

 >>> co.co_consts (3,) 

Para CALL_FUNCTION , es el número de argumentos para pasar a la función, codificados en 16 bits con el número de argumentos ordinarios en el byte bajo, y el número de argumentos de palabras clave en el byte alto.

Estoy transfiriendo mi respuesta a otra pregunta , para asegurarme de encontrarla mientras dis.dis() Google dis.dis() .


Para completar la gran respuesta de Gareth Rees , aquí hay solo un pequeño resumen columna por columna para explicar la salida del código de bytes desensamblado.

Por ejemplo, dada esta función:

 def f(num): if num == 42: return True return False 

Esto puede ser desmontado en (Python 3.6):

 (1)|(2)|(3)|(4)| (5) |(6)| (7) ---|---|---|---|----------------------|---|------- 2| | | 0|LOAD_FAST | 0|(num) |-->| | 2|LOAD_CONST | 1|(42) | | | 4|COMPARE_OP | 2|(==) | | | 6|POP_JUMP_IF_FALSE | 12| | | | | | | 3| | | 8|LOAD_CONST | 2|(True) | | | 10|RETURN_VALUE | | | | | | | | 4| |>> | 12|LOAD_CONST | 3|(False) | | | 14|RETURN_VALUE | | 

Cada columna tiene un propósito específico:

  1. El número de línea correspondiente en el código fuente.
  2. Opcionalmente, indica la instrucción actual ejecutada (cuando el código de bytes proviene de un objeto de marco, por ejemplo)
  3. Una etiqueta que denota un posible JUMP de una instrucción anterior a esta.
  4. La dirección en el bytecode que corresponde al índice de bytes (son múltiplos de 2 porque Python 3.6 usa 2 bytes para cada instrucción, mientras que podría variar en versiones anteriores)
  5. El nombre de la instrucción (también llamado nombre de operación ), cada uno se explica brevemente en el módulo dis y su implementación se puede encontrar en ceval.c (el bucle central de CPython)
  6. El argumento (si existe) de la instrucción que Python usa internamente para obtener algunas constantes o variables, administrar la stack, saltar a una instrucción específica, etc.
  7. La interpretación amigable del argumento de la instrucción.