Repository: ipython/ipython

Issue:
Better way to limit output cache size
As discussed on #712 (tagging @stefanv):

At present, if the output cache fills up (default 1000 items), it will be automatically flushed, leaving you with just the last three outputs in `_`, `__` and `___`. This could be annoying if you wanted to refer to something that's just been lost, and you're only warned about this when it's already happening.

I propose that we drop the oldest 20% (say) of items when we hit the cache limit. This is a little more computationally intensive (sort 1000 integers, delete 200 keys from a dictionary), but it means that we always have at least the last 800 pyout objects stored. I think this should either be silent, or with a brief message the first time it happens (e.g. "Starting to delete oldest cached output. The latest %d outputs at any point will still be available.").



Discussion:

Resolving patch:
diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py
--- a/IPython/core/displayhook.py
+++ b/IPython/core/displayhook.py
@@ -15,7 +15,7 @@
 from IPython.config.configurable import Configurable
 from IPython.utils import io
 from IPython.utils.py3compat import builtin_mod
-from IPython.utils.traitlets import Instance
+from IPython.utils.traitlets import Instance, Float
 from IPython.utils.warn import warn
 
 # TODO: Move the various attributes (cache_size, [others now moved]). Some
@@ -30,10 +30,10 @@ class DisplayHook(Configurable):
     """
 
     shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
+    cull_fraction = Float(0.2)
 
     def __init__(self, shell=None, cache_size=1000, **kwargs):
         super(DisplayHook, self).__init__(shell=shell, **kwargs)
-
         cache_size_min = 3
         if cache_size <= 0:
             self.do_full_cache = 0
@@ -178,13 +178,7 @@ def update_user_ns(self, result):
         # Avoid recursive reference when displaying _oh/Out
         if result is not self.shell.user_ns['_oh']:
             if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
-                warn('Output cache limit (currently '+
-                      repr(self.cache_size)+' entries) hit.\n'
-                     'Flushing cache and resetting history counter...\n'
-                     'The only history variables available will be _,__,___ and _1\n'
-                     'with the current result.')
-
-                self.flush()
+                self.cull_cache()
             # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise
             # we cause buggy behavior for things like gettext).
 
@@ -243,6 +237,21 @@ def __call__(self, result=None):
             self.log_output(format_dict)
             self.finish_displayhook()
 
+    def cull_cache(self):
+        """Output cache is full, cull the oldest entries"""
+        oh = self.shell.user_ns.get('_oh', {})
+        sz = len(oh)
+        cull_count = max(int(sz * self.cull_fraction), 2)
+        warn('Output cache limit (currently {sz} entries) hit.\n'
+             'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
+        
+        for i, n in enumerate(sorted(oh)):
+            if i >= cull_count:
+                break
+            self.shell.user_ns.pop('_%i' % n, None)
+            oh.pop(n, None)
+        
+
     def flush(self):
         if not self.do_full_cache:
             raise ValueError("You shouldn't have reached the cache flush "


Test patch: