Repository: lmfit/lmfit-py

Issue:
Composite models param_names not updated when prefix changes in a sub-model
Since sub-models are not copied but embedded this happens:

```
p1 = GaussianModel(prefix='p1_')
p2 = GaussianModel(prefix='p2_')

model = p1 + p2

p1.prefix = 'p3_'
model.param_names
Out[19]: 
{'p1_amplitude',
 'p1_center',
 'p1_sigma',
 'p2_amplitude',
 'p2_center',
 'p2_sigma'}
```



Discussion:
I'm not sure how to fix this. In principle param_names can become a property and be computed on-fly. But this is not possible with the recent "noparam" version.

The prefix setter should do something smart to recompute all the parameter names for a composite model, but the sub-model know nothing about being in a composite model. Maybe, for composite models, a property param_names should always be computed on fly through an ad-hoc method like _param_names_from components().

**EDIT**: Reformulate solution
**EDIT**: Rewrite solution again


Resolving patch:
diff --git a/lmfit/model.py b/lmfit/model.py
--- a/lmfit/model.py
+++ b/lmfit/model.py
@@ -85,6 +85,7 @@ def __init__(self, func, independent_vars=None, param_names=None,
         self.opts = kws
         self.result = None
         self.param_hints = {}
+        self._param_names = set()
         self._parse_params()
         if self.independent_vars is None:
             self.independent_vars = []
@@ -130,6 +131,20 @@ def prefix(self, value):
         self._prefix = value
         self._parse_params()
 
+    @property
+    def param_names(self):
+        if self.is_composite():
+            return self._compute_composite_param_names()
+        else:
+            return self._param_names
+
+    def _compute_composite_param_names(self):
+        param_names = set()
+        for sub_model in self.components:
+            param_names |= sub_model.param_names
+        param_names |= self._param_names
+        return param_names
+
     def is_composite(self):
         return len(self.components) > 0
 
@@ -203,7 +218,7 @@ def _parse_params(self):
                 arg in self._forbidden_args):
                 raise ValueError(self._invalid_par % (arg, fname))
 
-        self.param_names = set(names)
+        self._param_names = set(names)
 
     def set_param_hint(self, name, **kwargs):
         """set hints for parameter, including optional bounds
@@ -287,7 +302,7 @@ def make_params(self, **kwargs):
                     if item in  hint:
                         setattr(par, item, hint[item])
                 # Add the new parameter to the self.param_names
-                self.param_names.add(name)
+                self._param_names.add(name)
                 if verbose: print( ' - Adding parameter "%s"' % name)
         return params
 
@@ -470,13 +485,11 @@ def __add__(self, other):
         if len(self.components) > 0:
             # If the model is already composite just add other as component
             self.components.append(other)
-            self.param_names |= other.param_names
             return self
         else:
             # make new composite Model, add self and other as components
             new = Model(func=None)
             new.components = [self, other]
-            new.param_names = set(self.param_names | other.param_names)
             # we assume that all the sub-models have the same independent vars
             new.independent_vars = self.independent_vars[:]
             return new


Test patch: