# The list of symbols that are included by default in the generated # function's environment SAFE_SYMBOLS = ["list", "dict", "tuple", "set", "long", "float", "object", "bool", "callable", "True", "False", "dir", "frozenset", "getattr", "hasattr", "abs", "cmp", "complex", "divmod", "id", "pow", "round", "slice", "vars", "hash", "hex", "int", "isinstance", "issubclass", "len", "map", "filter", "max", "min", "oct", "chr", "ord", "range", "reduce", "repr", "str", "type", "zip", "xrange", "None", "Exception", "KeyboardInterrupt"] # Also add the standard exceptions __bi = __builtins__ if type(__bi) is not dict: __bi = __bi.__dict__ for k in __bi: if k.endswith("Error") or k.endswith("Warning"): SAFE_SYMBOLS.append(k) del __bi def createFunction(sourceCode, args="", additional_symbols=dict()): """ Create a python function from the given source code \param sourceCode A python string containing the core of the function. Might include the return statement (or not), definition of local functions, classes, etc. Indentation matters ! \param args The string representing the arguments to put in the function's prototype, such as "a, b", or "a=12, b", or "a=12, b=dict(akey=42, another=5)" \param additional_symbols A dictionary variable name => variable/funcion/object to include in the generated function's closure The sourceCode will be executed in a restricted environment, containing only the python builtins that are harmless (such as map, hasattr, etc.). To allow the function to access other modules or functions or objects, use the additional_symbols parameter. For example, to allow the source code to access the re and sys modules, as well as a global function F named afunction in the sourceCode and an object OoO named ooo in the sourceCode, specify: additional_symbols = dict(re=re, sys=sys, afunction=F, ooo=OoO) \return A python function implementing the source code. It can be recursive: the (internal) name of the function being defined is: __TheFunction__. Its docstring is the initial sourceCode string. Tests show that the resulting function does not have any calling time overhead (-3% to +3%, probably due to system preemption aleas) compared to normal python function calls. """ # Include the sourcecode as the code of a function __TheFunction__: s = "def __TheFunction__(%s):\n" % args s += "\t" + "\n\t".join(sourceCode.split('\n')) + "\n" # Byte-compilation (optional) byteCode = compile(s, "", 'exec') # Setup the local and global dictionaries of the execution # environment for __TheFunction__ bis = dict() # builtins globs = dict() locs = dict() # Setup a standard-compatible python environment bis["locals"] = lambda: locs bis["globals"] = lambda: globs globs["__builtins__"] = bis globs["__name__"] = "SUBENV" globs["__doc__"] = sourceCode # Determine how the __builtins__ dictionary should be accessed if type(__builtins__) is dict: bi_dict = __builtins__ else: bi_dict = __builtins__.__dict__ # Include the safe symbols for k in SAFE_SYMBOLS: # try from current locals try: locs[k] = locals()[k] continue except KeyError: pass # Try from globals try: globs[k] = globals()[k] continue except KeyError: pass # Try from builtins try: bis[k] = bi_dict[k] except KeyError: # Symbol not available anywhere: silently ignored pass # Include the symbols added by the caller, in the globals dictionary globs.update(additional_symbols) # Finally execute the def __TheFunction__ statement: eval(byteCode, globs, locs) # As a result, the function is defined as the item __TheFunction__ # in the locals dictionary fct = locs["__TheFunction__"] # Attach the function to the globals so that it can be recursive del locs["__TheFunction__"] globs["__TheFunction__"] = fct # Attach the actual source code to the docstring fct.__doc__ = sourceCode return fct ################################################################## ### Some tests def test(): # ----------------------------------------------------- # Code to execute as function 'f' (as a string): s = """ if a == "BE RECURSIVE": print "In the recursion 1" return __TheFunction__("THE END", 54) elif a == "THE END": print "In the recursion 2" return 54 print a print b x = True def sayhello(s): print "I say hello that way: %s" % s class SayHello(object): def __init__(self, s): self.__s = s print "ctor says %s" % self.__s def s(self): return self.__s try: 1/0 except ZeroDivisionError, ex: print "GOT EX", ex print "ooo in here says", ooo.mouf() result = a + b +1 afunction(a+1) c = re.compile("^a").search("ba", 1) d = re.compile("a").match("ba", 1) sayhello("I am so happy today %s,%s" % (c, d)) o = SayHello("this works") vvv = range(42) print vvv sys.stderr.write("writing to stderr\\n") print __TheFunction__ print "============ BEGIN docstring ===========" print __TheFunction__.__doc__ print "============ END docstring ===========" return a*b + __TheFunction__("BE RECURSIVE", 33) """ # End of source code string # ----------------------------------------------------- # Create objects, functions, etc. class OOO: def __init__(self, id): self.__id = id def mouf(self): return "OOO: My ID is %s" % self.__id def F(n): print "F: my parameter is", n # Generate a first function, f, which needs the re and sys modules import sys, re OoO = OOO(64) f = createFunction(s, "a=3, b=4", additional_symbols = dict(re=re, sys=sys, afunction=F, ooo=OoO)) # Generate another function OoO = OOO("FOR G") g = createFunction("print 'G: my parameter is ', p, 'and o says', o.mouf()\nreturn p", "p='undefined'", dict(o=OoO)) # Test them print "call f():", f() print "call g():", g() print "call f(42):", f(42) print "call g(22):", g(22) print "call f(b=42):", f(b=42) print "call f(b=7, a=8):", f(b=7, a=8) print "call f(9, 10):", f(9, 10) # Do some basic profiling def nothing(a): return a*42 import time ITER=10000000 time.sleep(1.) # force preemption st = time.time() for i in xrange(ITER): x = nothing(i) et = time.time() print "Basic: %fs" % (et-st) t = et-st time.sleep(1.) # force preemption f = createFunction("return a*42", "a") st = time.time() for i in xrange(ITER): x = f(i) et = time.time() print "FromString: %fs" % (et-st) print "FromString time = Basic%+f%%" % ((((et-st) - t) / t)*100.)