代码之家  ›  专栏  ›  技术社区  ›  phkahler

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

  •  1
  • phkahler  · 技术社区  · 16 年前

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

    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"
    

    作为一个一般性的指导方针,把您的解决方案看作是关于lambda和解析器的教程——而不是代码高尔夫。示例代码就是这样,所以写下你感觉最清楚的东西。

    3 回复  |  直到 16 年前
        1
  •  5
  •   Gabe Timothy Khouri    16 年前

    这个怎么样?

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

    它很容易支撑 [] 以及 () 并使用标准运算符优先级。但是,它不允许您使用没有parens的trig函数,也不允许您通过并置来表示乘法(例如 2x )然而,支持的函数列表很容易扩展,并且代码可能是尽可能清晰易懂的。

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

        2
  •  0
  •   Alex Martelli    16 年前

    如果您坚持在表达式中使用与Python截然不同的语言(其中 全部的 函数总是用括号调用,而您希望在不使用--allow的情况下调用多个函数 2x 代表 2 * x --等等),首先需要分析字符串,例如 pyparsing (独立解决方案),或 ply (python-lexx和yacc)如果你想要一个更传统的方法,基于词法分析器和一个单独的“编译器编译器”(yacc中的两个“c”代表另一个编译器的编译器)。

    从这个解析器的输出中,您可以生成要编译的Python语法——但是,没有真正的理由生成 lambda 而不是平原 def ,因为无论如何您都必须编译它们中的任何一个。因此,把这种方法当作“关于lambdas的教程”是非常奇怪的,因为决定使用 兰姆达 会是武断的,非常有争议的。

    我们讨论的是几个小时的编程价值,如果您希望有任何清晰的地方,可能会有超过100行的结果python代码。我认为这完全超出了这个问题和答案的正常范围。

    基本上更少的工作是生成一种私有类型的字节码(并在Python中为该私有代码提供一个简单的解释器),但是 兰姆达 (哪怕是在 标题 对于您的问题,澄清您在解决方案中使用该关键字的重要性会更加疯狂(因为实现该变量的明显方法是作为“函数”返回适当自定义类实例的绑定方法,因此“字节码”数据和python中的terpreter可以适当地绑定在一起)。

    兰姆达 在python中存活了下来(并且很不情愿地,因为guido最初在转换到python 3的过程中热衷于删除它,并且只有在面对“大规模叛乱”时才会停止,调用以下点…;-)原因是:因为有大量非常简单的任务(返回常量的函数,返回其参数的函数)等等)那是 非常短 兰姆达 , 具有 所有与它的主体仅仅是一个表达式相关的限制都可以方便地执行。把lambdas放在python中的任何地方,超过这个极其有限的角色(显然你非常热衷于这样做)是一个非常糟糕的主意。

        3
  •  0
  •   PaulMcG    16 年前

    实例 fourFn.py 代码可以很好地引导您使用pyparsing编写这篇文章。修改该代码以满足OP的特定需求,这是留给OP的一个练习。

    ——保罗