Repository: mne-tools/mne-python

Issue:
BaseEstimator.__repr__ implementation in mne/fixes is wrong
### Description of the problem

I was wondering why my output is spammed with estimator variables using MNE (in combination with MOABB):
It always looked like this:

```
{'component_order': 'mutual_info',
 'cov_est': 'concat',
 'cov_method_params': None,
 'log': None,
 'n_components': 8,
 'norm_trace': False,
 'rank': None,
 'reg': None,
 'transform_into': 'average_power'}
{'component_order': 'mutual_info',
 'cov_est': 'concat',
 'cov_method_params': None,
 'log': None,
 'n_components': 8,
 'norm_trace': False,
 'rank': None,
 'reg': None,
 'transform_into': 'average_power'}
... (and so on)
```

As it turned out the `__repr__` function of `BaseEstimator` (in mne/fixes) uses `pprint`. The problem is, that pprint does usually write to `sys.stdout` unless told otherwise and does return None. So the `repr` function does not work correctly and just spams the output.

One could solve the problem by using a StringIO object and let pprint write into it.
```python
from io import StringIO
from pprint import pprint

...

class BaseEstimator(object):
   ...
  def __repr__(self):
    pretty_string = StringIO()
    pprint(self.get_params(deep=False), pretty_string)
    pretty_string.seek(0)
    class_name = self.__class__.__name__
    return '%s(%s)' % (class_name, pretty_string.read().strip()))
```

### Steps to reproduce

```Python
from mne.decoding import CSP

csp = CSP()
res = repr(csp)
print(res)
```


### Link to data

_No response_

### Expected results

```
>>> res = repr(csp)
>>> print(res)
CSP({'component_order': 'mutual_info',
 'cov_est': 'concat',
 'cov_method_params': None,
 'log': None,
 'n_components': 4,
 'norm_trace': False,
 'rank': None,
 'reg': None,
 'transform_into': 'average_power'})
```

### Actual results

```
>>> res = repr(csp)
{'component_order': 'mutual_info',
 'cov_est': 'concat',
 'cov_method_params': None,
 'log': None,
 'n_components': 4,
 'norm_trace': False,
 'rank': None,
 'reg': None,
 'transform_into': 'average_power'}
>>> print(res)
CSP(None)
```

### Additional information

Platform:         Linux-5.10.136-1-MANJARO-x86_64-with-glibc2.36
Python:           3.10.5 (main, Aug  1 2022, 07:53:20) [GCC 12.1.0]
Executable:       /usr/bin/python
CPU:              : 4 cores
Memory:           31.1 GB

mne:              1.1.0
numpy:            1.23.1 {blas=NO_ATLAS_INFO, lapack=lapack}
scipy:            1.9.0
matplotlib:       3.5.2 {backend=QtAgg}

sklearn:          1.1.1
numba:            Not found
nibabel:          Not found
nilearn:          Not found
dipy:             Not found
cupy:             Not found
pandas:           1.4.3
pyvista:          Not found
pyvistaqt:        Not found
ipyvtklink:       Not found
vtk:              Not found
qtpy:             Not found
ipympl:           Not found
pyqtgraph:        Not found
pooch:            v1.6.0

mne_bids:         Not found
mne_nirs:         Not found
mne_features:     Not found
mne_qt_browser:   Not found
mne_connectivity: Not found
mne_icalabel:     Not found



Discussion:
Hello! 👋 Thanks for opening your first issue here! ❤️ We will try to get back to you soon. 🚴🏽‍♂️

I like your StringIO idea, would you be up for trying a PR to fix it?
I think we should follow the `repr` strategy from sklearn for decoding estimators.

Resolving patch:
diff --git a/mne/fixes.py b/mne/fixes.py
--- a/mne/fixes.py
+++ b/mne/fixes.py
@@ -15,6 +15,7 @@
 import inspect
 from math import log
 from pprint import pprint
+from io import StringIO
 import os
 import warnings
 
@@ -460,8 +461,11 @@ def set_params(self, **params):
         return self
 
     def __repr__(self):
+        params = StringIO()
+        pprint(self.get_params(deep=False), params)
+        params.seek(0)
         class_name = self.__class__.__name__
-        return '%s(%s)' % (class_name, pprint(self.get_params(deep=False)))
+        return '%s(%s)' % (class_name, params.read().strip())
 
     # __getstate__ and __setstate__ are omitted because they only contain
     # conditionals that are not satisfied by our objects (e.g.,


Test patch: