Aktualizacja: Z różnych powodów zacząłem pisać asembler w wersji Cross-Python. Zobacz https://github.com/rocky/python-xasm Jest to nadal bardzo wczesna wersja beta.
O ile mi wiadomo, nie ma obecnie obecnie utrzymywanego asemblera Python. PEAK's Bytecode Disassembler został opracowany dla Pythona 2.6, a później zmodyfikowany w celu obsługi wczesnego Pythona 2.7.
To całkiem fajne z documentation. Ale opiera się na innych bibliotekach PEAK, które mogą być problematyczne.
Przeanalizuję cały przykład, aby poczuć, co musisz zrobić. Nie jest ładna, ale należy się tego spodziewać.
Zasadniczo po modyfikacji kodu bajtowego należy utworzyć nowy obiekt types.CodeType
. Potrzebujesz nowego, ponieważ wiele obiektów w typie kodu, z dobrego powodu, nie możesz zmienić. Na przykład interpreter może mieć niektóre z tych wartości w pamięci podręcznej.
Po utworzeniu kodu można go użyć w funkcjach wykorzystujących kod, który może być używany w exec
lub eval
.
Lub możesz zapisać to do pliku kodu bajtowego. Niestety format kodu zmienił się pomiędzy Python 2 i Python 3. A tak na marginesie, tak samo jest z optymalizacją i bajtodami. W rzeczywistości, w Pythonie 3.6 będą one słowo kody nie bytecodes.
Więc tutaj jest to, co trzeba było zrobić dla przykładu:
a = """
def fact():
a = 8
a = 0
return a
"""
c = compile(a, '<string>', 'exec')
fn_code = c.co_consts[0] # Pick up the function code from the main code
from dis import dis
dis(fn_code)
print("=" * 30)
x = fn_code.co_code[6:16] # modify bytecode
import types
opt_fn_code = types.CodeType(fn_code.co_argcount,
# c.co_kwonlyargcount, Add this in Python3
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
x, # fn_code.co_code: this you changed
fn_code.co_consts,
fn_code.co_names,
fn_code.co_varnames,
fn_code.co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab, # In general, You should adjust this
fn_code.co_freevars,
fn_code.co_cellvars)
dis(opt_fn_code)
print("=" * 30)
print("Result is", eval(opt_fn_code))
# Now let's change the value of what's returned
co_consts = list(opt_fn_code.co_consts)
co_consts[-1] = 10
opt_fn_code = types.CodeType(fn_code.co_argcount,
# c.co_kwonlyargcount, Add this in Python3
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
x, # fn_code.co_code: this you changed
tuple(co_consts), # this is now changed too
fn_code.co_names,
fn_code.co_varnames,
fn_code.co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab, # In general, You should adjust this
fn_code.co_freevars,
fn_code.co_cellvars)
dis(opt_fn_code)
print("=" * 30)
print("Result is now", eval(opt_fn_code))
Kiedy wpadłem to tutaj jest to, co mam:
3 0 LOAD_CONST 1 (8)
3 STORE_FAST 0 (a)
4 6 LOAD_CONST 2 (0)
9 STORE_FAST 0 (a)
5 12 LOAD_FAST 0 (a)
15 RETURN_VALUE
==============================
3 0 LOAD_CONST 2 (0)
3 STORE_FAST 0 (a)
4 6 LOAD_FAST 0 (a)
9 RETURN_VALUE
==============================
('Result is', 0)
3 0 LOAD_CONST 2 (10)
3 STORE_FAST 0 (a)
4 6 LOAD_FAST 0 (a)
9 RETURN_VALUE
==============================
('Result is now', 10)
Zauważ, że nie mają numery linii zmieniono, mimo że usunąłem w kodzie kilka linii. To dlatego, że nie zaktualizowałem fn_code.co_lnotab
.
Jeśli chcesz teraz zapisać z tego plik kodu bajtowego Python. Oto, co można zrobić:
co_consts = list(c.co_consts)
co_consts[0] = opt_fn_code
c1 = types.CodeType(c.co_argcount,
# c.co_kwonlyargcount, Add this in Python3
c.co_nlocals,
c.co_stacksize,
c.co_flags,
c.co_code,
tuple(co_consts),
c.co_names,
c.co_varnames,
c.co_filename,
c.co_name,
c.co_firstlineno,
c.co_lnotab, # In general, You should adjust this
c.co_freevars,
c.co_cellvars)
from struct import pack
with open('/tmp/testing.pyc', 'w') as fp:
fp.write(pack('Hcc', 62211, '\r', '\n')) # Python 2.7 magic number
import time
fp.write(pack('I', int(time.time())))
# In Python 3 you need to write out the size mod 2**32 here
import marshal
fp.write(marshal.dumps(c1))
Aby uprościć pisanie kodu bajtowego boilerplate powyżej Dodałem rutynowych xdis zwanego write_python_file().
teraz, aby sprawdzić wyniki:
$ uncompyle6 /tmp/testing.pyc
# uncompyle6 version 2.9.2
# Python bytecode 2.7 (62211)
# Disassembled from: Python 2.7.12 (default, Jul 26 2016, 22:53:31)
# [GCC 5.4.0 20160609]
# Embedded file name: <string>
# Compiled at: 2016-10-18 05:52:13
def fact():
a = 0
# okay decompiling /tmp/testing.pyc
$ pydisasm /tmp/testing.pyc
# pydisasm version 3.1.0
# Python bytecode 2.7 (62211) disassembled from Python 2.7
# Timestamp in code: 2016-10-18 05:52:13
# Method Name: <module>
# Filename: <string>
# Argument count: 0
# Number of locals: 0
# Stack size: 1
# Flags: 0x00000040 (NOFREE)
# Constants:
# 0: <code object fact at 0x7f815843e4b0, file "<string>", line 2>
# 1: None
# Names:
# 0: fact
2 0 LOAD_CONST 0 (<code object fact at 0x7f815843e4b0, file "<string>", line 2>)
3 MAKE_FUNCTION 0
6 STORE_NAME 0 (fact)
9 LOAD_CONST 1 (None)
12 RETURN_VALUE
# Method Name: fact
# Filename: <string>
# Argument count: 0
# Number of locals: 1
# Stack size: 1
# Flags: 0x00000043 (NOFREE | NEWLOCALS | OPTIMIZED)
# Constants:
# 0: None
# 1: 8
# 2: 10
# Local variables:
# 0: a
3 0 LOAD_CONST 2 (10)
3 STORE_FAST 0 (a)
4 6 LOAD_CONST 0 (None)
9 RETURN_VALUE
$
Alternatywnym podejściem do optymalizacji jest optymalizacja na poziomie Abstract Syntax Tree (AST). Jednak nie jestem pewien, czy możesz wykonać to bezpośrednio. Niektóre procedury modułów sugerują, że możesz. Lub w jaki sposób wygenerować plik kodu bajtowego z AST. Więc przypuszczam, że wypiszesz to jako źródło Pythona.
Jestem ciekawy, dlaczego zrobiłbyś to, jaki jest twój przypadek użycia? – shuttle87
Python bytecode jest uważany za szczegół implementacji i może zmieniać się z wersji na wersję i tłumacza na interpreter. Nie ma dokumentacji oprócz źródła tłumacza. Czy na pewno chcesz to zrobić? – Antimony
@ shuttle87, piszę silnik gry ze skryptami, które mam nadzieję napisać w Pythonie, ale chcę uruchomić pewne optymalizacje kodu skryptu, który luźno mówiąc nie będzie znany z góry (chociaż podstawowa struktura będzie być znane, ponieważ wszystkie mają wspólną klasę podstawową). Mam wszystkie inne komponenty działające, właśnie ten konwertujący kod bajtowy do użytecznej funkcji jest ostatnią przeszkodą. –