[英]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.