Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/python-2.7/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
用Python中的自定义函数解析方程_Python_Python 2.7_Parsing_Equation - Fatal编程技术网

用Python中的自定义函数解析方程

用Python中的自定义函数解析方程,python,python-2.7,parsing,equation,Python,Python 2.7,Parsing,Equation,我有一个字符串,它是一个数学方程,但有一些自定义函数。我需要找到所有这样的函数,并用一些代码替换它们 例如,我有一个字符串: a+b+f1(f2(x,y),x) 我想要用x+y^2替换(比如)f2(x,y),用sin(x+y)替换f1(x,y) 如果支持嵌套函数(如示例中所示),这将非常理想。但是,如果不支持嵌套,它仍然是有用的 正如我从类似主题中了解到的,这可以使用编译器模块(如compiler.parse(eq))来完成。如何使用compiler.parse(eq)创建的AST对象重新构造

我有一个字符串,它是一个数学方程,但有一些自定义函数。我需要找到所有这样的函数,并用一些代码替换它们

例如,我有一个字符串:

a+b+f1(f2(x,y),x)
我想要用
x+y^2
替换(比如)f2(x,y),用
sin(x+y)
替换
f1(x,y)

如果支持嵌套函数(如示例中所示),这将非常理想。但是,如果不支持嵌套,它仍然是有用的

正如我从类似主题中了解到的,这可以使用编译器模块(如
compiler.parse(eq)
)来完成。如何使用
compiler.parse(eq)
创建的AST对象重新构造字符串,替换所有找到的函数


我只需要执行替换,然后字符串将在其他程序中使用。不需要评估。

你的长期目标是什么?是评估功能还是简单地执行替换?在前一种情况下,您可以简单地尝试一下(注意,
f1
f2
也可以动态定义):


如果您想替换函数并获得修改后的版本,您确实需要求助于某种AST解析器。但是在使用
eval
时要小心,因为这会为恶意用户输入代码打开一个安全漏洞。

我认为您需要使用类似于
PyBison
的解析器生成器

请参见包含所需基本代码的示例:

您需要为函数添加令牌类型,为函数添加规则,然后在遇到该函数时会发生什么


如果您需要有关解析等的其他信息,请尝试阅读一些关于Lex和(Yacc或Bison)的基本教程。

您事先知道这些变量吗

我建议使用Symphy

以以下为例:

import sympy

a,b,x,y = sympy.symbols('a b x y')
f1 = sympy.Function('f1')
f2 = sympy.Function('f2')

readString = "a+b+f1(f2(x,y),x)"

z = eval(readString)
“z”现在将是一个表示数学公式的符号项。你可以把它打印出来。然后可以使用
subs
替换符号术语或函数。您可以再次用符号表示正弦(如
f1
f2
),也可以在
sympy.mpmath
中使用
sin()


根据您的需要,此方法非常有用,因为您最终可以计算、计算或简化此表达式。

这里是一个最简单的工作示例(
+,-,*,/,***
二进制和一元运算以及实现的函数调用)。操作的优先级用括号设置

完成的功能比所给示例的功能多一点:

from __future__ import print_function
import ast

def transform(eq,functions):
    class EqVisitor(ast.NodeVisitor):
        def visit_BinOp(self,node):
            #generate("=>BinOp")
            generate("(")
            self.visit(node.left)
            self.visit(node.op)
            #generate("ici",str(node.op),node._fields,node._attributes)
            #generate(dir(node.op))
            self.visit(node.right)
            generate(")")
            #ast.NodeVisitor.generic_visit(self,node)
        def visit_USub(self,node):
            generate("-")
        def visit_UAdd(self,node):
            generate("+")

        def visit_Sub(self,node):
            generate("-")
        def visit_Add(self,node):
            generate("+")
        def visit_Pow(self,node):
            generate("**")
        def visit_Mult(self,node):
            generate("*")
        def visit_Div(self,node):
            generate("/")
        def visit_Name(self,node):
            generate(node.id)
        def visit_Call(self,node):
            debug("function",node.func.id)
            if node.func.id in functions:
                debug("defined function")
                func_visit(functions[node.func.id],node.args)
                return
            debug("not defined function",node.func.id)
            #generate(node._fields)
            #generate("args")
            generate(node.func.id)
            generate("(")
            sep = ""
            for arg in node.args:
                generate (sep)
                self.visit(arg)
                sep=","
            generate(")")
        def visit_Num(self,node):
            generate(node.n)
        def generic_visit(self, node):


            debug ("\n",type(node).__name__)
            debug (node._fields)
            ast.NodeVisitor.generic_visit(self, node)

    def func_visit(definition,concrete_args):
        class FuncVisitor(EqVisitor):
            def visit_arguments(self,node):
                #generate("visit arguments")
                #generate(node._fields)
                self.arguments={}
                for concrete_arg,formal_arg in zip(concrete_args,node.args):
                    #generate(formal_arg._fields)
                    self.arguments[formal_arg.id]=concrete_arg
                debug(self.arguments)
            def visit_Name(self,node):
                debug("visit Name",node.id)
                if node.id in self.arguments:
                    eqV.visit(self.arguments[node.id])
                else:
                    generate(node.id)


        funcV=FuncVisitor()
        funcV.visit(ast.parse(definition))

    eqV=EqVisitor()
    result = []
    def generate(s):
        #following line maybe usefull for debug
        debug(str(s))
        result.append(str(s))
    eqV.visit(ast.parse(eq,mode="eval"))
    return "".join(result)
def debug(*args,**kwargs):
    #print(*args,**kwargs)
    pass
用法:

functions= {
    "f1":"def f1(x,y):return x+y**2",
    "f2":"def f2(x,y):return sin(x+y)",
}
eq="-(a+b)+f1(f2(+x,y),z)*4/365.12-h"
print(transform(eq,functions))
结果

((-(a+b)+(((sin((+x+y))+(z**2))*4)/365.12))-h)
警告

该代码适用于Python2.7,由于它依赖于AST,因此不能保证适用于其他版本的Python。Python 3版本不起作用。

(按照建议使用
sympy
,并添加一些额外的代码。)

下面的代码在组合给定函数后将给定字符串转换为新字符串。它很仓促,文档也不完整,但是它很有效


警告

包含
exec
eval
,如果输入由外部用户提供,则恶意代码可能会受到影响


更新:

  • 重写整个代码。在Python2.7中工作
  • 函数参数可以用逗号或空格分隔,也可以同时用逗号和空格分隔
  • 所有有问题的例子和评论都有效


完全替换是相当棘手的。这是我的尝试。在这里,我们可以成功地内联表达式, 但并非所有情况下都如此。此代码仅适用于AST,由
AST
模块生成。并使用
codegen
将其字符串化回代码

首先,我们定义几个助手:

import ast
import codegen
import copy

def parseExpr(expr):
    # Strip:
    # Module(body=[Expr(value=
    return ast.parse(expr).body[0].value

def toSource(expr):
    return codegen.to_source(expr)
然后,我们使用
NodeTransformer
定义替换函数。 例如:

substitute(parseExpr("a + b"), { "a": parseExpr("1") }) # 1 + b
需要多个变量的模拟替换,以适当避免恶劣情况。 例如,在
a+b
中用
a
b
替换
a+b
。 结果应该是
(a+b)+(a+b)
,但是如果我们先用
a
替换
a+b
,我们将得到
(a+b)+b
,然后替换
b
,我们将得到
(a+(a+b))+b
,这是错误的结果!因此,同时很重要:

class NameTransformer(ast.NodeTransformer):
    def __init__(self, names):
        self.names = names

    def visit_Name(self, node):
        if node.id in self.names:
            return self.names[node.id]
        else:
            return node

