评估用户输入的模型作为 python 函数

Evaluate a model entered by user as python function

我必须在 python 3.4 中编写一个需要以下内容的任务: - 程序要求用户以字符串形式输入由多个子模型组成的数据模型,如下所示:

# the program ask :
please input the submodel1
# user will input:
A = a*x + b*y*exp(3*c/d*x)
# then the program ask :
-> please input the submodel2
# user will input:
B = e*x + f*y*exp(3*h/d*x)
# then the program ask :
-> please input the main model:
# user will input:
Y = A*x - exp(B**2/y)

然后程序采用这些模型(字符串)并对它们执行一些操作,例如将主模型曲线拟合到现有数据并绘制显示参数值的结果。 这里的想法是让用户可以在运行时自由选择模型,而无需将其作为应用程序中的函数进行编程。 我的问题在于将字符串转换或解释为具有 returns 值的 python 函数。我探索了像 eval() 函数这样的解决方案,但我正在寻找类似于 MatLab

中的解决方案
func = sym('string')
func = matlabfunction(func)

它创建了一个你可以轻松处理的匿名函数 希望我说清楚了,如果需要进一步说明,请告诉我, 该解决方案应与 python 3.4 兼容 提前致谢

您可以使用解析自: https://pypi.python.org/pypi/parse 并训练你的解析器识别数学表达式, 看这个例子(python27):
http://www.nerdparadise.com/tech/python/parsemath/

# A really simple expression evaluator supporting the
# four basic math functions, parentheses, and variables.

class Parser:
    def __init__(self, string, vars={}):
        self.string = string
        self.index = 0
        self.vars = {
            'pi' : 3.141592653589793,
            'e' : 2.718281828459045
            }
        for var in vars.keys():
            if self.vars.get(var) != None:
                raise Exception("Cannot redefine the value of " + var)
            self.vars[var] = vars[var]

    def getValue(self):
        value = self.parseExpression()
        self.skipWhitespace()
        if self.hasNext():
            raise Exception(
                "Unexpected character found: '" +
                self.peek() +
                "' at index " +
                str(self.index))
        return value

    def peek(self):
        return self.string[self.index:self.index + 1]

    def hasNext(self):
        return self.index < len(self.string)

    def skipWhitespace(self):
        while self.hasNext():
            if self.peek() in ' \t\n\r':
                self.index += 1
            else:
                return

    def parseExpression(self):
        return self.parseAddition()

    def parseAddition(self):
        values = [self.parseMultiplication()]
        while True:
            self.skipWhitespace()
            char = self.peek()
            if char == '+':
                self.index += 1
                values.append(self.parseMultiplication())
            elif char == '-':
                self.index += 1
                values.append(-1 * self.parseMultiplication())
            else:
                break
        return sum(values)

    def parseMultiplication(self):
        values = [self.parseParenthesis()]
        while True:
            self.skipWhitespace()
            char = self.peek()
            if char == '*':
                self.index += 1
                values.append(self.parseParenthesis())
            elif char == '/':
                div_index = self.index
                self.index += 1
                denominator = self.parseParenthesis()
                if denominator == 0:
                    raise Exception(
                        "Division by 0 kills baby whales (occured at index " +
                        str(div_index) +
                        ")")
                values.append(1.0 / denominator)
            else:
                break
        value = 1.0
        for factor in values:
            value *= factor
        return value

    def parseParenthesis(self):
        self.skipWhitespace()
        char = self.peek()
        if char == '(':
            self.index += 1
            value = self.parseExpression()
            self.skipWhitespace()
            if self.peek() != ')':
                raise Exception(
                    "No closing parenthesis found at character "
                    + str(self.index))
            self.index += 1
            return value
        else:
            return self.parseNegative()

    def parseNegative(self):
        self.skipWhitespace()
        char = self.peek()
        if char == '-':
            self.index += 1
            return -1 * self.parseParenthesis()
        else:
            return self.parseValue()

    def parseValue(self):
        self.skipWhitespace()
        char = self.peek()
        if char in '0123456789.':
            return self.parseNumber()
        else:
            return self.parseVariable()

    def parseVariable(self):
        self.skipWhitespace()
        var = ''
        while self.hasNext():
            char = self.peek()
            if char.lower() in '_abcdefghijklmnopqrstuvwxyz0123456789':
                var += char
                self.index += 1
            else:
                break

        value = self.vars.get(var, None)
        if value == None:
            raise Exception(
                "Unrecognized variable: '" +
                var +
                "'")
        return float(value)

    def parseNumber(self):
        self.skipWhitespace()
        strValue = ''
        decimal_found = False
        char = ''

        while self.hasNext():
            char = self.peek()            
            if char == '.':
                if decimal_found:
                    raise Exception(
                        "Found an extra period in a number at character " +
                        str(self.index) +
                        ". Are you European?")
                decimal_found = True
                strValue += '.'
            elif char in '0123456789':
                strValue += char
            else:
                break
            self.index += 1

        if len(strValue) == 0:
            if char == '':
                raise Exception("Unexpected end found")
            else:
                raise Exception(
                    "I was expecting to find a number at character " +
                    str(self.index) +
                    " but instead I found a '" +
                    char +
                    "'. What's up with that?")

        return float(strValue)

