Problem with exec

Michael Spencer mahs at telcopartners.com
Fri Dec 16 15:13:41 EST 2005


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)








More information about the Python-list mailing list