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

1 投票
3 回答
2663 浏览
提问于 2025-04-16 01:29

我正在使用Python,想要一个函数,这个函数可以接收一个包含数学表达式的字符串(只有一个变量x),然后返回一个可以计算这个表达式的函数,使用的是lambda表达式。语法应该是这样的:

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

语法应该允许使用()和[],并且遵循标准的运算符优先级。三角函数的优先级应该低于乘法,但高于加法。因此,字符串'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"

作为一个一般性的指导,考虑把你的解决方案当作一个关于lambda和解析器的教程,而不是代码比赛。示例代码只是一个参考,所以请写出你认为最清晰的代码。

3 个回答

0

如果你坚持要用一种和Python差别很大的语言来写表达式(在Python中,所有函数调用时都需要加上括号,而你想让一些函数可以不加括号,比如让2x表示2 * x),那么你首先需要解析这个字符串,比如可以使用pyparsing(这是一个独立的解决方案),或者使用ply(这是一个基于词法分析器和一个单独的“编译器的编译器”的传统方法,yacc中的两个“c”就是这个意思:又一个编译器的编译器)。

从这个解析器的输出中,你可以生成Python语法来编译——不过,生成lambda而不是普通的def其实没有什么必要,因为无论如何你都得编译它们。所以,把这个方法看作是“关于lambda的教程”其实很奇怪,因为使用lambda的决定是任意的,而且很有争议。

我们说的是几小时的编程工作,如果你希望代码清晰,结果可能会超过100行Python代码。我认为这绝对超出了一个正常的StackOverflow问题和回答的范围。

要做的工作少得多的是生成一种私有的字节码(然后在Python中为这种私有代码写一个简单的解释器)——但这样一来,lambda(它甚至在你问题的标题中,说明你非常关注在解决方案中使用这个关键词的重要性)使用起来就更疯狂了(因为实现这个变体的明显方法是返回一个适当自定义类的实例的绑定方法作为“函数”,这样“字节码”数据和Python中的简单解释器就可以适当地绑定在一起)。

lambda在Python中得以保留(虽然是勉强的,因为Guido最初想在转向Python 3时去掉它,只有在面对“群众反抗”时才妥协……;-)),原因只有一个:因为有很多非常简单的任务(比如一个函数返回常量,另一个返回它的参数等等),用一个非常短lambda(由于它的主体只能是一个表达式而受到限制)来完成这些任务是很方便的。因此,想把lambda在Python中扩展到这个极其有限的角色之外(显然你非常想这样做)是个相当糟糕的主意。

0

这个例子 fourFn.py 的代码可以帮助你很好地理解如何用 pyparsing 来写这个。把这段代码改成符合提问者具体需求的部分就留给提问者自己去练习了。

-- 保罗

5

这样怎么样:

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

这个代码可以很容易地支持 [](),而且它遵循标准的运算符优先级。不过,它不允许你在没有括号的情况下使用三角函数,也不允许你像 2x 这样直接写乘法。尽管如此,支持的函数列表是可以轻松扩展的,而且这段代码应该是尽可能清晰易懂的。

如果你真的需要那些额外的功能,可以看看 http://christophe.delord.free.fr/tpg/。那页上的示例可以很容易地修改,以满足你的所有需求。

撰写回答