[英]Function interpreter in python
假设我在 Python 中有一个 function,其中包括来自 Python 基础的数学表达式以及来自 Numpy 和 Scipy 的一些数学表达式,可能包括一些分布。 作为一个运行示例,请考虑:
import numpy as np
from scipy.stats import *
def my_process(args):
""" My process
"""
x1 = norm.rvs(loc=0, scale=1)
x2 = x1 + norm.rvs(loc=2, scale=0.5)
x3 = (x1 * np.exp(x2)) + norm.rvs(loc=-1, scale=2)
return x1, x2, x3
我想为这个function写一个解释器,把每一个出现的变量都变成一个class,一般写法如下:
class genericProcess():
def __init__(self):
pass
def process(self, parents):
""" This needs to be implemented for each class
"""
raise NotImplementedError
因此,对于我们的示例 function,我们会将给定的 function 解释为以下三个类:
class x1Process(genericProcess):
def __init__(self):
pass
def process(self):
return norm.rvs(loc=0, scale=1)
class x2Process(genericProcess):
def __init__(self):
pass
def process(self, parents):
return parents["x1"] + norm.rvs(loc=2, scale=0.5)
class x3Process(genericProcess):
def __init__(self):
pass
def process(self, parents):
return (parents["x1"] * parents["x2"]) + norm.rvs(loc=-1, scale=2)
这有可能吗? 如果是,那么开始实施它的第一步是什么?如果不是,什么可以使问题适当化以便我可以开始实施它? 例如,我认为使用字符串而不是 function 可能会使问题更简单,尽管我不确定。
编辑:
感谢评论,我可以使问题更具体一些。 我想要一个 function,称为“my_interpreter”,它将用户指定的 function 作为输入,并输出一个字典,其中每个键是 function 的一行(或者每个键是函数的返回元素之一),以及该字典是一个 class,它实现了“genericProcess”class 的“process”方法。我们的运行示例:
interpreted_function_dictionary = my_interpreter(my_process)
和
interpreted_function = {
"x1": x1Process,
"x2": x2Process,
"x3": x3Process
}
很难拦截定义。 您需要按照评论中的建议使用ast
解析代码。
sympy
另一种方法是将所有数学运算替换为它们的符号表示,这些符号表示可以在以后执行。 sympy
package 正是这样做的,应该包含您需要的大多数数学运算。 还有sympy.stats
具有大部分统计功能。 (与matlab
中带有syms
的符号计算非常相似。)
要将sympy
与numpy
后端一起使用,您可以使用他们的lambdify
function,例如
from sympy import sin, lambdify
from sympy.abc import x
expr = sin(x)/x
f = lambdify(x, expr, "numpy")
从1.11版本开始,它似乎还不支持scipy
。
与sympy
类似,您可以为所有返回表达式而不是结果的数学运算创建包装类。 然后,每个表达式都是您的process
,您可以计算每个表达式以获得结果值。
不确定这是否符合 OP 的要求。
from dataclasses import dataclass, field
from typing import Any, ClassVar
import numpy as np
import scipy
@dataclass
class EvaluatableExpression:
name: str
args: Any = field(default_factory=tuple)
kwargs: Any = field(default_factory=dict)
package: ClassVar = None
def evaluate(self):
# recursively evaluate any executable args and kwargs
args = (arg.evaluate() if isinstance(arg, EvaluatableExpression) else arg for arg in self.args)
kwargs = {k: v.evaluate() if isinstance(v, EvaluatableExpression) else v for k, v in self.kwargs.items()}
return getattr(self.package, self.name)(*args, **kwargs)
@dataclass
class NumpyFunc(EvaluatableExpression):
package: ClassVar = np
@dataclass
class ScipyFunc(EvaluatableExpression):
package: ClassVar = scipy
@dataclass
class ScipyStats(EvaluatableExpression):
stats_package: str = ''
def __post_init__(self):
self.package = getattr(scipy.stats, self.stats_package)
对于 python 数学,您可以使用魔术方法处理它们:
@dataclass
class PythonMath(EvaluatableExpression):
def evaluate(self):
# the function names are names of magic methods, e.g. '__add__',
# assuming only binary ops on args[0] and args[1]
op0 = self.args[0]
self.package = op0.evaluate() if isinstance(op0, EvaluatableExpression) else op0
# save args and load args later so it doesn't change args before and after evaluation
temp_args = self.args
self.args = self.args[1:]
result = super().evaluate()
self.args = temp_args
return result
@dataclass
class Operand:
content: Any
def __add__(self, other):
return PythonMath(name='__add__', args=(self.content, other))
def __sub__(self, other):
return PythonMath(name='__sub__', args=(self.content, other))
def __mul__(self, other):
return PythonMath(name='__mul__', args=(self.content, other))
def __truediv__(self, other):
return PythonMath(name='__truediv__', args=(self.content, other))
...
对于Operand
,不可能使用__getattr__
或__getattribute__
捕捉magic methods
。 您可以编写自定义元类来执行此操作以简化复制和粘贴代码。
def process(args):
""" My process
"""
x1 = ScipyStats(stats_package='norm', name='rvs', kwargs={'loc': 0, 'scale': 1})
x2 = Operand(x1) + ScipyStats(stats_package='norm', name='rvs', kwargs={'loc': 2, 'scale': 0.5})
x3 = Operand(Operand(x1) * NumpyFunc(name='exp', args=(x2,))) + ScipyStats(stats_package='norm', name='rvs',
kwargs={'loc': -1, 'scale': 0.5})
return x1, x2, x3
现在,所有返回的变量都将是“表达式”。 我们可以看到
>>> print(x[0])
ScipyStats(name='rvs', args=(), kwargs={'loc': 0, 'scale': 1}, stats_package='norm')
>>> print(x[1])
PythonMath(name='__add__', args=(ScipyStats(name='rvs', args=(), kwargs={'loc': 0, 'scale': 1}, stats_package='norm'), ScipyStats(name='rvs', args=(), kwargs={'loc': 2, 'scale': 0.5}, stats_package='norm')), kwargs={})
>>> print(x[2])
PythonMath(name='__add__', args=(PythonMath(name='__mul__', args=(ScipyStats(name='rvs', args=(), kwargs={'loc': 0, 'scale': 1}, stats_package='norm'), NumpyFunc(name='exp', args=(PythonMath(name='__add__', args=(ScipyStats(name='rvs', args=(), kwargs={'loc': 0, 'scale': 1}, stats_package='norm'), ScipyStats(name='rvs', args=(), kwargs={'loc': 2, 'scale': 0.5}, stats_package='norm')), kwargs={}),), kwargs={})),
评估它们给出:
>>> print(x[0].evaluate())
-1.331802485169775
>>> print(x[1].evaluate())
0.7789471967940289
>>> print(x[2].evaluate())
-60.03245897617831
当然,你可以通过定义别名来让定义数学表达式更漂亮更简洁,例如借用pyspark
库
def _create_function(name, doc=""):
""" Create a function for aggregator by name"""
def _(*args, **kwargs):
package, new_name = name.split('__')
if package == 'np':
cls = NumpyFunc
elif package == 'scipy':
cls = ScipyFunc
elif package == 'ss':
cls = ScipyStats
return cls(func=new_name, args=args, kwargs=kwargs)
_.__name__ = name
_.__doc__ = doc
return _
ALL = [f'np__{func}' for func in np.ma.__all__] + [f'scipy__{func}' for func in ...] +
...
for func_dict in ALL:
for _name, _doc in func_dict.items():
globals()[_name] = _create_function(_name, _doc)
del _name, _doc
然后你可以有类似的东西:
x1 = ss__norm_rvs(loc=0, scale=1)
x2 = Operand(x1) + ss__norm_rvs(loc=2, scale=0.5)
x3 = Operand(Operand(x1) * np__exp(x2)) + ss__norm_rvs(loc=-1, scale=2)
您甚至可以通过使所有内容成为Operand
的子类来摆脱讨厌的Operand
。
希望这可以帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.