python python-2.7

python - ¿Cómo debería entender la salida de dis.dis?



python-2.7 (2)

Me gustaría entender cómo usar dis (el desensamblador de bytecode 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 bytecode (aunque, BUILD_SET , 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 de la izquierda son números goto ... Noté 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 <44> # what is <44> ? 10 DELETE_SLICE+1 11 STORE_SLICE+1


Está intentando desensamblar 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 código de bytes (vea la función dis.py en dis.py ). Por lo tanto, está viendo resultados sin sentido basados ​​en una interpretación errónea del código fuente como código de bytes.

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

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 necesita compilar 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)'', ''<none>'', ''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 desde el que se compiló este código de bytes. Los números en la columna de la izquierda son el desplazamiento de la instrucción dentro del bytecode, y los números a la derecha son los opargs . Veamos el código de bytes real:

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

En el desplazamiento 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 da el nombre para cada código de operación, y opmap que le da el código de operación para 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, codificado en 16 bits con el número de argumentos ordinarios en el byte bajo y el número de argumentos de palabra clave en el byte alto.


Estoy volviendo a enviar mi respuesta a otra pregunta , para estar seguro de encontrarla mientras hace Google dis.dis() .

Para completar la gran respuesta de Gareth Rees , aquí hay un pequeño resumen de columna por columna para explicar la salida del bytecode 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 bytecode 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 (esos 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 opname ), cada uno se explica brevemente en el módulo dis y su implementación se puede encontrar en ceval.c (el ciclo central de CPython)
  6. El argumento (si existe) de la instrucción que Python usa internamente para obtener algunas constantes o variables, administrar la pila, saltar a una instrucción específica, etc.
  7. La interpretación humana del argumento de la instrucción