diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py index 578e783..25aa2f0 100644 --- a/IPython/core/displayhook.py +++ b/IPython/core/displayhook.py @@ -91,7 +91,13 @@ class DisplayHook(Configurable): # some uses of ipshellembed may fail here return False - sio = _io.StringIO(cell) + return self.semicolon_at_end_of_expression(cell) + + @staticmethod + def semicolon_at_end_of_expression(expression): + """Parse Python expression and detects whether last token is ';'""" + + sio = _io.StringIO(expression) tokens = list(tokenize.generate_tokens(sio.readline)) for token in reversed(tokens): diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index c8aacdc..69b4418 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2363,12 +2363,12 @@ class InteractiveShell(SingletonConfigurable): with self.builtin_trap: result = fn(*args, **kwargs) - # The code below prevents output from being displayed - # when using magic %time. - # Output from '%time foo();', for instance, would never - # be displayed. - if magic_name == "time" and len(magic_arg_s) > 0 and magic_arg_s[-1] == ";": - return None + # The code below prevents the output from being displayed + # when using magics with decodator @output_can_be_disabled + # when the last Python token in the expression is a ';'. + if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_DISABLED, False): + if DisplayHook.semicolon_at_end_of_expression(magic_arg_s): + return None return result diff --git a/IPython/core/magic.py b/IPython/core/magic.py index cedba61..0eadc17 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -258,7 +258,7 @@ def _function_magic_marker(magic_kind): MAGIC_NO_VAR_EXPAND_ATTR = '_ipython_magic_no_var_expand' - +MAGIC_OUTPUT_CAN_BE_DISABLED = '_ipython_magic_output_can_be_disabled' def no_var_expand(magic_func): """Mark a magic function as not needing variable expansion @@ -275,6 +275,15 @@ def no_var_expand(magic_func): setattr(magic_func, MAGIC_NO_VAR_EXPAND_ATTR, True) return magic_func +def output_can_be_disabled(magic_func): + """Mark a magic function so its output may be disabled. + + The output is disabled if the Python expression used as a parameter of + the magic ends in a semicolon, not counting a Python comment that can + follows it. + """ + setattr(magic_func, MAGIC_OUTPUT_CAN_BE_DISABLED, True) + return magic_func # Create the actual decorators for public use diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index da7f780..5d7942f 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -37,6 +37,7 @@ from IPython.core.magic import ( magics_class, needs_local_scope, no_var_expand, + output_can_be_disabled, on_off, ) from IPython.testing.skipdoctest import skip_doctest @@ -1194,6 +1195,7 @@ class ExecutionMagics(Magics): @no_var_expand @needs_local_scope @line_cell_magic + @output_can_be_disabled def time(self,line='', cell=None, local_ns=None): """Time execution of a Python statement or expression. diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index cb9890c..55408d4 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -419,7 +419,7 @@ def test_time(): # ';' at the end of %time prevents instruction value to be printed. # This tests fix for #13837. -def test_time_no_outputwith_semicolon(): +def test_time_no_output_with_semicolon(): ip = get_ipython() with tt.AssertPrints(" 123456"): @@ -432,6 +432,21 @@ def test_time_no_outputwith_semicolon(): with tt.AssertPrints("CPU times: ", suppress=False): ip.run_cell("%time 123000+456;") + with tt.AssertPrints(" 123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%time 123000+456 # Comment") + + with tt.AssertNotPrints(" 123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%time 123000+456; # Comment") + + with tt.AssertPrints(" 123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%time 123000+456 # ;Comment") + def test_time_last_not_expression(): ip.run_cell("%%time\n"