
On 03/30/2015 04:54 PM, Andrew Barnert wrote:
[snip]
And the reason for bringing this idea up here was I think it could be used to implement the lite macro behaviour that was suggested with a bit of added syntax.
On the other hand it appears to me, that Python is going in the direction of making it easier to compile to C code. More dynamic features may not be helpful in the long run.
I don't think this is true. There definitely isn't any momentum in that direction--shedskin and its two competitors are dead, while PyPy and numba are alive and kicking ass. And I don't think it's desirable, or that the core developers think it's desirable.
Good to know.
BTW... here is a first try. Works barely. Don't use the decorators inside a function yet. I'm sure there's quite a few byte code changes that need to be done to make this dependable, but it does show it's possible and is something interesting to play with. ;-)
This is closely related to continuations, but rather than try to capture a frame and then continue the code, it just applies code objects to a name space. That in it self ins't new, but getting the parts from decorated functions is a nice way to do it.
import inspect
def fix_code(c): """ Fix code object.
This attempts to make a code object from a function be more like what you would get if you gave exec a string.
* There is more that needs to be done to make this work dependably. But it's a start.
""" varnames = c.co_varnames names = c.co_names
xchg = [(124, 0x65), #LOAD_FAST to LOAD_NAME (125, 0x5a)] #STORE_FAST to STORE_NAME
bcode = [] bgen = iter(c.co_code) for b in bgen: for bx1, bx2 in xchg: if b == bx1: i1 = next(bgen) i2 = next(bgen) index = i1 + i2 * 256 if b in [124, 125]: b = bx2 char = varnames[index] names = names + (char,) index = names.index(char) i2 = index // 256 i1 = index - i2 bcode.append(b) bcode.append(i1) bcode.append(i2) break else: bcode.append(b) co_code = bytes(bcode)
Code = type(c) co = Code( 0, #co_argcount, 0, #co_kwonlyargcount, 0, #co_nlocals, c.co_stacksize, 64, #co_flags, co_code, c.co_consts, names, #co_names (), #co_varnames, c.co_filename, c.co_name, c.co_firstlineno, c.co_lnotab ) return co
class fn_signature: """ Hold a signature from a function.
When called with argumetns it will bind them and return a mapping. """ def __init__(self, fn): self.sig = inspect.signature(fn)
def __call__(self, *args, **kwds): return dict(self.sig.bind(*args, **kwds).arguments)
class fn_code: """ Create a relocatable code object that can be applied to a name space. """ def __init__(self, fn): self.co = fix_code(fn.__code__)
def __call__(self, ns): return eval(self.co, ns)
def get_sig(fn): """ Decorator to get a signature. """ return fn_signature(fn)
def get_code(fn): """ Decorator to get a code object. """ return fn_code(fn)
# Example 1 # Applying code to a namespace created by a signature.
@get_sig def foo(x): pass
@get_code def inc_x(): x += 1
@get_code def dec_x(): x -= 1
@get_code def get_x(): return x
ns = foo(3) # creates a namespace
inc_x(ns) # Apply code to namespace inc_x(ns) print(get_x(ns)) # --> 5
dec_x(ns) dec_x(ns) print(get_x(ns)) # --> 3
# Example 2 # Code + signature <---> function
def add_xy(x, y): return x + y
sig = get_sig(add_xy) co = get_code(add_xy) print(co(sig(3, 5))) # --> 8