It is recommended to use the pyobject library, especially pyobject.objproxy, which can be installed via pip install pyobject.
pyobject.objproxy provides the ObjChain class, which can track every call and operation on any object added to an ObjChain and automatically generate "decompiled" code based on them.
Example usage:
from pyobject import ObjChain
chain = ObjChain(export_attrs=["__array_struct__"])
try:
np = chain.new_object("import numpy as np","np")
plt = chain.new_object("import matplotlib.pyplot as plt","plt",
export_funcs = ["show"])
# wrapped fake numpy and matplotlib
arr = np.array(range(1,11))
arr_squared = arr ** 2
mean = np.mean(arr)
std_dev = np.std(arr)
print(mean, std_dev)
plt.plot(arr, arr_squared)
plt.show()
finally:
# output generated code
print(f"Code:\n{chain.get_code()}\n")
print(f"Optimized:\n{chain.get_optimized_code()}")
Additionally, if you intend to wrap an object for other usage rather than decompiling, you can refer to the implementation of objproxy/__init__.py and modify it.
Note that I'm the developer of pyobject.