Created
January 28, 2015 20:16
-
-
Save earwig/28a64ffb94d51a608e3d to your computer and use it in GitHub Desktop.
Python object replacement demonstration, from https://benkurtovic.com/2015/01/28/python-object-replacement.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ctypes | |
from ctypes import pythonapi as api | |
import sys | |
from types import (BuiltinFunctionType, GetSetDescriptorType, FrameType, | |
MemberDescriptorType, MethodType) | |
import guppy | |
from guppy.heapy import Path | |
hp = guppy.hpy() | |
def _w(x): | |
def f(): | |
x | |
return f | |
CellType = type(_w(0).func_closure[0]) | |
del _w | |
# ----------------------------------------------------------------------------- | |
def _write_struct_attr(addr, value, add_offset): | |
ptr_size = ctypes.sizeof(ctypes.py_object) | |
ptrs_in_struct = (3 if hasattr(sys, "getobjects") else 1) + add_offset | |
offset = ptrs_in_struct * ptr_size + ctypes.sizeof(ctypes.c_ssize_t) | |
ref = ctypes.byref(ctypes.py_object(value)) | |
ctypes.memmove(addr + offset, ref, ptr_size) | |
def _replace_attribute(source, rel, new): | |
if isinstance(source, (MethodType, BuiltinFunctionType)): | |
if rel == "__self__": | |
# Note: PyMethodObject->im_self and PyCFunctionObject->m_self | |
# have the same offset | |
_write_struct_attr(id(source), new, 1) | |
return | |
if rel == "im_self": | |
return # Updated via __self__ | |
if isinstance(source, type): | |
if rel == "__base__": | |
return # Updated via __bases__ | |
if rel == "__mro__": | |
return # Updated via __bases__ when important, otherwise futile | |
if isinstance(source, (GetSetDescriptorType, MemberDescriptorType)): | |
if rel == "__objclass__": | |
_write_struct_attr(id(source), new, 0) | |
return | |
try: | |
setattr(source, rel, new) | |
except TypeError as exc: | |
print "Unknown R_ATTRIBUTE (read-only):", rel, type(source) | |
def _replace_indexval(source, rel, new): | |
if isinstance(source, tuple): | |
temp = list(source) | |
temp[rel] = new | |
replace(source, tuple(temp)) | |
return | |
source[rel] = new | |
def _replace_indexkey(source, rel, new): | |
source[new] = source.pop(source.keys()[rel]) | |
def _replace_interattr(source, rel, new): | |
if isinstance(source, CellType): | |
api.PyCell_Set(ctypes.py_object(source), ctypes.py_object(new)) | |
return | |
if rel == "ob_type": | |
source.__class__ = new | |
return | |
print "Unknown R_INTERATTR:", rel, type(source) | |
def _replace_local_var(source, rel, new): | |
source.f_locals[rel] = new | |
api.PyFrame_LocalsToFast(ctypes.py_object(source), ctypes.c_int(0)) | |
_RELATIONS = { | |
Path.R_ATTRIBUTE: _replace_attribute, | |
Path.R_INDEXVAL: _replace_indexval, | |
Path.R_INDEXKEY: _replace_indexkey, | |
Path.R_INTERATTR: _replace_interattr, | |
Path.R_LOCAL_VAR: _replace_local_var | |
} | |
def _path_key_func(path): | |
reltype = type(path.path[1]).__bases__[0] | |
return 1 if reltype is Path.R_ATTRIBUTE else 0 | |
def replace(old, new): | |
for path in sorted(hp.iso(old).pathsin, key=_path_key_func): | |
relation = path.path[1] | |
try: | |
func = _RELATIONS[type(relation).__bases__[0]] | |
except KeyError: | |
print "Unknown relation:", relation, type(path.src.theone) | |
continue | |
func(path.src.theone, relation.r, new) | |
# ----------------------------------------------------------------------------- | |
class A(object): | |
def func(self): | |
return self | |
class B(object): | |
pass | |
class X(object): | |
pass | |
def sure(obj): | |
def inner(): | |
return obj | |
return inner | |
def gen(obj): | |
while True: | |
yield obj | |
class S(object): | |
__slots__ = ("p", "q") | |
class T(object): | |
__slots__ = ("p", "q") | |
class U(object): | |
pass | |
class V(object): | |
pass | |
class W(U): | |
pass | |
# ----------------------------------------------------------------------------- | |
a = A() | |
b = B() | |
X.cattr = a | |
x = X() | |
x.iattr = a | |
d = {a: a} | |
L = [a] | |
t = (a,) | |
f = a.func | |
meth = a.__sizeof__ | |
clo = sure(a) | |
g = gen(a) | |
s = S() | |
s.p = a | |
u = U() | |
ud = U.__dict__["__dict__"] | |
s.q = S | |
sd = S.q | |
# ----------------------------------------------------------------------------- | |
def examine_vars(id1, id2, id3): | |
ex = lambda v, id_: str(v) + ("" if id(v) == id_ else " - ERROR!") | |
print "dict (local var): ", ex(a, id1) | |
print "dict (class attr): ", ex(X.cattr, id1) | |
print "dict (inst attr): ", ex(x.iattr, id1) | |
print "dict (key): ", ex(d.keys()[0], id1) | |
print "dict (value): ", ex(d.values()[0], id1) | |
print "list: ", ex(L[0], id1) | |
print "tuple: ", ex(t[0], id1) | |
print "method (instance): ", ex(f(), id1) | |
print "method (builtin): ", ex(meth.__self__, id1) | |
print "closure: ", ex(clo(), id1) | |
print "frame (generator): ", ex(next(g), id1) | |
print "slots: ", ex(s.p, id1) | |
print "class (instance): ", ex(type(u), id2) | |
print "class (subclass): ", ex(W.__bases__[0], id2) | |
print "class (g/s descr): ", ex(ud.__get__(u, U) or type(u), id2) | |
print "class (mem descr): ", ex(sd.__get__(s, S), id3) | |
if __name__ == "__main__": | |
examine_vars(id(a), id(U), id(S)) | |
print "-" * 35 | |
replace(a, b) | |
replace(U, V) | |
replace(S, T) | |
print "-" * 35 | |
examine_vars(id(b), id(V), id(T)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment