开发者

Dynamic function building in Python

I need to create a function for an arbitrary number of peak开发者_JAVA技巧s to pass to a least square fitting routine. For each peak there is an extra term in the function, i.e.

One term with a value at 50 returns a function: f(p, x) = p[0]*50

Two terms with values at 50, 60 returns a function: f(p, x) = p[0]*50 + p[1]*60*x

Three terms with values at 50, 60, 70 returns: f(p, x) = p[0]*50 + p[1]*60*x + p[2]*70*x^2

etc.

A couple of naive attempts are shown below,

def foo(vals):
    fn = lambda p, x: 0
    i = 0
    for v in vals:
        fn = lambda p, x : fn(p, x) + p[i] * v * x**i
        i += 1
    return fn
# Causes a recursion error (I think)

Second attempt ...

def bar(vals):
    terms = []
    i = 0
    for v in vals:
        terms.append(lambda x, p: p[i] * v * x**i)
        i += 1
    def fn(x, p):
        tvals = [t(x, p) for t in terms]
        sum = 0
        for t in terms:
            sum = sum + t(x, p)
        return sum
    return fn
# Generates the wrong values

I suspect that this is a problem with referencing, i.e. Python refers to list declarations etc. but this is a little complicated to untangle - any help would be appreciated!


How about:

def foo(vals):
    def f(p,x):
        result=0
        for i,(av,ap) in enumerate(zip(vals,p)):
            result+=av*ap*(x**i)
        return result
    return f

print(foo([50])([2],3))
# f(p,x)=50*2
# 100
print(foo([50,60])([2,3],4))
# f(p,x)=50*2+60*3*x
# 820


You can write the whole thing as a function that returns a closure:

def make_function(vals):
    def evaluate(x,p):
        return sum(p[i] * v * x**i
                   for i,v in enumerate(vals))
    return evaluate

The issue you're running into with the terms functions comes up regularly. I wrote a long explanation about this problem before, hopefully it will be helpful.

Btw, foo and bar are syntactic variables, which means they are used to explain the syntax. For implementation problems you should really use good names that mean something in the domain, that often makes the problem much easier to understand.


Closures don't capture the values of variables in outer scopes at the time of their creation, they really capture these variables. [lambda: i for i in range(5)] gives you five functions that all return 4, because they all refer to the same i (which is 4 when iteration ends). You can hack around this by using default arguments (which are bind values at function definition time): [lambda i=i: i for i in range(5)] works as expected.

Also, use enumerate. This, together with making fn a lambda, can reduce your code to just two, imo equally readable, lines (I'm assuming the second version, the first seems broken in more ways, as indicated in comments):

def bar(vals):
    terms = [lambda x, p, i=i, v=v: p[i] * v * x**i for i, v in enumerate(vals)]
    return lambda x, p: sum(term(x, p) for term in terms)


A simple modification stores a reference to each fn in a default parameter

def foo(vals):
    fn = lambda p, x: 0
    i = 0
    for v in vals:
        fn = lambda p, x, f=fn, i=i: f(p, x) + p[i] * v * x**i
        i += 1
    return fn
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