def evaluate(expression, vars={}):
    try:
        p = Parser(expression, vars)
        value = p.getValue()
    except Exception as (ex):
        msg = ex.message
        raise Exception(msg)

    # Return an integer type if the answer is an integer
    if int(value) == value:
        return int(value)

    # If Python made some silly precision error
    # like x.99999999999996, just return x + 1 as an integer
    epsilon = 0.0000000001
    if int(value + epsilon) != int(value):
        return int(value + epsilon)
    elif int(value - epsilon) != int(value):
        return int(value)

    return value

print evaluate("1 + 2 * 3")
print evaluate("(1 + 2) * 3")
print evaluate("-(1 + 2) * 3")
print evaluate("(1-2)/3.0 + 0.0000")
print evaluate("1 + pi / 4")
print evaluate("(a + b) / c", { 'a':1, 'b':2, 'c':3 })
print evaluate("(x + e * 10) / 10", { 'x' : 3 })
print evaluate("1.0 / 3 * 6")
print evaluate("(1 - 1 + -1) * pi")
print evaluate("pi * e")

我通过创建一个小型解析器找到了解决我的问题的方法,或者说是一个基于 python 库 PLY 的非常小的 dsl,请参阅此网页了解详细信息: http://www.dabeaz.com/ply/ply.html#ply_nn1 和这个项目: https://github.com/maldoinc/mamba

lmfit (http://lmfit.github.io/lmfit-py/) 有一个您可能会觉得有用的 ExpressionModel:

import numpy as np
import matplotlib.pyplot as plt
from lmfit.models import ExpressionModel

# make fake data
x = np.linspace(-10, 10, 201)
amp, cen, wid =  3.4, 1.8, 0.5

y = amp * np.exp(-(x-cen)**2 / (2*wid**2)) / (np.sqrt(2*np.pi)*wid)
y += np.random.normal(size=len(x), scale=0.01)

# create model from expression
model= ExpressionModel("amp * exp(-(x-cen)**2 /(2*wid**2))/(sqrt(2*pi)*wid)")

# fit data to that model
result = model.fit(y, x=x, amp=5, cen=5, wid=1)

# show results
print(result.fit_report())
plt.plot(x, y,         'bo')
plt.plot(x, result.init_fit, 'k--')
plt.plot(x, result.best_fit, 'r-')
plt.show()

这使用了 asteval (http://newville.github.io/asteval),因此支持相当丰富的 Python 语法子集以及 numpy 常量和 ufunc,包括 sqrtexpsinpi,依此类推。