Using ExprEvaluator
¶
This page describes how to use multilstsq.ExprEvaluator
, which is a tool to parse and evaluate (convert to a Python object) expressions provided as strings.
At first, an expression is parsed:
from multilstsq import ExprEvaluator
e = ExprEvaluator('a*(2+c*b)')
This expression has 3 variables: a
, b
and c
. We can get this information using:
e.variables # returns {'a', 'b', 'c'}
As long as these variables are not substituted, it is not possible to evaluate the expression. We can substitute these variables and evaluate the results by doing:
e(a=3, b=5, c=1) # returns 21, as expected.
However, this is not the typical use case. The goal of multilstsq.ExprEvaluator
is to enable partial substitution, for example substituting b
and c
, but not a
:
e2 = e.substitute(constants={'b':5, 'c':1})
e2(a=3) # Will also return 21, as previously
We can see that e2
has two constants (defined variables) and one variable. We can also get the expression as a string for debugging:
e2.variables # returns {'a'}
e2.constants # returns {'b', 'c'}
str(e2) # returns '(a) * ((2) + ((c) * (b)))'
# The parentheses are added to ensure correct resolution order
By looking at the expression, we can observe that second operand of the multiplication is fully known, and therefore that it could be reduced:
e3 = e2.reduce()
str(e3) # returns '(a) * (__ExprEvaluator_0)'
e3.variables # returns {'a'}
e3.constants # returns {'__ExprEvaluator_0'}
We can observe that the expression has indeed be reduced. In the present case, it would have made sense to reduce it to (a) * 7
, but instead a (reserved) constant __ExprEvaluator_0
has been created. This makes sense the result of the parenthesis could have been a complex object, like a numpy array.
If the expression is evaluated multiple times, it is usually a large performance improvement to substitute everything possible before evaluating it.
Note that any python object can be substituted:
import numpy as np
e4 = e3.substitute(constants = {'a': np.array([1, 2, 3])})
e4.eval() # returns array([ 7, 14, 21])
e4() # returns the same thing
e4.reduce().eval() # still the same thing
Complex expression can also be used in the evaluation string. Here’s another example:
import numpy as np
e = ExprEvaluator('np.linalg.inv(A).dot(y)')
v1 = np.array([[1, 4], [2, -1]])
v2 = np.array([9, 0])
e(A=v1,y=v2) # returns array([1., 2.])
By default, multilstsq.ExprEvaluator
uses the modules defined at the calling stack frame. This makes np
work in the expression in the example above.
This behavious can be disabled if needed:
import numpy as np
e = ExprEvaluator('np.linalg.inv(A).dot(y)', enable_caller_modules=False)
v1 = np.array([[1, 4], [2, -1]])
v2 = np.array([9, 0])
e(A=v1,y=v2) # Will fail, np is not defined
e(A=v1,y=v2, np=np) # returns array([1., 2.])
Additionally, it is also possible to substitute a variable in the expression by something else:
import numpy as np
e = ExprEvaluator('np.linalg.inv(A).dot(y)')
e2 = e.substitute(expressions={'A':'A.T'})
str(e2) # '(((((np).linalg).inv)(A)).dot)(y)'
v1 = np.array([[1, 4], [2, -1]])
v2 = np.array([9, 0])
e(A=v1,y=v2) # returns array([1., 4.])
This functionnality is used extensively by the multilstsq.MultiRegression
class.
Warning
Using multilstsq.ExprEvaluator
to evaluate expressions from untrusted sources may lead to security vulnerabilities.