Last active
December 18, 2015 03:29
-
-
Save remram44/5718325 to your computer and use it in GitHub Desktop.
Implicit self
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 dis | |
import struct | |
import types | |
class instancemethod(object): | |
def __init__(self, func): | |
code = func.func_code | |
bytecode = code.co_code | |
# weird stuff here | |
def make_self_funcs(): | |
container = [None] | |
def getter(): | |
return container[0] | |
def setter(self): | |
container[0] = self | |
return getter, setter | |
getter, self.setter = make_self_funcs() | |
# witchcraft here | |
def blah(): | |
return getter | |
cell = blah.func_closure[0] | |
# Rewrite bytecode | |
try: | |
global_number = func.func_code.co_names.index('self') # the global | |
except ValueError: | |
pass # Function does not use 'self' | |
else: | |
if func.func_closure is not None: | |
cell_number = len(func.func_closure) # the cell to use instead | |
else: | |
cell_number = 0 | |
# LOAD_GLOBAL <global_number> 74 xx xx | |
# -> | |
# LOAD_DEREF <cell_number> 88 xx xx | |
# CALL_FUNCTION 0 83 00 00 | |
# numbers are little endian | |
print "replacing..." | |
dis.dis(bytecode) | |
bytecode = bytecode.replace( | |
struct.pack('<BH', 0x74, global_number), | |
struct.pack('<BHBH', 0x88, cell_number, 0x83, 0)) | |
print "with..." | |
dis.dis(bytecode) | |
# Create new function object | |
freevars = code.co_freevars + ('self',) | |
new_code = types.CodeType( | |
code.co_argcount, | |
code.co_nlocals, | |
code.co_stacksize, | |
code.co_flags | 0x10, | |
bytecode, | |
code.co_consts, | |
code.co_names, | |
code.co_varnames, | |
code.co_filename, | |
code.co_name, | |
code.co_firstlineno, | |
code.co_lnotab, | |
freevars, | |
code.co_cellvars) | |
if func.func_closure is None: | |
closure = (cell,) | |
else: | |
closure = func.func_closure + (cell,) | |
self.new_func = types.FunctionType( | |
new_code, | |
func.func_globals, | |
func.func_name, | |
func.func_defaults, | |
closure) | |
def __get__(self, instance, owner): | |
self.setter(instance) | |
return self.new_func | |
class A(object): | |
@instancemethod | |
def foo(): | |
print A, self | |
A().foo() |
Also, note a very subtle error in the original that the above fixes. This is also more in line with how PyMethod_New
works.
# using the original definition of instancemethod
class A(object):
@instancemethod
def foo():
return self
a, b = A(), A()
a_foo, b_foo = a.foo, b.foo
assert a_foo() is a and b_foo() is b # AssertionError!
I didn't test extensively, I was just wondering whether it could be done by only manipulating bytecode and not actual interpreter structures. Thinking about it, another problem with the original is that it's not reentrant (recursive calls would alter 'self' on the caller).
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Interesting approach!
I think if you're willing to just construct a FunctionType yourself, then you don't need to fake the mutable closure with your cell + CALL_FUNCTION. I think you can then remove some black magic & make things a bit more concise.