Repository: lmfit/lmfit-py

Issue:
asteval errors are not flushed after raising
#### Description

With parameter.set(expr="foo"), if the expression raises an exception in asteval, this error is stored in parameter._expr_eval.errors. Now, the function https://github.com/lmfit/lmfit-py/blob/6c87262fcfd3c361b197c6769852f76366113246/lmfit/parameter.py#L18-L21 gets called from parameter.__set_expression(val) and checks if the length of this errors variable is longer than 0 and if it is, the exception is raised.

However, if you try to set the expression for this parameter again (to a VALID expression), the Parameter._expr_eval.errors variable will still contain the first exception and so, check_ast_errors() will always raise an Exception. This should not be the expected behaviour, for example if lmfit is used from a GUI and the user tries to enter a valid expression after first entering an invalid one (where the exception was caught).

I will submit a PR where check_ast_errors() flushes the parameter._expr_eval.errors before trying to set a new expression.


###### Version information
Python: 3.6.5 (default, Apr  1 2018, 05:46:30) 
[GCC 7.3.0]
lmfit: 0.9.11, scipy: 1.1.0, numpy: 1.15.0, asteval: 0.9.12, uncertainties: 3.0.2, six: 1.11.0

###### Minimal example
```python
import lmfit

def set_constraints(paramname, expr):      
#        params[paramname]._expr_eval.error.clear() # this would be the dirty fix
        try:
            params[paramname].set(expr=expr)
            print("expr was valid")
        except SyntaxError:
            params[paramname].set(expr="")
            print("expr was not valid")
model = lmfit.models.PseudoVoigtModel()
params = model.make_params()
set_constraints("amplitude", expr="sigma * 2")
set_constraints("amplitude", expr="fail *")
set_constraints("amplitude", expr="sigma * 2")
```
produces:
```python
expr was valid
expr was not valid
expr was not valid
```
If you uncomment the "dirty fix", it produces
```python
expr was valid
expr was not valid
expr was valid
```

Comments:

Patch:
diff --git a/lmfit/parameter.py b/lmfit/parameter.py
index 9a5c7d1a..235a919b 100644
--- a/lmfit/parameter.py
+++ b/lmfit/parameter.py
@@ -826,6 +826,8 @@ class Parameter(object):
         if val is None:
             self._expr_ast = None
         if val is not None and self._expr_eval is not None:
+            self._expr_eval.error = []
+            self._expr_eval.error_msg = None
             self._expr_ast = self._expr_eval.parse(val)
             check_ast_errors(self._expr_eval)
             self._expr_deps = get_ast_names(self._expr_ast)


Test patch:
diff --git a/tests/test_parameters.py b/tests/test_parameters.py
index 73604e47..7344241d 100644
--- a/tests/test_parameters.py
+++ b/tests/test_parameters.py
@@ -212,6 +212,23 @@ class TestParameters(unittest.TestCase):
         pars.add('c4', expr='csum-c1-c2-c3', min=0, max=1)
         assert_(isclose(pars['c4'].value, 0.2))
 
+    def test_invalid_expr_exceptions(self):
+        "test if an exception is raised for invalid expressions (GH486)"""
+        p1 = Parameters()
+        p1.add('t', 2.0, min=0.0, max=5.0)
+        p1.add('x', 10.0)
+        with self.assertRaises(SyntaxError):
+            p1.add('y', expr='x*t + sqrt(t)/')
+        assert(len(p1['y']._expr_eval.error) > 0)
+        p1.add('y', expr='x*t + sqrt(t)/3.0')
+        p1['y'].set(expr='x*3.0 + t**2')
+        assert('x*3' in p1['y'].expr)
+        assert(len(p1['y']._expr_eval.error) == 0)
+        with self.assertRaises(SyntaxError):
+            p1['y'].set(expr='t+')
+        assert(len(p1['y']._expr_eval.error) > 0)
+        assert_almost_equal(p1['y'].value, 34.0)
+
 
 if __name__ == '__main__':
     unittest.main()