Peter Otten wrote: > > If you could provide a function with a different namespace when it's called, > e. g > > f() in namespace > > would look up its globals in namespace, that might be an interesting concept > but it's not how Python works. > > Peter > It does seem like an interesting concept, so I took it as an opportunity to waste some time ;-)
The 'thunk' function, defined below, turns a function into code that can be exec'd in another environment: First, the example: @thunk def simple_assigns(): a = 3 b = 4 def square(p): return p * p # test in a clean environment: >>> d = dict(__builtins__ = None) >>> exec simple_assigns in d >>> d {'__builtins__': None, 'a': 3, 'b': 4, 'square': <function square at 0x017A09B0>} >>> d["square"](4) 16 Another example: we can use a thunk to define classes - who needs a class statement? def maketype(func): """Treat the body of func as a class suite""" cls = type(func.func_name, (), dict(__builtins__ = None)) c = thunk(func) exec c in globals(), dwrapper(cls) return cls @maketype def ClassA(): def __init__(self): self.init = True >>> a = ClassA() >>> a <def_hacks.ClassA object at 0x019AE810> >>> a.init True >>> Michael Anyway, here's the function: #----------- def_hacks.py """Silly operations on bytecode""" 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): """Hack bytecode so that it uses a real local dictionary rather than optimized locals""" out = [] c= func.func_code codestring = c.co_code 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) -- http://mail.python.org/mailman/listinfo/python-list