Le module dis permet d'analyser le bytecode python en le désassemblant.
Pour rappel un bytecode est le code compilé par l'interpréteur python. C'est-à-dire à chaque fois que vous lancez un script python, l'interpréteur (généralement CPython) va généré le bytecode, avec les instructions comprises par le systeme. Depuis python 3.2 ces binaires sont maintenant dans un dossier __pycache__, toujours avec les extensions pyc.
Cela permet lors du prochain appel de charger plus rapidement le module.
Prenons comme exemple la fonction suivante, avec l'utilisation de dis
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 >>> def is_good(val): ... if val is None: ... return False ... return True ... >>> dis.dis(is_good(True)) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Users\FX29620\AppData\Local\Programs\Python\Python37\lib\dis.py", li e 77, in dis type(x).__name__) TypeError: don't know how to disassemble bool objects >>> dis.dis('is_good(True)') 1 0 LOAD_NAME 0 (is_good) 2 LOAD_CONST 0 (True) 4 CALL_FUNCTION 1 6 RETURN_VALUE
Les données affichées par colonnes correspondent aux informations suivantes,
- 1 => Le numéro de la ligne dans le code
- 2 => L'instruction courante executée
- 3 => Un label indiquant un possible JUMP d'une instruction précédente
- 4 => L'adresse dans le bytecode, cela sera toujours un multiple de 2 en python > 3.6 car chaque instruction prend 2 octets
- 5 => opname qui est l'instruction bytecode, on trouvera la liste sur la doc python
- 6 => Les arguments de l'instruction
- 7 => La partie de code python liée à l'instruction
Chaque instruction du bytecode est découpé en "Code Object" et écrit en C (pour CPython).
Pour visualiser ce code object il faut simplement ajouter __code__ à la fin d'une instruction python
Copier le code
1 2 3 4 5 6 >>> is_good.__code__ <code object is_good at 0x0000000001D2F9C0, file "<stdin>", line 1> # On peut aussi passé par le built-in compile qui retournera un Code Object >>> compile('is_good(True)', '<string>', 'exec') <code object <module> at 0x0000000002133F60, file "<string>", line 1>
Pour exécuter un Code Object il faut soit passer par un exec() soit par eval(), le 1er permet de gérer le code python sur plusieurs lignes tandis que eval() gère uniquement les instructions simples.
Copier le code
1 2 3 >>> obj = compile('is_good(True)', '<string>', 'exec') >>> print(exec(obj)) None
On peut également obtenir des informations sur le code compilé
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # On accède ensuite à des informations comme le bytecode généré en byte >>> compile('is_good(True)', '<string>', 'exec').co_code b'e\x00d\x00\x83\x01\x01\x00d\x01S\x00' # ... les constantes de la fonction >>> compile('is_good(True)', '<string>', 'exec').co_consts (True, None) # ... le nom de l'instruction python >>> compile('is_good(True)', '<string>', 'exec').co_names ('is_good',) # ... la liste éventuelles des symboles locales >>> compile('is_good(True)', '<string>', 'exec').co_varnames
Voilà les informations minimales à avoir pour faire de l'analyse de bytecode.
Si vous cherchez un moyen d'obtenir le code à partir d'un bytecode il existe aussi un décompileur facilement accessible
Copier le code
1 2 pipenv install uncompyle6 uncompyle6 file.pyc