def substitute(expr, names):
    print "substitute"
    for varName, varValue in names.iteritems():
        print "  name " + varName + " for " + toSource(varValue)
    print "  in " + toSource(expr)
    return NameTransformer(names).visit(expr)
然后我们编写类似的
NodeTransformer
来查找调用,我们可以在其中内联函数定义:

打印出:

f1 first, unique variable names
expand call to f1(u, v) with arguments f2(x, y), x
substitute
  name u for f2(x, y)
  name v for x
  in sin((u + v))
expand call to f2(i, j) with arguments x, y
substitute
  name i for x
  name j for y
  in ((i + j) ^ 2)
((a + b) + sin((((x + y) ^ 2) + x)))
---
f1 first
expand call to f1(x, y) with arguments f2(x, y), x
substitute
  name y for x
  name x for f2(x, y)
  in sin((x + y))
expand call to f2(x, y) with arguments x, y
substitute
  name y for y
  name x for x
  in ((x + y) ^ 2)
((a + b) + sin((((x + y) ^ 2) + x)))
---
f2 first
expand call to f1(x, y) with arguments f1(x, x), y
expand call to f1(x, y) with arguments x, x
substitute
  name y for x
  name x for x
  in (x + y)
substitute
  name y for y
  name x for (x + x)
  in (x + x)
((x + x) + ((x + x) + x))
---
fact
expand call to fact(n) with arguments n
substitute
  name n for n
  in n if (n == 0) else (n * fact((n - 1)))
expand call to fact(n) with arguments (n - 1)
substitute
  name n for (n - 1)
  in n if (n == 0) else (n * fact((n - 1)))
expand call to fact(n) with arguments ((n - 1) - 1)
substitute
  name n for ((n - 1) - 1)
  in n if (n == 0) else (n * fact((n - 1)))
n if (n == 0) else (n * (n - 1) if ((n - 1) == 0) else ((n - 1) * ((n - 1) - 1) if (((n - 1) - 1) == 0) else (((n - 1) - 1) * fact((((n - 1) - 1) - 1)))))
不幸的是,
pypi
中的
codegen
版本有缺陷。它不能正确地插入表达式,即使AST说应该这样做。我使用了(
pip-install-git+git://github.com/jbremer/codegen
)。它也添加了不必要的括号,但总比没有好。感谢@XavierCombelle的提示



如果使用匿名函数,即
lambda
,则替换会变得更加棘手。然后需要重命名变量。您可以尝试使用替换或实现来搜索lambda演算。然而,我运气不好,找到了任何使用Python完成任务的文章。

为了重建字符串,需要在给定的AST上使用LVR遍历。为什么不标记字符串?@Maher这正是问题所在。如何执行LVR遍历。我需要一个例子。@Styvane如果我将字符串简单标记化,那么我想,我将无法使用嵌套函数?出于好奇:你是在尝试为某种进化算法这样做吗?我只需要执行替换。当语法是python的一个子集时,将使用其他程序(模拟器)来完成评估,编写语法分析器似乎有些过分。但在我的例子中,我事先并不知道变量。看起来codegen项目是未维护的,并且有关键的bug供使用(binop周围没有括号,因此没有操作优先级)
class NameTransformer(ast.NodeTransformer):
    def __init__(self, names):
        self.names = names

    def visit_Name(self, node):
        if node.id in self.names:
            return self.names[node.id]
        else:
            return node

def substitute(expr, names):
    print "substitute"
    for varName, varValue in names.iteritems():
        print "  name " + varName + " for " + toSource(varValue)
    print "  in " + toSource(expr)
    return NameTransformer(names).visit(expr)
class CallTransformer(ast.NodeTransformer):
    def __init__(self, fnName, varNames, fnExpr):
        self.fnName = fnName
        self.varNames = varNames
        # substitute in new fn expr for each CallTransformer
        self.fnExpr = copy.deepcopy(fnExpr)
        self.modified = False

    def visit_Call(self, node):
        if (node.func.id == self.fnName):
            if len(node.args) == len(self.varNames):
                print "expand call to " + self.fnName + "(" + (", ".join(self.varNames)) + ")" + " with arguments "+ ", ".join(map(toSource, node.args))
                # We substitute in args too!
                old_node = node
                args = map(self.visit, node.args)
                names = dict(zip(self.varNames, args))
                node = substitute(self.fnExpr, names)
                self.modified = True
                return node
            else:
                raise Exception("invalid arity " + toSource(node))
        else:
            return self.generic_visit(node)

def substituteCalls(expr, definitions, n = 3):
    while True:
        if (n <= 0):
            break
        n -= 1

        modified = False
        for fnName, varNames, fnExpr in definitions:
            transformer = CallTransformer(fnName, varNames, fnExpr)
            expr = transformer.visit(expr)
            modified = modified or transformer.modified

        if not modified:
            break

    return expr
if True:
    print "f1 first, unique variable names"
    ex = parseExpr("a+b+f1(f2(x, y), x)")
    ex = substituteCalls(ex, [
        ("f1", ["u", "v"], parseExpr("sin(u + v)")),
        ("f2", ["i", "j"], parseExpr("i + j ^ 2"))])
    print toSource(ex)
    print "---"

if True:
    print "f1 first"
    ex = parseExpr("a+b+f1(f2(x, y), x)")
    ex = substituteCalls(ex, [
        ("f1", ["x", "y"], parseExpr("sin(x + y)")),
        ("f2", ["x", "y"], parseExpr("x + y ^ 2"))])
    print toSource(ex)
    print "---"

if True:
    print "f2 first"
    ex = parseExpr("f1(f1(x, x), y)")
    ex = substituteCalls(ex, [
        ("f1", ["x", "y"], parseExpr("x + y"))])
    print toSource(ex)
    print "---"

if True:
    print "fact"
    ex = parseExpr("fact(n)")
    ex = substituteCalls(ex, [
        ("fact", ["n"], parseExpr("n if n == 0 else n * fact(n-1)"))])
    print toSource(ex)
    print "---"
f1 first, unique variable names
expand call to f1(u, v) with arguments f2(x, y), x
substitute
  name u for f2(x, y)
  name v for x
  in sin((u + v))
expand call to f2(i, j) with arguments x, y
substitute
  name i for x
  name j for y
  in ((i + j) ^ 2)
((a + b) + sin((((x + y) ^ 2) + x)))
---
f1 first
expand call to f1(x, y) with arguments f2(x, y), x
substitute
  name y for x
  name x for f2(x, y)
  in sin((x + y))
expand call to f2(x, y) with arguments x, y
substitute
  name y for y
  name x for x
  in ((x + y) ^ 2)
((a + b) + sin((((x + y) ^ 2) + x)))
---
f2 first
expand call to f1(x, y) with arguments f1(x, x), y
expand call to f1(x, y) with arguments x, x
substitute
  name y for x
  name x for x
  in (x + y)
substitute
  name y for y
  name x for (x + x)
  in (x + x)
((x + x) + ((x + x) + x))
---
fact
expand call to fact(n) with arguments n
substitute
  name n for n
  in n if (n == 0) else (n * fact((n - 1)))
expand call to fact(n) with arguments (n - 1)
substitute
  name n for (n - 1)
  in n if (n == 0) else (n * fact((n - 1)))
expand call to fact(n) with arguments ((n - 1) - 1)
substitute
  name n for ((n - 1) - 1)
  in n if (n == 0) else (n * fact((n - 1)))
n if (n == 0) else (n * (n - 1) if ((n - 1) == 0) else ((n - 1) * ((n - 1) - 1) if (((n - 1) - 1) == 0) else (((n - 1) - 1) * fact((((n - 1) - 1) - 1)))))