用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)))))