簡體   English   中英

Function 翻譯 python

[英]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的符號計算非常相似。)

要將sympynumpy后端一起使用,您可以使用他們的lambdify function,例如

from sympy import sin, lambdify
from sympy.abc import x
expr = sin(x)/x
f = lambdify(x, expr, "numpy")

從1.11版本開始,它似乎還不支持scipy

DIY:)

sympy類似,您可以為所有返回表達式而不是結果的數學運算創建包裝類。 然后,每個表達式都是您的process ,您可以計算每個表達式以獲得結果值。

不確定這是否符合 OP 的要求。

對於 Numpy、Scipy 函數

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 數學操作

對於 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM