Interpretr CPython je v zásadě zásobníkový jazyk s postfixovou notací. Python přitom v rámci standardní knihovny poskytuje nástroj pro disasemblování pythonovských zdrojových kódů z bajtkódu tohoto interpretru do jejich mnemonické podoby (tedy vlastně příslušného asembleru) – modul dis.
Pokud vás zajímá přímo číselná podoba bajtkódu, poskytuje Python funkci compile(), která předložený zdrojový kód (ze souboru či textu, ale dokonce i v podobě AST-stromu) dokáže zkompilovat do výstupní binární podoby.
dis
i funkce compile()
je tedy pochopitelně relevantní jen a pouze pro vzorový interpretr CPython a žádný jiný!
Disasemblaci konkrétního modulu pomocí modulu dis můžeme snadno vyvolat z příkazové řádky:
Program (bez vnitřních skoků) se disasembluje do tří skupin sloupečků:
Jejich význam je následující:
Vlastní číselný bajtkód interpretru CPython zabírá jeden bajt na identifikátor příslušné instrukce a určený počet bajtů (v little-endian pořadí) na její argumenty (má-li nějaké). Příklad z předchozího slajdu vypadá číselně následovně:
<string>
(aneb „kód není načítán ze souboru“) a exec
(aneb „vykonej kód pomocí funkce exec()“).
Podle offsetů vidíme, že příslušné řádky symbolického bajtkódu odpovídají následujícím hodnotám:
offset | hodnoty |
---|---|
0 | b'd\x00\x00' |
3 | b'g\x01\x00' |
6 | b'Z\x00\x00' |
9 | b'd\x01\x00' |
12 | b'S' |
Korespondence k symbolickému zápisu v tomto případě je následující:
instrukce | argumenty | význam |
a) vršek zásobníku (TOS)
b) jiná operace |
|
---|---|---|---|---|
symbolicky | číselně | |||
LOAD_CONST |
'd'=64h=10010 |
00 00 |
uloží konstantu z uvedeného indexu (0) tabulky konstant co_consts na zásobník | 0: 1 |
BUILD_LIST |
'g'=67h=10310 |
01 00 |
načte ze zásobníku uvedený počet prvků (1) a vyrobí z nich seznam, který uloží na zásobník | 0: [1] |
STORE_NAME |
'Z'=5Ah=9010 |
00 00 |
uloží do proměnné, jejíž název získá na příslušné pozici (0) v tabulce jmen co_names, poslední prvek zásobníku | xs = [1] |
LOAD_CONST |
'd'=64h=10010 |
01 00 |
uloží konstantu z uvedeného indexu (1) tabulky konstant co_consts na zásobník | 0: None |
RETURN_VALUE |
'S'=53h=8310 |
vrátí poslední prvek zásobníku | — |
Přičemž příslušné tabulky obsahují následující hodnoty:
tabulka | obsah |
---|---|
co_consts | (1, None) |
co_names | ('xs',) |
Program s vnitřními skoky obsahuje navíc mezi prvním a druhým sloupečkem ještě označení >>
pro ty řádky, které jsou cílem operací skoku:
Mezi prvním a druhým sloupečkem se ještě můžete potkat se značkou -->
. Ta označuje řádek, na kterém došlo k chybě při provádění programu:
i
na pravé straně přiřazovacího příkazu neexistuje, program tudíž na tomto řádku zhavaruje.
Nejčastěji užívanými metodami modulu dis budou asi funkce dis(), která přebírá jeden parametr – objekt k disasemblování (tím může být například třída, metoda, funkce nebo třeba i zdrojový kód jako řetězec), a funkce show_code(), která vypisuje podrobnější informace k disasemblovanému objektu:
dis.show_code()
je vlastně pouze zkratka pro print( dis.code_info() )
.
Stejně snadno však můžete kód k prozkoumání vnutit funkci dis() přímo jako řetězec:
Pro extrémně jednoduché kousky kódu (vlastně pouze výrazy) můžete ještě použít trik za pomoci lambda:
Modul dis je pochopitelně hodně nízkoúrovňová záležitost a jako takový může být většině lidí a většinu času ukradený. Ale jsou chvíle, kdy se vám může hodit, například:
Krásnou ukázkou je dekorátor od Raymonda Hettingera, který (mimo jiné) nahrazuje odkazy na globální hodnoty známé v čase kompilace přímo za tyto hodnoty plus vyměňuje složitější datové konstrukce konstant za konstantu jednu. Výsledkem je pochopitelně podstatně rychlejší kód, protože ušetříte několikanásobná dohledávání objektů podle jména v řetězci objektů jejich nahrazením přímým kódem.