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