newbie: generate a function based on an expression

Michael Spencer mahs at telcopartners.com
Tue Dec 13 23:01:06 EST 2005


Bengt Richter wrote:
> On 12 Dec 2005 21:38:23 -0800, "Jacob Rael" <jacob.rael at gmail.com> wrote:
> 
>> Hello,
>>
>> I would like write a function that I can pass an expression and a
>> dictionary with values. The function would return a function that
>> evaluates the expression on an input. For example:
>>
>> fun = genFun("A*x+off", {'A': 3.0, 'off': -0.5, 'Max': 2.0,  'Min':
>> -2.0} )
>>
>>>>> fun(0)
>> -0.5
>>>>> fun(-10)
>> -2
>>>>> fun(10)
>> 2
>>
>> so fun would act as if I did:
>>
>> def fun(x):
>>    A = 3
>>    off = -0.5
>>    Max = 2
>>    Min = -2
>>    y = min(Max,max(Min,A*x + off))
>>    return(y)
>>
>> Any ideas?
> 
> ISTM genFun above can't generate fun without special interpretation
> of Min and Max. I.e., how is it supposed to know to use min and max (lower case)
> as in your expression for y, unless you tell it to by writing
> 
>     fun = genFun("min(Max,max(Min,A*x+off))", {'A': 3.0, 'off': -0.5, 'Max': 2.0,  'Min':
> 
> or state a rule about using min and/or max when Max and/or Min are present?
> 
> Setting aside security for the moment, and guessing at the rule you might
> want for Min and Max, perhaps something like (untested beyond what you see):
> (also limited to using x as the single function parameter name in the expression!)
> 
>  >>> import math
>  >>> allow = dict((k,v) for k,v in vars(math).items()
>  ...                 if callable(v) and not k.startswith('_') or k in ('e','pi'))
>  >>>
>  >>> allow.update(min=min, max=max)
>  >>> def vetexpr(expr, dct): return expr # XXX generate and check AST later
>  ...
>  >>> def genFun(expr, dct=None):
>  ...     d = allow.copy()
>  ...     if dct: d.update(dct)
>  ...     expr = '('+expr+')'
>  ...     vetexpr(expr, d) # check for illegal stuff XXX later
>  ...     if 'Min' in d: expr = 'max(Min, %s)'%(expr,)
>  ...     if 'Max' in d: expr = 'min(Max, %s)'%(expr,)
>  ...     return eval('lambda x: %s'%expr, d)
>  ...
>  >>> fun = genFun("A*x+off", {'A': 3.0, 'off': -0.5, 'Max': 2.0,  'Min': -2.0})
>  >>> fun
>  <function <lambda> at 0x02EEBDF4>
>  >>> fun(0)
>  -0.5
>  >>> fun(-10)
>  -2.0
>  >>> fun(10)
>  2.0
>  >>> import dis
>  >>> dis.dis(fun)
>    1           0 LOAD_GLOBAL              0 (min)
>                3 LOAD_GLOBAL              1 (Max)
>                6 LOAD_GLOBAL              2 (max)
>                9 LOAD_GLOBAL              3 (Min)
>               12 LOAD_GLOBAL              4 (A)
>               15 LOAD_FAST                0 (x)
>               18 BINARY_MULTIPLY
>               19 LOAD_GLOBAL              6 (off)
>               22 BINARY_ADD
>               23 CALL_FUNCTION            2
>               26 CALL_FUNCTION            2
>               29 RETURN_VALUE
> 
> I just made things from the math module accessible in anticipation. E.g.,
>  >>> sin2xd = genFun('sin(2.0*x*pi/180.)')
>  >>> sin2xd(15)
>  0.49999999999999994
>  >>> sin2xd(0)
>  0.0
>  >>> sin2xd(45)
>  1.0
> 
> Of course, if the dict is all constant named coefficients, you could substitute them as
> literal values in the expression before generating the function. You could do that
> in the placeholder vetexpr, but we'll leave that implementation for another post ;-)
> 
> 
> Regards,
> Bengt Richter
Here's another take on the source-composition approach:

Setup a useful enviroment:

def makenamespace():
     """Get some useful, and (I hope) safe functions"""
     import math, __builtin__
     modules = {
     math:['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos',
     'cosh', 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod',
     'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi',
     'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'],
     __builtin__:['max', 'min', 'pow']
     }
     ns = dict((n, getattr(mod, n)) for mod in modules
         for n in modules[mod])
     return ns


def compose_source(expr, presets, namespace = makenamespace()):
     """Return a function whose arguments are inferred from expr
     and whose constants are found in presets"""
     func = compile(expr, "src", "eval")
     names = func.co_names
     arguments = ",".join(name for name in names
         if name not in presets and name not in namespace)
     sourcelines = \
         ["def func(%s):" % arguments] +\
         ["%s=%s" % (name, value)
             for name, value in presets.iteritems()] +\
         ["return %s" % expr]
     composed = "\n    ".join(sourcelines) + "\n"
     exec composed in namespace
     return namespace["func"]

  >>> f = compose_source("min(a * b, max(c* d))",{"a": 3, "b": 4, "c": 5})

f is now a function only of d, since a,b and c are constants:

  >>> import inspect
  >>> inspect.formatargspec(inspect.getargspec(f))
  '((d,), None, None, None)'

  >>> f = compose_source("min(a * b, max(c* d))",{"d": 3, "b": 4, "c": 5})
  >>> inspect.getargspec(f)







More information about the Python-list mailing list