Repository: mne-tools/mne-python

Issue:
Improve MNE performance in IDEs / Limit object string representations 
#### Describe the new feature or enhancement

I have the unfortunate case of having MNE epochs that have a vast number of unique events (> 4000). While this may be a fault of my data not using the metadata property, this still leads to bad performance of MNE in, e.g., PyCharm.
PyCharm has a "Variable Explorer" window which (as I suspect) makes use of the string representation of current objects in memory and updates after each update, e.g., when executing a line in the interactive python console. Unfortunately, in the case of BaseEpoch the string representation sorts and enumerates all events, leading to quite the large string representation when an epoch has many events.

https://github.com/mne-tools/mne-python/blob/0789b7dade297a7e5cae10a343a870ed260ab9a0/mne/epochs.py#L1664-L1667

Even without sophisticated IDEs, in my work flow I often execute code in a console interactively. E.g. to take a quick look at the epoch object i simply type `epochs` and press enter, as the default for python consoles is to print the string representation of the objects. Currently this takes around 20 seconds to print on my machine.

#### Describe your proposed implementation

Replace the above lines with
```python
        events_limit = 10
        counts = ['%r: %i' % (k, sum(self.events[:, 2] == v))
                  for k, v in sorted(self.event_id.items())[:events_limit]]
        if len(self.event_id) > 0:
            s += ',' + '\n '.join([''] + counts)
        if len(self.event_id) > events_limit:
            not_shown_events = len(self.event_id) - events_limit + 1
            s += f"\n 'and {not_shown_events} more events ...'"

```

For my epoch object, the above change reduces `len(epochs.__repr__())` from 257655 to 717 chars. Example output:

```
>>> epochs
<Epochs |  4283 events (all good), -0.2 - 0.75 sec, baseline off, ~20.3 MB, data loaded,
 'Letter_1/Sequence_1/NonTarget/A/B/G/J/L/Q/R/T/W/Y/ /?': 1
 'Letter_1/Sequence_1/NonTarget/A/C/D/J/K/L/O/S/X/./!/<': 1
 'Letter_1/Sequence_1/NonTarget/A/C/E/G/J/K/L/P/W/Z/,/<': 1
 'Letter_1/Sequence_1/NonTarget/A/D/G/H/K/M/N/U/V/W/Y/?': 1
 'Letter_1/Sequence_1/NonTarget/A/D/H/M/O/Q/T/W/Y/Z/,/<': 1
 'Letter_1/Sequence_1/NonTarget/A/D/H/M/P/Q/T/W/Y/!/?/<': 1
 'Letter_1/Sequence_1/NonTarget/A/E/G/I/L/O/Q/S/U/Y/./?': 1
 'Letter_1/Sequence_1/NonTarget/A/E/G/J/L/N/P/Q/S/U/ /?': 1
 'Letter_1/Sequence_1/NonTarget/A/E/H/I/K/O/R/T/U/./,/?': 1
 'Letter_1/Sequence_1/NonTarget/B/C/D/G/L/N/P/W/X/Z/,/!': 1
 'and 4273 more events ...'>
```

#### Describe possible alternatives

One could also add an optional keyword to `__repr__`, then one could replicate the old behaviour by calling `epochs.__repr__(full=True)`. Or one could instruct people to not use so damn many events when they could use metadata :)



Discussion:
> For my epoch object, the above change reduces len(epochs.__repr__()) from 257655 to 717 chars. Example output:

That looks good to me, agreed 10 events is a reasonable limit

Resolving patch:
diff --git a/mne/epochs.py b/mne/epochs.py
--- a/mne/epochs.py
+++ b/mne/epochs.py
@@ -1661,10 +1661,14 @@ def __repr__(self):
         s += ', ~%s' % (sizeof_fmt(self._size),)
         s += ', data%s loaded' % ('' if self.preload else ' not')
         s += ', with metadata' if self.metadata is not None else ''
+        max_events = 10
         counts = ['%r: %i' % (k, sum(self.events[:, 2] == v))
-                  for k, v in sorted(self.event_id.items())]
+                  for k, v in list(self.event_id.items())[:max_events]]
         if len(self.event_id) > 0:
             s += ',' + '\n '.join([''] + counts)
+        if len(self.event_id) > max_events:
+            not_shown_events = len(self.event_id) - max_events
+            s += f"\n and {not_shown_events} more events ..."
         class_name = self.__class__.__name__
         class_name = 'Epochs' if class_name == 'BaseEpochs' else class_name
         return '<%s | %s>' % (class_name, s)
diff --git a/mne/evoked.py b/mne/evoked.py
--- a/mne/evoked.py
+++ b/mne/evoked.py
@@ -301,7 +301,13 @@ def save(self, fname):
         write_evokeds(fname, self)
 
     def __repr__(self):  # noqa: D105
-        s = "'%s' (%s, N=%s)" % (self.comment, self.kind, self.nave)
+        max_comment_length = 1000
+        if len(self.comment) > max_comment_length:
+            comment = self.comment[:max_comment_length]
+            comment += "..."
+        else:
+            comment = self.comment
+        s = "'%s' (%s, N=%s)" % (comment, self.kind, self.nave)
         s += ", %0.5g – %0.5g sec" % (self.times[0], self.times[-1])
         s += ', baseline '
         if self.baseline is None:


Test patch: