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