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:
- El número de línea correspondiente en el código fuente
- Opcionalmente indica la instrucción actual ejecutada (cuando el bytecode proviene de un objeto de marco, por ejemplo)
- Una etiqueta que denota un posible
JUMP
de una instrucción anterior a esta - 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)
- 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 enceval.c
(el ciclo central de CPython) - 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.
- La interpretación humana del argumento de la instrucción