user-supplied locals dict for function execution?

Michael Spencer mahs at telcopartners.com
Mon Mar 20 20:27:08 CET 2006


Lonnie Princehouse wrote:
>> What's your use case exactly ?
> 
> I'm trying to use a function to implicitly update a dictionary.  The
> whole point is to avoid the normal dictionary semantics, so kw['x'] = 5
> unfortunately won't do.
> 
> I think bytecode hacks may be the way to go
> 
I once messed around with something like that:

#----------- def_hacks.py

"""Silly operations on bytecode.  Just for fun"""

from dis import HAVE_ARGUMENT, opmap
from types import CodeType as code

def gendis(co):
     """Yield atomic operations from bytecode"""
     coiter = iter(co)
     for char in coiter:
         op = ord(char)
         if op >= HAVE_ARGUMENT:
             yield [op, ord(coiter.next()), ord(coiter.next())]
         else:
             yield [op]

def thunk(func):
     """Function decorator -> code object.
     Hacks func.func_code so that it uses a real local dictionary rather than
     optimized locals.  Returns a code object that can be exec'd in a
     context.  This amounts to `exec function_body_source in context`, but
     does not requre accesses to the source"""

     out = []
     c= func.func_code
     codestring = c.co_code

     # These may not be all the necessary hacks!
     replace_map = {
         opmap["STORE_FAST"]: opmap["STORE_NAME"],
         opmap["LOAD_FAST"]: opmap["LOAD_NAME"],
         opmap["DELETE_FAST"]: opmap["DELETE_NAME"],
     }

     names_list = list(c.co_names)

     # optimized locals are indexed in co_varnames
     # non-locals are indexed in co_names
     # so when we switch operations, we have to change the
     # index variables too

     name_map = dict((ix, names_list.index(name))
         for ix, name in enumerate(c.co_varnames)
         if name in names_list)

     for atom in gendis(codestring):
         opcode = atom[0]
         if opcode in replace_map:
             atom[0] = replace_map[opcode]
             varindex = atom[1] + 256 * atom[2]
             atom[1:] = reversed(divmod(name_map[varindex], 256))
         out.append("".join(chr(byte) for byte in atom))

     codestring = "".join(out)
     # Make a new code object, using most of the properties of the original
     # but with new codestring, no arguments, and with flags adjusted
     return code(0, #c.co_argcount
             c.co_nlocals, c.co_stacksize, c.co_flags-3,
             codestring, c.co_consts, c.co_names, c.co_varnames,
             c.co_filename, c.co_name, c.co_firstlineno, c.co_lnotab,
             c.co_freevars, c.co_cellvars)


@thunk
def simple_test():
     a = 4
     b = 2
     return c*a+b

Then usage is:

 >>> d = {"__builtins__":None, "c":10}
 >>> eval(simple_test, d)
42
 >>> d
{'__builtins__': None, 'a': 4, 'c': 10, 'b': 2}
 >>>

Use at your peril!

Michael







More information about the Python-list mailing list