使用Lambdas从字符串表达式构建可执行函数

时间:2010-07-19 02:39:50

标签: python math parsing lambda

我正在使用python,我想要一个函数,它接受一个包含一个变量(x)的数学表达式的字符串,并返回一个使用lambdas计算该表达式的函数。语法应该是这样的:

f = f_of_x("sin(pi*x)/(1+x**2)")
print f(0.5)
0.8

语法应该允许()以及[]并使用标准运算符优先级。 Trig函数的优先级应低于乘法且高于加法。因此,字符串'sin 2x + 1'将等同于sin(2x)+1,尽管两者都是有效的。这是用于评估代数和三角表达式的用户输入,因此认为数学语法不是编程语法。支持的函数列表应该易于扩展,代码应该清晰易懂。不要破坏常量表达式是可以的。

此处的示例功能不完整。它采用表示表达式的嵌套列表并生成适当的函数。虽然有点容易理解,但即使对于python来说这看起来也很难看。

import math

def f_of_x(op):
    if (isinstance((op[0]), (int, long, float, complex)) ):
        return (lambda x:op[0])
    elif op[0]=="pi": return lambda x: 3.14159265358979
    elif op[0]=="e": return lambda x: 2.718281828459
    elif op[0]=="x": return lambda x: x
    elif op[0]=="sin": return lambda x: math.sin(f_of_x(op[1])(x))
    elif op[0]=="cos": return lambda x: math.cos(f_of_x(op[1])(x))
    elif op[0]=="tan": return lambda x: math.tan(f_of_x(op[1])(x))
    elif op[0]=="sqrt": return lambda x: math.sqrt(f_of_x(op[1])(x))
    elif op[0]=="+": return lambda x: (f_of_x(op[1])(x))+(f_of_x(op[2])(x))
    elif op[0]=="-": return lambda x: (f_of_x(op[1])(x))-(f_of_x(op[2])(x))
    elif op[0]=="*": return lambda x: (f_of_x(op[1])(x))*(f_of_x(op[2])(x))
    elif op[0]=="/": return lambda x: (f_of_x(op[1])(x))/(f_of_x(op[2])(x))
    elif op[0]=="**": return lambda x: (f_of_x(op[1])(x))**(f_of_x(op[2])(x))
    # should never get here with well formed input
    return

def test():
    # test function f(x) = sin(pi*x)/(1+x**2)
    s = ['/',['sin',['*',['pi'],['x']]],['+',[1],['**',['x'],[2]]]]
    f = f_of_x(s)
    for x in range(30):
        print " "*int(f(x*0.2)*30+10)+"x"

作为一般指导原则,请将您的解决方案视为关于lambdas和解析器的教程 - 而不是代码高尔夫。示例代码就是这样,所以写下你觉得最清楚的东西。

3 个答案:

答案 0 :(得分:5)

这个怎么样:

import math
def f_of_x(op):
    return eval("lambda x:" + op, math.__dict__)

可以很容易地支持[]以及()并使用标准运算符优先级。但是,它不允许你在没有parens的情况下使用trig函数,它也不会让你意味着并置乘法(如2x)。但是,支持的函数列表很容易扩展,并且代码可能很清晰,易于理解。

如果您绝对需要额外的功能,请查看http://christophe.delord.free.fr/tpg/。可以轻松修改该页面上给出的示例以执行您想要的任何操作。

答案 1 :(得分:0)

如果你坚持使用一种与Python完全不同的语言(其中所有函数总是用括号调用,而你想允许在没有调用的情况下调用几个函数)允许2x代表2 * x - 等等),您首先需要解析字符串,例如使用pyparsing(一个独立的解决方案),或ply(“python lexx和yacc”),如果你想要一个基于词法分析器和单独的“编译器编译器”的更传统的方法(那是yacc中的两个“c”代表什么:又一个编译器的编译器。

从这个解析器的输出中,您可能会生成要编译的Python语法 - 但是,没有真正的理由来生成lambda而不是普通def,因为您必须无论如何编译其中任何一个。因此,将该方法视为“lambdas教程”将会非常奇怪,因为使用lambda的决定将是任意的并且是非常有争议的。

我们正在谈论几个小时的编程,如果你希望任何清晰度,可能会超过100行产生的Python代码。我认为这绝对超出了SO问题和答案的正常范围。

生成一种私有字节码(并在Python中为该私有代码设置一个简单的解释器)的工作量大大减少了 - 但随后lambda(即使在 title 你的问题,澄清你已经全部消耗了在解决方案中使用该关键字的重要性)使用起来更加疯狂(因为实现这个变体的明显方法是返回,作为“函数”,相反自定义类的实例的绑定方法 - 所以“字节码”数据和python中的简单解释器可以适当地绑定在一起)。

lambda在Python中幸存下来(并且不情愿地,因为Guido最初热衷于在过渡到Python 3时将其删除,并且只有在面对“大规模反叛”时才会消退,并引用以下观点......; - )由于一个原因:因为有大量简单的简单任务(一个函数返回一个常量,一个完全返回它的参数,等等),非常短 lambda,< em> with 与其身体相关的所有限制只是一个表达式,非常方便。因此,在Python超出这个极其有限的角色(因为你显然非常热衷于这样做)的任何地方伸展lambdas都是一个非常糟糕的主意。

答案 2 :(得分:0)

示例fourFn.py代码将通过pyparsing为您提供相当好的领导。调整该代码以满足OP的特定要求留作OP的练习。

- 保罗