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