Format: text/x-python
⬇ Download File (12.7 KB)
   1  # -*- coding: utf-8 -*-
   2  """Displayhook for IPython.
   3  
   4  This defines a callable class that IPython uses for `sys.displayhook`.
   5  """
   6  
   7  # Copyright (c) IPython Development Team.
   8  # Distributed under the terms of the Modified BSD License.
   9  
  10  import builtins as builtin_mod
  11  import sys
  12  import io as _io
  13  import tokenize
  14  
  15  from traitlets.config.configurable import Configurable
  16  from traitlets import Instance, Float
  17  from warnings import warn
  18  
  19  # TODO: Move the various attributes (cache_size, [others now moved]). Some
  20  # of these are also attributes of InteractiveShell. They should be on ONE object
  21  # only and the other objects should ask that one object for their values.
  22  
  23  class DisplayHook(Configurable):
  24      """The custom IPython displayhook to replace sys.displayhook.
  25  
  26      This class does many things, but the basic idea is that it is a callable
  27      that gets called anytime user code returns a value.
  28      """
  29  
  30      shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
  31                       allow_none=True)
  32      exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
  33                             allow_none=True)
  34      cull_fraction = Float(0.2)
  35  
  36      def __init__(self, shell=None, cache_size=1000, **kwargs):
  37          super(DisplayHook, self).__init__(shell=shell, **kwargs)
  38          cache_size_min = 3
  39          if cache_size <= 0:
  40              self.do_full_cache = 0
  41              cache_size = 0
  42          elif cache_size < cache_size_min:
  43              self.do_full_cache = 0
  44              cache_size = 0
  45              warn('caching was disabled (min value for cache size is %s).' %
  46                   cache_size_min,stacklevel=3)
  47          else:
  48              self.do_full_cache = 1
  49  
  50          self.cache_size = cache_size
  51  
  52          # we need a reference to the user-level namespace
  53          self.shell = shell
  54          
  55          self._,self.__,self.___ = '','',''
  56  
  57          # these are deliberately global:
  58          to_user_ns = {'_':self._,'__':self.__,'___':self.___}
  59          self.shell.user_ns.update(to_user_ns)
  60  
  61      @property
  62      def prompt_count(self):
  63          return self.shell.execution_count
  64  
  65      #-------------------------------------------------------------------------
  66      # Methods used in __call__. Override these methods to modify the behavior
  67      # of the displayhook.
  68      #-------------------------------------------------------------------------
  69  
  70      def check_for_underscore(self):
  71          """Check if the user has set the '_' variable by hand."""
  72          # If something injected a '_' variable in __builtin__, delete
  73          # ipython's automatic one so we don't clobber that.  gettext() in
  74          # particular uses _, so we need to stay away from it.
  75          if '_' in builtin_mod.__dict__:
  76              try:
  77                  user_value = self.shell.user_ns['_']
  78                  if user_value is not self._:
  79                      return
  80                  del self.shell.user_ns['_']
  81              except KeyError:
  82                  pass
  83  
  84      def quiet(self):
  85          """Should we silence the display hook because of ';'?"""
  86          # do not print output if input ends in ';'
  87          
  88          try:
  89              cell = self.shell.history_manager.input_hist_parsed[-1]
  90          except IndexError:
  91              # some uses of ipshellembed may fail here
  92              return False
  93          
  94          return self.semicolon_at_end_of_expression(cell)
  95  
  96      @staticmethod
  97      def semicolon_at_end_of_expression(expression):
  98          """Parse Python expression and detects whether last token is ';'"""
  99  
 100          sio = _io.StringIO(expression)
 101          tokens = list(tokenize.generate_tokens(sio.readline))
 102  
 103          for token in reversed(tokens):
 104              if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
 105                  continue
 106              if (token[0] == tokenize.OP) and (token[1] == ';'):
 107                  return True
 108              else:
 109                  return False
 110  
 111      def start_displayhook(self):
 112          """Start the displayhook, initializing resources."""
 113          pass
 114  
 115      def write_output_prompt(self):
 116          """Write the output prompt.
 117  
 118          The default implementation simply writes the prompt to
 119          ``sys.stdout``.
 120          """
 121          # Use write, not print which adds an extra space.
 122          sys.stdout.write(self.shell.separate_out)
 123          outprompt = 'Out[{}]: '.format(self.shell.execution_count)
 124          if self.do_full_cache:
 125              sys.stdout.write(outprompt)
 126  
 127      def compute_format_data(self, result):
 128          """Compute format data of the object to be displayed.
 129  
 130          The format data is a generalization of the :func:`repr` of an object.
 131          In the default implementation the format data is a :class:`dict` of
 132          key value pair where the keys are valid MIME types and the values
 133          are JSON'able data structure containing the raw data for that MIME
 134          type. It is up to frontends to determine pick a MIME to to use and
 135          display that data in an appropriate manner.
 136  
 137          This method only computes the format data for the object and should
 138          NOT actually print or write that to a stream.
 139  
 140          Parameters
 141          ----------
 142          result : object
 143              The Python object passed to the display hook, whose format will be
 144              computed.
 145  
 146          Returns
 147          -------
 148          (format_dict, md_dict) : dict
 149              format_dict is a :class:`dict` whose keys are valid MIME types and values are
 150              JSON'able raw data for that MIME type. It is recommended that
 151              all return values of this should always include the "text/plain"
 152              MIME type representation of the object.
 153              md_dict is a :class:`dict` with the same MIME type keys
 154              of metadata associated with each output.
 155  
 156          """
 157          return self.shell.display_formatter.format(result)
 158  
 159      # This can be set to True by the write_output_prompt method in a subclass
 160      prompt_end_newline = False
 161  
 162      def write_format_data(self, format_dict, md_dict=None) -> None:
 163          """Write the format data dict to the frontend.
 164  
 165          This default version of this method simply writes the plain text
 166          representation of the object to ``sys.stdout``. Subclasses should
 167          override this method to send the entire `format_dict` to the
 168          frontends.
 169  
 170          Parameters
 171          ----------
 172          format_dict : dict
 173              The format dict for the object passed to `sys.displayhook`.
 174          md_dict : dict (optional)
 175              The metadata dict to be associated with the display data.
 176          """
 177          if 'text/plain' not in format_dict:
 178              # nothing to do
 179              return
 180          # We want to print because we want to always make sure we have a
 181          # newline, even if all the prompt separators are ''. This is the
 182          # standard IPython behavior.
 183          result_repr = format_dict['text/plain']
 184          if '\n' in result_repr:
 185              # So that multi-line strings line up with the left column of
 186              # the screen, instead of having the output prompt mess up
 187              # their first line.
 188              # We use the prompt template instead of the expanded prompt
 189              # because the expansion may add ANSI escapes that will interfere
 190              # with our ability to determine whether or not we should add
 191              # a newline.
 192              if not self.prompt_end_newline:
 193                  # But avoid extraneous empty lines.
 194                  result_repr = '\n' + result_repr
 195  
 196          try:
 197              print(result_repr)
 198          except UnicodeEncodeError:
 199              # If a character is not supported by the terminal encoding replace
 200              # it with its \u or \x representation
 201              print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding))
 202  
 203      def update_user_ns(self, result):
 204          """Update user_ns with various things like _, __, _1, etc."""
 205  
 206          # Avoid recursive reference when displaying _oh/Out
 207          if self.cache_size and result is not self.shell.user_ns['_oh']:
 208              if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
 209                  self.cull_cache()
 210  
 211              # Don't overwrite '_' and friends if '_' is in __builtin__
 212              # (otherwise we cause buggy behavior for things like gettext). and
 213              # do not overwrite _, __ or ___ if one of these has been assigned
 214              # by the user.
 215              update_unders = True
 216              for unders in ['_'*i for i in range(1,4)]:
 217                  if not unders in self.shell.user_ns:
 218                      continue
 219                  if getattr(self, unders) is not self.shell.user_ns.get(unders):
 220                      update_unders = False
 221  
 222              self.___ = self.__
 223              self.__ = self._
 224              self._ = result
 225  
 226              if ('_' not in builtin_mod.__dict__) and (update_unders):
 227                  self.shell.push({'_':self._,
 228                                   '__':self.__,
 229                                  '___':self.___}, interactive=False)
 230  
 231              # hackish access to top-level  namespace to create _1,_2... dynamically
 232              to_main = {}
 233              if self.do_full_cache:
 234                  new_result = '_%s' % self.prompt_count
 235                  to_main[new_result] = result
 236                  self.shell.push(to_main, interactive=False)
 237                  self.shell.user_ns['_oh'][self.prompt_count] = result
 238  
 239      def fill_exec_result(self, result):
 240          if self.exec_result is not None:
 241              self.exec_result.result = result
 242  
 243      def log_output(self, format_dict):
 244          """Log the output."""
 245          if 'text/plain' not in format_dict:
 246              # nothing to do
 247              return
 248          if self.shell.logger.log_output:
 249              self.shell.logger.log_write(format_dict['text/plain'], 'output')
 250          self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
 251                                                      format_dict['text/plain']
 252  
 253      def finish_displayhook(self):
 254          """Finish up all displayhook activities."""
 255          sys.stdout.write(self.shell.separate_out2)
 256          sys.stdout.flush()
 257  
 258      def __call__(self, result=None):
 259          """Printing with history cache management.
 260  
 261          This is invoked every time the interpreter needs to print, and is
 262          activated by setting the variable sys.displayhook to it.
 263          """
 264          self.check_for_underscore()
 265          if result is not None and not self.quiet():
 266              self.start_displayhook()
 267              self.write_output_prompt()
 268              format_dict, md_dict = self.compute_format_data(result)
 269              self.update_user_ns(result)
 270              self.fill_exec_result(result)
 271              if format_dict:
 272                  self.write_format_data(format_dict, md_dict)
 273                  self.log_output(format_dict)
 274              self.finish_displayhook()
 275  
 276      def cull_cache(self):
 277          """Output cache is full, cull the oldest entries"""
 278          oh = self.shell.user_ns.get('_oh', {})
 279          sz = len(oh)
 280          cull_count = max(int(sz * self.cull_fraction), 2)
 281          warn('Output cache limit (currently {sz} entries) hit.\n'
 282               'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
 283          
 284          for i, n in enumerate(sorted(oh)):
 285              if i >= cull_count:
 286                  break
 287              self.shell.user_ns.pop('_%i' % n, None)
 288              oh.pop(n, None)
 289          
 290  
 291      def flush(self):
 292          if not self.do_full_cache:
 293              raise ValueError("You shouldn't have reached the cache flush "
 294                               "if full caching is not enabled!")
 295          # delete auto-generated vars from global namespace
 296  
 297          for n in range(1,self.prompt_count + 1):
 298              key = '_'+repr(n)
 299              try:
 300                  del self.shell.user_ns_hidden[key]
 301              except KeyError:
 302                  pass
 303              try:
 304                  del self.shell.user_ns[key]
 305              except KeyError:
 306                  pass
 307          # In some embedded circumstances, the user_ns doesn't have the
 308          # '_oh' key set up.
 309          oh = self.shell.user_ns.get('_oh', None)
 310          if oh is not None:
 311              oh.clear()
 312  
 313          # Release our own references to objects:
 314          self._, self.__, self.___ = '', '', ''
 315  
 316          if '_' not in builtin_mod.__dict__:
 317              self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___})
 318          import gc
 319          # TODO: Is this really needed?
 320          # IronPython blocks here forever
 321          if sys.platform != "cli":
 322              gc.collect()
 323  
 324  
 325  class CapturingDisplayHook(object):
 326      def __init__(self, shell, outputs=None):
 327          self.shell = shell
 328          if outputs is None:
 329              outputs = []
 330          self.outputs = outputs
 331  
 332      def __call__(self, result=None):
 333          if result is None:
 334              return
 335          format_dict, md_dict = self.shell.display_formatter.format(result)
 336          self.outputs.append({ 'data': format_dict, 'metadata': md_dict })
 337