93 votes

Comment dois-je comprendre la sortie de dis.dis ?

J'aimerais comprendre comment utiliser dis (le dissembleur du bytecode Python) . Plus précisément, comment doit-on interpréter la sortie de dis.dis (ou dis.disassemble ) ?

.

Voici un exemple très précis (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

Je vois que JUMP_IF_TRUE_OR_POP etc. sont des instructions en bytecode _(bien que, de manière intéressante, BUILD_SET n'apparaît pas dans cette liste, bien que je pense qu'il fonctionne comme BUILD_TUPLE )_ . Je pense que les nombres à droite sont des allocations de mémoire, et que les nombres à gauche sont goto les chiffres... Je remarque qu'ils presque incrémenter par 3 à chaque fois (mais pas tout à fait).

Si j'emballe dis.dis("heapq.nsmallest(d,3)") à l'intérieur d'une fonction :

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

112voto

Gareth Rees Points 31350

Vous essayez de désassembler une chaîne de caractères contenant du code source, mais cela n'est pas pris en charge par le logiciel dis.dis dans Python 2. Avec un argument de type chaîne, elle traite la chaîne comme si elle contenait du code d'octet (voir la fonction disassemble_string en dis.py ). Vous obtenez donc un résultat absurde basé sur une mauvaise interprétation du code source en tant que code d'octet.

Les choses sont différentes dans Python 3, où dis.dis compile un argument de type chaîne de caractères avant de le démonter :

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, vous devez compiler vous-même le code avant de le passer à la fonction 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 signifient les chiffres ? Le nombre 1 à l'extrême gauche est le numéro de ligne dans le code source à partir duquel ce code d'octet a été compilé. Les nombres dans la colonne de gauche sont le décalage de l'instruction dans le bytecode, et les nombres sur la droite sont les numéros d'ordre de l'instruction. opargs . Regardons le code d'octet réel :

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

Au décalage 0 dans le code d'octet nous trouvons 65 l'opcode pour LOAD_NAME avec l'oparg 0000 ; ensuite (à l'offset 3) 6a est l'opcode LOAD_ATTR avec 0100 l'oparg, et ainsi de suite. Notez que les opargs sont dans l'ordre little-endian, de sorte que 0100 est le numéro 1. Les sans-papiers opcode Le module contient des tableaux opname vous donnant le nom de chaque opcode, et opmap vous donnant l'opcode pour chaque nom :

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

La signification de l'oparg dépend de l'opcode, et pour connaître l'histoire complète, vous devez lire l'implémentation de la machine virtuelle CPython. sur ceval.c . Para LOAD_NAME y LOAD_ATTR l'oparg est un index dans le co_names de l'objet code :

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

Pour LOAD_CONST il s'agit d'un index dans le co_consts de l'objet code :

>>> co.co_consts
(3,)

Pour CALL_FUNCTION il s'agit du nombre d'arguments à passer à la fonction, codé sur 16 bits avec le nombre d'arguments ordinaires dans l'octet de poids faible, et le nombre d'arguments de mots-clés dans l'octet de poids fort.

90voto

Delgan Points 4395

<em>Je republie ma réponse à <a href="https://stackoverflow.com/questions/3299648/python-compilation-interpretation-process">une autre question </a>afin d'être sûr de le trouver lors d'une recherche sur Google. <code>dis.dis()</code> .</em>


Pour compléter le grand La réponse de Gareth Rees Voici un petit résumé colonne par colonne pour expliquer la sortie du bytecode désassemblé.

Par exemple, étant donné cette fonction :

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

Ceci peut être désassemblé 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          |   |

Chaque colonne a un but précis :

  1. L'indice correspondant numéro de ligne dans le code source
  2. Il est possible d'indiquer le instruction en cours exécuté (lorsque le bytecode provient d'une objet du cadre par exemple)
  3. Une étiquette qui indique une possibilité JUMP d'une instruction antérieure à celui-ci
  4. En adresse dans le bytecode qui correspond à l'index des octets (ce sont des multiples de 2 car Python 3.6 utilise 2 octets pour chaque instruction, alors que cela pouvait varier dans les versions précédentes)
  5. Le nom de l'instruction (également appelé nom de l'opération ), chacune d'entre elles est brièvement expliquée dans le site dis module et leur mise en œuvre se trouvent dans ceval.c (la boucle centrale de CPython)
  6. En argument (le cas échéant) de l'instruction qui est utilisée en interne par Python pour récupérer certaines constantes ou variables, gérer la pile, sauter à une instruction spécifique, etc.
  7. En interprétation adaptée aux humains de l'argument d'instruction

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X