Skip to content

Instantly share code, notes, and snippets.

@dutc
Last active August 29, 2024 20:06
Show Gist options
  • Save dutc/8a4f6d6c436e9ab484d6 to your computer and use it in GitHub Desktop.
Save dutc/8a4f6d6c436e9ab484d6 to your computer and use it in GitHub Desktop.
implicit self
#!/usr/bin/env python
from ctypes import c_void_p, c_char_p, c_long, c_int, c_size_t, py_object, \
Structure, POINTER, cdll
from opcode import HAVE_ARGUMENT, opmap
from itertools import imap, chain
from copy import deepcopy
from sysconfig import get_config_vars
from sys import version_info
assert version_info.major == 2 and version_info.minor == 7, \
'this code presumes certain C struct layouts; works only with Python 2.7'
PYLIB_PATH = 'libpython2.7.so' # path to python dyn lib
PyCell = cdll.LoadLibrary(PYLIB_PATH).PyCell_New
PyCell.restype, PyCell.argtypes = py_object, [py_object]
class PyMemberDef(Structure):
_fields_ = [('name', c_char_p),
('type', c_int),
('offset', c_size_t),
('flags', c_int),
('doc', c_char_p),]
class PyTypeObject(Structure):
_fields_ = ([('_ob_next', py_object),
('_ob_prev', py_object),]
if get_config_vars().get('Py_DEBUG', False) else []) + \
[('ob_refcnt', c_size_t),
('ob_type', py_object),
('ob_size', c_size_t),
('tp_name', c_char_p),
('tp_basicsize', c_size_t),
('tp_itemsize', c_size_t),
('tp_dealloc', c_void_p),
('tp_print', c_void_p),
('tp_getattr', c_void_p),
('tp_setattr', c_void_p),
('tp_compare', c_void_p),
('tp_repr', c_void_p),
('tp_as_number', c_void_p),
('tp_as_sequence', c_void_p),
('tp_as_mapping', c_void_p),
('tp_hash', c_void_p),
('tp_call', c_void_p),
('tp_str', c_void_p),
('tp_getattro', c_void_p),
('tp_setattro', c_void_p),
('tp_as_buffer', c_void_p),
('tp_flags', c_long),
('tp_doc', c_char_p),
('tp_traverse', c_void_p),
('tp_clear', c_void_p),
('tp_richcompare', c_void_p),
('tp_weaklistoffset', c_size_t),
('tp_iter', c_void_p),
('tp_iternext', c_void_p),
('tp_methods', c_void_p),
('tp_members', POINTER(PyMemberDef)),
('tp_getset', c_void_p),
('tp_base', c_void_p),
('tp_dict', py_object),
('tp_descr_get', c_void_p),
('tp_descr_set', c_void_p),
('tp_dictoffset', c_size_t),
('tp_init', c_void_p),
('tp_alloc', c_void_p),
('tp_new', c_void_p),
('tp_free', c_void_p),
('tp_is_gc', c_void_p),
('tp_bases', py_object),
('tp_mro', py_object),
('tp_cache', py_object),
('tp_subclasses', py_object),
('tp_weaklist', py_object),
('tp_del', c_void_p),
('tp_version_tag', c_int),]
def f(): pass
func_closure = PyTypeObject.from_address(id(type(f))).tp_members[0]
__closure__ = PyTypeObject.from_address(id(type(f))).tp_members[1]
co_flags = PyTypeObject.from_address(id(type(f.func_code))).tp_members[3]
co_code = PyTypeObject.from_address(id(type(f.func_code))).tp_members[4]
co_freevars = PyTypeObject.from_address(id(type(f.func_code))).tp_members[8]
func_closure.flags &= ~0x1 # unset READONLY flag
__closure__.flags &= ~0x1
co_flags.flags &= ~0x1
co_code.flags &= ~0x1
co_freevars.flags &= ~0x1
def opcodes(ops):
ops = imap(ord,ops)
while True:
op = next(ops)
if op < HAVE_ARGUMENT:
yield op, None
else:
# ignores extended args
oparg = next(ops) + next(ops)*256
yield op, oparg
def instancemethod(f):
@apply
class wrapper(object):
def __get__(_, self, cls, f=f):
func_code = f.func_code
# see if __self__ or __cls__ are referenced but unbound
# (i.e., default to global)
global_self = next((i for i,name in enumerate(func_code.co_names)
if name == '__self__'), None)
global_cls = next((i for i,name in enumerate(func_code.co_names)
if name == '__cls__'), None)
if global_self is None and global_cls is None:
return f
closure = (f.__closure__ or ()) + (PyCell(self), PyCell(cls),)
freevars = func_code.co_freevars + ('__self__', '__cls__',)
load_self = (opmap['LOAD_DEREF'], len(closure)-2, 0)
load_cls = (opmap['LOAD_DEREF'], len(closure)-1, 0)
code = ''.join(imap(chr,chain.from_iterable(
load_self if op is opmap['LOAD_GLOBAL']
and arg == global_self
else load_cls if op is opmap['LOAD_GLOBAL']
and arg == global_cls
else (op, arg%256, arg//256) if arg is not None
else (op,)
for i,(op,arg) in enumerate(opcodes(func_code.co_code)))))
f = deepcopy(f)
f.func_closure = closure
f.func_code.co_freevars = freevars
f.func_code.co_code = code
f.func_code.co_flags &= ~0x40 # unset CO_NOFREE (no freevars) flag
return f
return wrapper
### example of use: ###
# silence PyFlakes NameError
__self__, __cls__ = None, None
class Foo(object):
@instancemethod
def __init__(x):
__self__.x = x
@instancemethod
def bar(y):
return __self__.x * y
class Baz(object):
@instancemethod
def __init__(x):
__self__.x = x
def bar(z):
@instancemethod
def bar(y):
return __self__.x + y + z
return bar
bar = bar(30)
if __name__ == '__main__':
foo, baz = Foo(10), Baz(10)
assert foo.bar(20) == 10*20
assert baz.bar(20) == 10+20+30
print 'all done!'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment