From 693ddad68ced6b905b289d89fbc11ddc80fac7b2 2017-11-14 11:55:40 From: Thomas Kluyver Date: 2017-11-14 11:55:40 Subject: [PATCH] Avoid unnecessary calculation of indent on every line --- diff --git a/IPython/core/inputsplitter.py b/IPython/core/inputsplitter.py index 92a28b1..0ae30b1 100644 --- a/IPython/core/inputsplitter.py +++ b/IPython/core/inputsplitter.py @@ -290,10 +290,11 @@ class InputSplitter(object): isp.push(line) print 'Input source was:\n', isp.source_reset(), """ - # Number of spaces of indentation computed from input that has been pushed - # so far. This is the attributes callers should query to get the current - # indentation level, in order to provide auto-indent facilities. - indent_spaces = 0 + # A cache for calculating the current indentation. + # If the first value matches self.source, the second value is an integer + # number of spaces for the current indentation. If the first value does not + # match, self.source has changed, and the indentation must be recalculated. + _indent_spaces_cache = None, None # String, indicating the default input encoding. It is computed by default # at initialization time via get_input_encoding(), but it can be reset by a # client with specific knowledge of the encoding. @@ -327,7 +328,6 @@ class InputSplitter(object): def reset(self): """Reset the input buffer and associated state.""" - self.indent_spaces = 0 self._buffer[:] = [] self.source = '' self.code = None @@ -371,7 +371,7 @@ class InputSplitter(object): if self._is_invalid: return 'invalid', None elif self.push_accepts_more(): - return 'incomplete', self.indent_spaces + return 'incomplete', self.get_indent_spaces() else: return 'complete', None finally: @@ -412,7 +412,6 @@ class InputSplitter(object): if source.endswith('\\\n'): return False - self._update_indent() try: with warnings.catch_warnings(): warnings.simplefilter('error', SyntaxWarning) @@ -470,7 +469,7 @@ class InputSplitter(object): # If there's just a single line or AST node, and we're flush left, as is # the case after a simple statement such as 'a=1', we want to execute it # straight away. - if self.indent_spaces==0: + if self.get_indent_spaces() == 0: if len(self.source.splitlines()) <= 1: return False @@ -488,9 +487,15 @@ class InputSplitter(object): # General fallback - accept more code return True - def _update_indent(self): + def get_indent_spaces(self): + sourcefor, n = self._indent_spaces_cache + if sourcefor == self.source: + return n + # self.source always has a trailing newline - self.indent_spaces = find_next_indent(self.source[:-1]) + n = find_next_indent(self.source[:-1]) + self._indent_spaces_cache = (self.source, n) + return n def _store(self, lines, buffer=None, store='source'): """Store one or more lines of input. diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 1d4fed5..e094a11 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1940,7 +1940,7 @@ class InteractiveShell(SingletonConfigurable): def _indent_current_str(self): """return the current level of indentation as a string""" - return self.input_splitter.indent_spaces * ' ' + return self.input_splitter.get_indent_spaces() * ' ' #------------------------------------------------------------------------- # Things related to text completion diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index d528078..0b3e47d 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -37,7 +37,7 @@ def mini_interactive_loop(input_func): # input indefinitely, until some exit/quit command was issued. Here we # only illustrate the basic inner loop. while isp.push_accepts_more(): - indent = ' '*isp.indent_spaces + indent = ' '*isp.get_indent_spaces() prompt = '>>> ' + indent line = indent + input_func(prompt) isp.push(line) @@ -132,7 +132,7 @@ class InputSplitterTestCase(unittest.TestCase): isp.push('x=1') isp.reset() self.assertEqual(isp._buffer, []) - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) self.assertEqual(isp.source, '') self.assertEqual(isp.code, None) self.assertEqual(isp._is_complete, False) @@ -149,21 +149,21 @@ class InputSplitterTestCase(unittest.TestCase): def test_indent(self): isp = self.isp # shorthand isp.push('x=1') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) isp.push('if 1:\n x=1') - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) isp.push('y=2\n') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) def test_indent2(self): isp = self.isp isp.push('if 1:') - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) isp.push(' x=1') - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) # Blank lines shouldn't change the indent level isp.push(' '*2) - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) def test_indent3(self): isp = self.isp @@ -171,75 +171,75 @@ class InputSplitterTestCase(unittest.TestCase): # shouldn't get confused. isp.push("if 1:") isp.push(" x = (1+\n 2)") - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) def test_indent4(self): isp = self.isp # whitespace after ':' should not screw up indent level isp.push('if 1: \n x=1') - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) isp.push('y=2\n') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) isp.push('if 1:\t\n x=1') - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) isp.push('y=2\n') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) def test_dedent_pass(self): isp = self.isp # shorthand # should NOT cause dedent isp.push('if 1:\n passes = 5') - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) isp.push('if 1:\n pass') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) isp.push('if 1:\n pass ') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) def test_dedent_break(self): isp = self.isp # shorthand # should NOT cause dedent isp.push('while 1:\n breaks = 5') - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) isp.push('while 1:\n break') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) isp.push('while 1:\n break ') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) def test_dedent_continue(self): isp = self.isp # shorthand # should NOT cause dedent isp.push('while 1:\n continues = 5') - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) isp.push('while 1:\n continue') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) isp.push('while 1:\n continue ') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) def test_dedent_raise(self): isp = self.isp # shorthand # should NOT cause dedent isp.push('if 1:\n raised = 4') - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) isp.push('if 1:\n raise TypeError()') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) isp.push('if 1:\n raise') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) isp.push('if 1:\n raise ') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) def test_dedent_return(self): isp = self.isp # shorthand # should NOT cause dedent isp.push('if 1:\n returning = 4') - self.assertEqual(isp.indent_spaces, 4) + self.assertEqual(isp.get_indent_spaces(), 4) isp.push('if 1:\n return 5 + 493') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) isp.push('if 1:\n return') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) isp.push('if 1:\n return ') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) isp.push('if 1:\n return(0)') - self.assertEqual(isp.indent_spaces, 0) + self.assertEqual(isp.get_indent_spaces(), 0) def test_push(self): isp = self.isp @@ -508,7 +508,7 @@ if __name__ == '__main__': while True: prompt = start_prompt while isp.push_accepts_more(): - indent = ' '*isp.indent_spaces + indent = ' '*isp.get_indent_spaces() if autoindent: line = indent + input(prompt+indent) else: